在上一篇文章中,分析了夜莺的protobuff的使用方案
复习下结论
1.C#类本身,如例子中的ExampleMessage,成员名字与.proto文件中定义的要一致
2.协议字符串,即protobuff中的通用的.proto中的内容,需要注意,不支持import,所以需要把依赖的内容写到一起
3.协议的唯一标识,函数GetMessageName中返回的字符串
我们知道,protobuff-net已经生成了用于序列化的C#类了,我尝试过把生成的类直接转为JS,出现若干问题,但是通过把相关的类导出到JS的方式能够解决转换失败的问题,但是还有运行时的问题,最要命的几点是
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"msgVector3")]
public partial class msgVector3 : global::ProtoBuf.IExtensible
{
public msgVector3() {}
private long _x = default(long);
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"x", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(long))]
public long x
{
get { return _x; }
set { _x = value; }
}
private long _y = default(long);
[global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"y", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(long))]
public long y
{
get { return _y; }
set { _y = value; }
}
private long _z = default(long);
[global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"z", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(long))]
public long z
{
get { return _z; }
set { _z = value; }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}
这是用protobuff-net生成的一个最基础的向量类,有三个分量
//基础3维向量
message msgVector3
{
//x
optional int64 x = 1;
//y
optional int64 y = 2;
//z
optional int64 z = 3;
}
我们看到,.proto文件中定义的成员为xyz,但是在生成的类中,变成了_x _y _z,而在JS运行中,序列化使用的是field功能,名字认的就是成员名字
而不是C#中的property的名字,直接导致序列化失败,需要写个后处理工具,把对应名字改下,生成.cs后再进行处理
还一个问题是
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
这里有个成员
extensionObject,是用来实现protobuff的继承功能,这么强大(鸡肋)的功能我还真没有用过,但是由于 extensionObject也是一个
成员,而且在没有父类的情况下,为null,直接导致序列化失败,悲剧,不过也是有解决办法的,经过分析,假设我们不使用继承功能的前提下,
extensionObject一定是为null的,而且C#运行情况下,需要的是GetExtensionObject这个函数,而不是extensionObject这个变量,所以我们尝试
把extensionObject这个变量删除,然后把函数GetExtensionObject的实现直接改为 return null,OK,妥了,可以序列化了
幸运的是,我做了实验,如果把整个.proto文件作为protoString传入给序列化函数,是可以正确的序列化的
现在,C#类和protoString都已经有了,就差一个protoName了,做法就是,在生成的C#类里加个函数,直接返回protoName,所以改变后的文件长
这样
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Vector3Msg")]
public partial class Vector3Msg : global::ProtoBuf.IExtensible
{
public Vector3Msg() {}
public int x = default(int);
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"_x", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(int))]
private int _x
{
get { return x; }
set { x = value; }
}
public int y = default(int);
[global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"_y", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(int))]
private int _y
{
get { return y; }
set { y = value; }
}
public int z = default(int);
[global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"_z", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(int))]
private int _z
{
get { return z; }
set { z = value; }
}
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return null; }
public static string GetProtoName()
{
return "Vector3Msg";
}
}
仔细看发现msgVector3变成了Vector3msg,不要在意这些细节,因为我是从不同的工程中获得的资料,然后封装一个类,可以这么写
public class GameSocketInterface
{
public static void sendMessage<T>(short cmd, T param, string fileName = null, string protuBufname = null)
{
if (VersionControl.useJS)
{
Network.MessageWrap msgWrap = new Network.MessageWrap();
msgWrap.protuBufname = "protocol.msg." + protuBufname;
msgWrap.fileName = fileName;
GameSocket.Instance.sendStringMessage(cmd, msgWrap.Encode(param));
}
else
{
GameSocket.Instance.sendMessage(cmd, param);
}
}
public static T deserialize<T>(NetMsgParam data, string fileName = null, string protuBufname = null)
{
if (VersionControl.useJS)
{
Network.MessageWrap msgWrap = new Network.MessageWrap();
msgWrap.protuBufname = "protocol.msg." + protuBufname;
msgWrap.fileName = fileName;
return (T) msgWrap.Decode(data.Get64String());
}
else
{
return GameSocket.Instance.deserialize<T>(data.GetBytes());
}
}
}
关于 MessageWrap代码
public class MessageWrap : MessageParent
{
public string fileName;
public string protuBufname;
public override string GetProToString()
{
TextAsset proto = (TextAsset)Resources.Load("@Protos/" + fileName);
return proto.ToString();
}
public override string GetMessageName()
{
return protuBufname;
}
}
关于 NetMsgParam的代码
public struct NetMsgParam
{
private byte[] m_data;
public void SetData(byte[] data)
{
m_data = data;
}
public string Get64String()
{
return Convert.ToBase64String(m_data);
}
public byte[] GetBytes()
{
return m_data;
}
}
为什么要封装下呢,因为byte[]是从C#传入,然后转到JS的,但是sharpkit不支持直接传入byte[]的转换
序列化使用示例
TeleporterMsg msg = new TeleporterMsg();
msg.teleporterTid = teleportId;
GameSocketInterface.sendMessage((short)PacketProtocolType.CG_SCENE_SWITCH, msg, "scene", TeleporterMsg.GetProtoName());
反序列化示例
private void OnGetAOIInfoMsg(NetMsgParam param)
{
//根据信息生成角色
SceneAOIInfoMsg msg = GameSocketInterface.deserialize<SceneAOIInfoMsg>(param, "scene", SceneAOIInfoMsg.GetProtoName());
List<SceneObjInfoMsg> objList = msg.aoiObjects;
for (int i = 0; i < objList.Count; i++)
{
SceneObjInfoMsg info = objList[i];
if (info.facadeType == AOIObjFacadeType.PLAYER)
{
CreateOtherHero(info);
}
else if (info.facadeType == AOIObjFacadeType.MONSTER)
{
CreateMoster(info);
}
}
}
我们可以高兴的看到,在使用者层面,已经跟直接用protobuff-net版本写C#代码十分接近了,只是目前还需要传入proto文件的名字,从而获得
proto文件内的字符串获得protoString,而通过设置开关,就可以实现传说级的终极目标:C#写代码,调试,JS发布,热更妥妥的
已经出现太长不看的风险了,再啰嗦几句,其实如果你够勤奋并且有耐心,到这里已经可以实现在JSB使用protobuff了,实际还不够,因为改协议生
成的C#文件太痛苦了,好在我们已经实现了终极解决方案,直接改了protobuff-net的代码生成规则,直接生成符合JS使用的代码,而且兼容C#模式
运行,后续会给大家分享出源代码和原理分析,今天就到这里了~