【问题标题】:Protocol Buffers with Extensions带有扩展的协议缓冲区
【发布时间】:2009-08-14 15:20:14
【问题描述】:

我可能忽略了一些东西,但我正在尝试将协议缓冲区转换为一种简单的方法,以便以后提供扩展。这似乎有点不清楚,所以我将直接进入问题。

我正在编写一个程序集来支持各种任务,其中之一包括描述结构化数据。使用协议缓冲区的最佳时机。使用协议缓冲区的主要类称为 StateDefinition。这是我为它设计的 .proto 文件:

包 Kannon.State; 消息状态定义 { 枚举状态类型 { 图形 = 0; 音频 = 1; 头脑 = 2; 物理 = 3; 网络 = 4; 通用 = 5; } 重复的 StateTypes requiredStates = 1; 可选 GraphicsStateDef 图形 = 2; 可选的 AudioStateDef 音频 = 3; (ETC) } 消息 GraphicsStateDef { 扩展 100 到最大; } 消息 AudioStateDef { 扩展 100 到最大; } (ETC)

我的目标是允许这些 _StateDef 消息在以后使用它需要的字段进行扩展。但是,此扩展将独立于我当前正在编写的库发生。

Kagents.dll -> 处理 StateDefinition 解析等。

引用 Kagents.dll 的东西 -> 有一个带有“扩展 GraphicsStateDef”的 protobuff 文件来定义所需的状态。

我希望定义“扩展 GraphicsStateDef”会生成允许我使用属性访问这些字段的代码,并避免繁琐的“Extendible.AppendValue()”和 GetValue() 语法。

我设计的一个解决方案,看起来有点骇人听闻,是在引用 DLL 中定义一个具有扩展方法的类,如下所示:

公共静态类 GraphicsExt { 枚举字段 { 某个值 = 1, 一些其他值 = 2 } 公共静态 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 吗?扩展定义的代码生成是否存在任何已知问题?
  • 是的,我正在使用 protobuf-net。不,不是我知道的,不过我会检查的。这也不是代码生成问题,因为我想不出使用哪种语言机制来“完成”外部程序集中的类。部分类会很好用,但拒绝跨越程序集边界。
  • 对我的编辑有什么想法吗?我仍然不太确定我想如何解决这个问题。

标签: c# protocol-buffers protobuf-net


【解决方案1】:

我是 protobuf-net 的作者。我没有添加任何内容来解决直接呈现的场景(Extensible 代码除外),但我愿意就您认为它应该做什么提出建议。

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

【讨论】:

  • 嗯,我不确定。在我看来,允许在代码生成之后将扩展消息的消息转换为派生类可能会起作用。或者也许采用我的方法并将其自动化,生成“扩展方法”以自动访问 IExtensible 接口。我对您的系统不太熟悉(现在已经使用了大约 3 或 4 天),因此我觉得不足以肯定地建议解决方案。
  • 遗憾的是没有“扩展属性”......继承会很棘手,因为继承的单独含义不能很好地混合(尤其是单继承)。我还需要检查扩展是否可以区分,或者它们是否与protoc“相同”......我会调查。
【解决方案2】:

最终答案:

好的,所以,几天前我确定了一个解决方案,我只是在更新它,以防其他人遇到同样的问题。

整个问题源于我没有意识到 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());
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 2017-02-12
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多