生成 lua版本的proto 与使用

[前言]

关于如何使用生成proto的exe工具, 参考我的另一篇文章:

https://blog.csdn.net/liuyongjie1992/article/details/115185967

我发现生成lua版本的proto和生成C#版的调用方式不太一样,因此开一片文章记录一下

新建一个bat文件执行这一句话即可调用lua的生成工具

.\protoclient.exe --proto_path=./proto --lua_out=./genpath ./proto/TestProto.proto 

.\protoclient.exe是调用当前目录的protoclient.exe文件

--proto_path=./proto是proto的文件夹,请注意是文件夹路径

--lua_out=./genpath 输出后的proto路径

./proto/TestProto.proto找到我们要输入的proto

上述操作不出意外可以顺利生成TestProto_pb.lua文件

很显然lua和C#的exe输入的语句是不一样的

C#版本的执行语句为

 .\protogen.exe -i:xxx.proto -o:.\genpath\xxx.cs

[Lua版本的pb使用方法]

以上文的TestMessage.proto为例

生成的lua版pb长这样子

下面这句话是业务代码如何使用Message,项目里各种UI需要向服务器请求数据展示自己的内容,TestProto_pb.TestMessage()就是消息体,表示你想请求哪个内容。

--这是一个lua函数
function HowUseProtoMessage()
    --提示:在使用TestProto_pb之前别忘了require,下文会介绍如何在项目中批处理多个require
	local msg = TestProto_pb.TestMessage()
	msg.id = 1;
	msg.name = "测试message"
	--!!!这句话很关键,把一个message序列化成string
	local NetString = msg:SerializeToString();
	--如果你想把这个msg发给服务器,那么就把这个string发过去
	NetMgr.SendMsg(NetString)
end

下面开始进阶版 lua pb的使用

项目中我们通常会给Proto里面的mssage加上id,这样前后端通信的时候就知道你传的到底是个毛线,从而方便分发,添加id的方式有两种。提示!!!:无论protoid是3个数的组合还是1个数,这个protoid都是批处理工具按照一定规则生成出来的枚举,不要手动去添加这个id。

我们给proto加上一个枚举用来做标识,第一种方式是mrid,groupid,unitid三个数的组合,第二种是protoid一个数的。

前后端在通信的时候可以从包头中提取出 第一种或者第二种 id从而得出该数据包要用哪个message解析。

【项目启动时注册消息回调】

下面的Reg函数是在项目启动时调用,所有业务都要根据protoid记录消息体、回调函数、错误处理函数。

--[[注册消息处理函数
proto			消息类,示例Module_XXX_pb.XX
handler			处理函数
errorHandler	错误处理函数(错误码不为0时会调用)
--]]
function Reg(proto, handler, errorHandler)
    local mid = proto.protoid  -- proto.MRID; 
    if mMsgCallBacks[mid] then
        local callBack = mMsgCallBacks[mid];
        local callBackMeta = getmetatable(callBack.proto());
        local newCallBackMeta = getmetatable(proto());
        GameLog.LogModuleError("msg handler repeat %s %s %d", callBackMeta._descriptor.name, newCallBackMeta._descriptor.name, mid);
    else
        local callBack = {};
        callBack.proto = proto;
        callBack.handler = handler;
        callBack.errorHandler = errorHandler;
        mMsgCallBacks[mid] = callBack;
    end
end

可以在项目启动时,调用一个统一的lua文件把所有业务的回调全部注册。

--所有需要接受服务器通讯的协议都要注册哦,下面这个注册只是举个例子

GameNet.Reg(Module_Item_pb.SCItemGetDataRe, ItemMgr.OnGetItemData)

上面注册完了协议回调,那么客户端就等待服务器数据啦。

下面的代码就是收到服务器数据后,C#层把数据传递给lua,请注意,lua不支持byte[]数组,因此数据流会被转化成LuaByte,最终就是个string。

--参数解释:
    --参数1:protoid是我们自定义的id
    --参数2:data是C#层接收到的byte[]数组转换成的string
function OnRecvMsg(protoid, data)
    if mMsgCallBacks[protoid] then
        local callBack = mMsgCallBacks[protoid]; -- mMsgCallBacks是什么在上文注册中已讲明
        local msg = callBack.proto();--proto()拿到消息体,此时msg是个空消息体
        if msg == nil then
            GameLog.LogModuleError(NetModule.LOG_TAG,"Failed to OnRecvMsg, cannot find proto for protoid= %d",protoid);
        end
        local flag, errorMsg = xpcall(msg.ParseFromString, traceback, msg, data);
        --调用了ParseFromString后,上文的msg不再是空消息体啦,服务器数据已经序列化进去了
        if flag then--没有错误
            if callBack.handler then
                --xpcall调用回调函数,并把消息体当做参数传入该函数,该msg就是服务发过来的message
                local flag, errorMsg = xpcall(callBack.handler, traceback, msg);
                if not flag then
                    OnRecvFail(tid, errorMsg, msg);
                end
            else
                OnRecvFail(tid, "handler is nil", msg);
            end
        end
        
    end
end

下面是UI业务层随便一个小例子

-- 下面是UI层业务接受proto消息体的回调函数,msg就是proto里的message
function OnGetItemData(msg)
    local id = msg.id
    local name = msg.name
    --数据拿到,下面开始刷UI啦
end

本篇文章到此,lua版pb的使用教程已经完成啦。但是!!!还没有说明protoid的生成规则以及pb文件的批处理require

[protoid]批处理生成

protoid的枚举值不要自己生成,这个前文有提到过

 先给大家展示一下我们项目的proto,这么多proto如果要手动添加一个唯一的id显然是不可能的。

[生成protoid的本质]

朋友们!生成protoid的本质是什么,其实就是在message里加一个枚举,这样的好处是,当我们拿到这个消息体时,直接通过 TestMessage.protoid就可以拿到这个枚举值。建议大家再回到上面的文章看一下消息回调注册的地方,加深理解。

如何在原有proto的基础上,加上一个枚举?这就很简单啦,直接用批处理工具写入这句话就好


 

下面就开始介绍如何批处理写入枚举值,我个人首选python写批处理,真的好用,python工程怎么创建以及环境部署看我的另一篇文章:

https://blog.csdn.net/liuyongjie1992/article/details/112378661

顺便安利一下这个系列的文章,Python版本的打表工具

下面开始讲生成protoid的核心思路:

1:哪些message不需要生成protoid?经常用proto的人都应该知道,很多被引用的message是用来做数据包体,而不是消息包体,我们批处理工具里自己定义了一些头标识,当这个message名前两个字母带着两个大写字母且匹配,那么就给改message生成protoid

msgtype = {
  'CS':1,
  'SC':1,
  'SS':1,
  'SD':1,
  'DS':1,
  'CF':1,
  'FC':1,
  'SF':1,
  'FF':1,
  'RR':1,
  'RC':1,
  'CO':1,
  'SO':1,
  'FO':1,
  'OO':1,
  'WS':1,
  'TS':1,
  'TT':1,
  'ST':1,
  'SW':1,
  'CT':1,
  'WC':1,
  'OC':1,
  'OS':1,
  'FS':1,
  'TF':1,
  'WF':1,
  'FT':1,
  'CW':1,
  'TC':1,
}

这是我们项目中的战斗proto为例,可以看到该message是以SC开头的,那么该message就要生成protoid,而该message内的CampData是一个引用数据体,CampData没有以我们定义的message头开始,那么该message就不会生成protoid

//[返回][游戏服]进入战斗结果
message SCEnterBattle
{
    optional int64 battleId = 1;
    repeated CampData camps = 2;                // 战斗阵营数据 (仅站位和card_sid)
    optional int32 totalChapterNum = 3;         // 回目数量
    optional int32 battleSid = 4;          		// 战役配置id
    repeated FightPlayerInfo playerInfo = 5; 
    optional int32 result = 6;                  // 结果 0:成功 其他:返回对应错误码
    optional int32 isReady = 7;                 // 0没准备 1已准备 (用于战前断线重连)
    optional int32 isFixedArray = 8;			// 是否固定阵容0不是 1是
    optional int64 bettleBeginTime = 9;			// 0 不限制, > 0 战斗开始时间
}


//阵营数据
message CampData
{ 
    optional int32 id = 1;                     //阵营id
    optional int32 campType = 2;               //阵营类型   0:玩家  1:怪物  2:裁判
    optional int32 energyType = 3;             //鬼火类型    0:阵营   1:单体
    optional int32 energyValue = 4;            //鬼火当前值(阵营鬼火)
    optional int32 energyProgressValue = 5;    //鬼火进度当前值
    optional int32 energyRound = 6;    		   //第几次恢复鬼火
    repeated UnitData units = 7;         	   //战斗站位数据
    repeated UnitData cardpool = 8;        	   //卡池中数据
    optional int32 speed  = 9; 				   //阵营速度
}

2:在python工程中遍历所有proto(1步骤被引用的除外),遍历每一个proto的每一行,每检测到一个message消息体,就给消息体添加一个枚举,并且protoid+1向上累积。举个例子,假设这个proto里有10个message,那么该协议内的protoid就是1-10,当遍历到下一个proto时,该proto的message内生成的枚举值就要从11开始啦。这样想是不是非常的简单。遍历到最后,每一个message都有一个唯一的protoid啦

3:步骤2的实现方法可以,但非常愚蠢,因为开发者不希望每一次都把所有proto都生成一遍,因为我正在开发一个UI功能,我只修改这一个proto,却导致我需要把所有proto的protoid都生成一遍,因此有一个简单的解决办法,就是给每个proto定义一个段位

def gengid(modulename):
    gids = {
        "Module_AiPet.proto" : 1,
        "Module_Bag.proto" : 2,
        "Module_Buff.proto" : 3,
        "Module_Chat.proto" : 4,
        "Module_Coin.proto" : 5,
        "Module_DaySign.proto" : 6,
        "Module_Faction.proto" : 7,
        "Module_Friend.proto" : 8,
        "Module_Gem.proto" : 9,
   
    }

注意:每创建一个proto的时候就要手动添加一个段,这样我们生成protoid的时候就不会扰动其他的proto里的protoid,python文件定义的段作为千位,如果是第100个proto,那么它的段位就是100000+ ***后面的个十百位可以让你的proto内定义999个消息体,很显然已经够用了。举个例子:假设是第100个proto里的第65个消息体,那么该message的protoid就是100065。到此为止,protoid的生成教程就完成啦。

[protoid]lua批处理require

批量处理require和上述批量处理protoid可以在一个python工程中连续完成,也可以分步完成, 因为他们之间没有必然联系。

#这是一个python函数,该函数会生成一个AllPB.lua文件,当客户端require该lua文件后,会require所有我们批处理生成的XXXX_pb.lua文件

#这是一个python函数,该函数会生成一个AllPB.lua文件,当客户端require该lua文件后,会require所有我们批处理生成的XXXX_pb.lua文件
def export_require(lua_path):
	outPath = os.path.abspath(lua_path + "/AllPB.lua")
	fileObj = open(outPath,"wb")
	fileObj.write("--this file is generated by tools, do not edit\n\n")
	fileObj.write("module(...,package.seeall)\n")
	fileObj.write("function InitModule()\n")
	fileObj.write("    ------------register NetMsg pb------------\n")
	for file in os.listdir(lua_path + "\\NetMsg"):
		if file[-3:] == "lua":
			fileObj.write("\trequire \"Logic/Proto/NetMsg/"+ file[:-4] +"\"\n")
	fileObj.write("end\n")
	fileObj.close()

分析上述代码,也不难看出该函数干了啥

--this file is generated by tools, do not edit

module(...,package.seeall)
function InitModule()
    ------------register NetEnum pb------------
    ------------register NetStruct pb------------
    ------------register NetMsg pb------------
	require "Logic/Proto/NetMsg/Module_Achievement_pb"
	require "Logic/Proto/NetMsg/Module_AsyncArena_pb"
	require "Logic/Proto/NetMsg/Module_Award_pb"
	require "Logic/Proto/NetMsg/Module_Buildings_pb"
	require "Logic/Proto/NetMsg/Module_CenturyAdventure_pb"
	require "Logic/Proto/NetMsg/Module_Chat_pb"
	require "Logic/Proto/NetMsg/Module_ClientConfig_pb"
	require "Logic/Proto/NetMsg/Module_CommonInfo_pb"
	require "Logic/Proto/NetMsg/Module_Condition_pb"
	require "Logic/Proto/NetMsg/Module_Cross_pb"
	require "Logic/Proto/NetMsg/Module_Currencys_pb"
	require "Logic/Proto/NetMsg/NetStruct_Item_pb"
	require "Logic/Proto/NetMsg/NetStruct_Math_pb"
	require "Logic/Proto/NetMsg/NetStruct_Thing_pb"
end

最后展示一下AllPB.lua长这样,在项目启动的时候require该文件,则自动require所有生成的pb,是不是很nice!!!!

[完结语]

到此为止,lua版Proto生成与使用教程全部结束,并且还补充了protoid的生成方式。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Little丶Seven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值