协议缓冲区与扩展功能

3
我可能忽略了什么,但我试图将协议缓冲区转换为提供扩展的简单方法。这似乎有点不清楚,所以我直接跳到问题上。
我正在编写一个程序集来支持各种任务,其中之一包括描述结构化数据。使用协议缓冲区的完美时机。使用协议缓冲区的主要类别称为StateDefinition。这是我为它想出的.proto文件: package Kannon.State; message StateDefinition { enum StateTypes { GRAPHICS = 0; AUDIO = 1; MIND = 2; PHYSICS = 3; NETWORK = 4; GENERIC = 5; } repeated StateTypes requiredStates = 1; optional GraphicsStateDef Graphics = 2; optional AudioStateDef Audio = 3; (等等) }
message GraphicsStateDef { extensions 100 to max; }
message AudioStateDef { extensions 100 to max; } (等等)
我的目标是允许那些_StateDef消息稍后通过需要的字段进行扩展。然而,此扩展将独立于我当前编写的库发生。
Kagents.dll -> 处理StateDefinition解析等。
引用Kagents.dll的某个东西 -> 具有“扩展GraphicsStateDef”以定义所需状态的protobuff文件。
我希望定义“扩展GraphicsStateDef”会生成代码,使我能够使用属性来访问这些字段,并避免笨重的“Extendible.AppendValue()”和GetValue()语法。
我想出了一个解决方案,似乎很麻烦,就是在引用DLL中定义一个类,其中包含扩展方法,如下所示: public static class GraphicsExt { enum Fields { someValue = 1, someOtherValue = 2 }
public static Int32 someValue(this State.GraphicsStateDef def) { return Extensible.GetValue(def, Fields.someValue); } public static void someValue(this State.graphicsStateDef def, Int32 value) { Extensible.AppendValue(def, fields.someValue, value); } }
如果有人能想到更好的方法,我将不胜感激。=) 此外,我不确定我的问题描述是否清晰明了,因此如果需要澄清或提供进一步信息,请告诉我。=)
编辑: 因此,在仔细考虑后,我意识到我正在错误地处理问题。 StateReference应该存储不同GameState的列表。此外,它存储一个StateDefinition,该定义应描述此状态引用的状态。当前,我尝试将状态缓冲区反序列化为不同的类(GraphicsStateDef),而实际上应该反序列化为状态对象本身。
因此,我需要重新考虑设计,使StateDefinition成为流的容器,并仅提取“repeated StateTypes requiredStates=1”字段所需的足够信息。然后,在引用程序集中,其余部分的流可以反序列化为相应的状态。
有没有人对如何解决这个问题有建议?有一些想法正在形成,但还没有具体的方案,我很希望得到其他人的意见。

你正在使用protobuf-net吗?对于扩展定义的代码生成是否存在已知问题? - Merritt
是的,我正在使用protobuf-net。不过,就我所知没有这样的问题,我会再检查一下。问题并不在于代码生成,而是我无法想出要使用哪种语言机制来在外部程序集中“完成”类。部分类很好用,但是拒绝跨越程序集边界。 - Quantumplation
我的修改有什么想法吗?我还不确定如何处理这个问题。 - Quantumplation
2个回答

1

我是protobuf-net的作者。除了Extensible代码外,我没有添加任何内容来解决直接呈现的情况,但我乐意听取您对它应该做什么的建议。

我还需要检查一下"protoc"(我用于解析.proto并生成代码之前的.proto编译器)是否允许我区分常规成员和扩展成员。


嗯,我不太确定。在代码生成后允许将扩展消息翻译成派生类似乎可以起作用。或者采取我的方法并自动化它,为对IExtensible接口进行自动访问生成“扩展方法”。我对你的系统不是很熟悉(现在使用了3到4天),因此我不太有把握提出解决方案。 - Quantumplation
很遗憾没有“扩展属性”...继承会很棘手,因为有一个单独的继承含义,不会很好地混合(特别是单一继承)。我还需要检查扩展是否可以区分,或者它们是否对协议来说是“相同的”...我会进行调查。 - Marc Gravell

0

最终答案:

好的,几天前我找到了一个解决方案,现在我更新一下,以防其他人遇到同样的问题。

整个问题的根源在于我没有意识到protobuf-net可以支持byte[]。所以,这是我的解决方案:

namespace Kannon.State
{
    /// <summary>
    /// ReferenceDefinition describes the layout of the reference in general.
    /// It tells what states it should have, and stores the stream buffers for later serialization.
    /// </summary>
    [ProtoBuf.ProtoContract]
    public class ReferenceDefinition
    {
        /// <summary>
        /// There are several built in state types, as well as rudimentary support for a "Generic" state.
        /// </summary>
        public enum StateType
        {
            Graphics=0,
            Audio,
            Mind,
            Physics,
            Network,
            Generic
        }

        /// <summary>
        /// Represents what states should be present in the ReferenceDefinition
        /// </summary>
        [ProtoBuf.ProtoMember(1)]
        List<StateType> m_StatesPresent = new List<StateType>();

        /// <summary>
        /// Represent a list of StateDefinitions, which hold the buffers for each different type of state.
        /// </summary>
        [ProtoBuf.ProtoMember(2)]
        List<StateDefinition> m_StateDefinition = new List<StateDefinition>();

        /// <summary>
        /// Add a state, mapped to a type, to this reference definition.
        /// </summary>
        /// <param name="type">Type of state to add</param>
        /// <param name="def">State definition to add.</param>
        public void AddState(StateType type, StateDefinition def)
        {
            // Enforce only 1 of each type, except for Generic, which can have as many as it wants.
            if (m_StatesPresent.Contains(type) && type != StateType.Generic)
                return;
            m_StatesPresent.Add(type);
            m_StateDefinition.Add(def);
        }
    }

    /// <summary>
    /// Represents a definition of some gamestate, storing protobuffered data to be remapped to the state.
    /// </summary>
    [ProtoBuf.ProtoContract]
    public class StateDefinition
    {
        /// <summary>
        /// Name of the state
        /// </summary>
        [ProtoBuf.ProtoMember(1)]
        string m_StateName;
        /// <summary>
        /// Byte array to store the "data" for later serialization.
        /// </summary>
        [ProtoBuf.ProtoMember(2)]
        byte[] m_Buffer;

        /// <summary>
        /// Constructor for the state definition, protected to enforce the Pack and Unpack functionality to keep things safe.
        /// </summary>
        /// <param name="name">Name of the state type.</param>
        /// <param name="buff">byte buffer to build state off of</param>
        protected StateDefinition(String name, byte[] buff)
        {
            m_StateName = name;
            m_Buffer = buff;
        }

        /// <summary>
        /// Unpack a StateDefinition into a GameState
        /// </summary>
        /// <typeparam name="T">Gamestate type to unpack into.  Must define Protobuf Contracts.</typeparam>
        /// <param name="def">State Definition to unpack.</param>
        /// <returns>The unpacked state data.</returns>
        public static T Unpack<T>(StateDefinition def) where T:GameState
        {
            // Make sure we're unpacking into the right state type.
            if (typeof(T).Name == def.m_StateName)
                return ProtoBuf.Serializer.Deserialize<T>(new MemoryStream(def.m_Buffer));
            else
                // Otherwise, return the equivalent of Null.
                return default(T);
        }

        /// <summary>
        /// Pack a state type into a State Definition
        /// </summary>
        /// <typeparam name="T">Gamestate to package up.  Upst define protobuf contracts.</typeparam>
        /// <param name="state">State to pack up.</param>
        /// <returns>A state definition serialized from the passed in state.</returns>
        public static StateDefinition Pack<T>(T state) where T:GameState
        {
            // Using a memory stream, to make sure Garbage Collection knows what's going on.
            using (MemoryStream s = new MemoryStream())
            {
                ProtoBuf.Serializer.Serialize<T>(s, state);
                // Uses typeof(T).Name to do semi-enforcement of type safety.  Not the best, but it works.
                return new StateDefinition(typeof(T).Name, s.ToArray());
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接