找回密码
 立即注册
查看: 238|回复: 0

Google.Protobuf与Unity优化问题完美解决

[复制链接]
发表于 2022-12-3 10:51 | 显示全部楼层 |阅读模式
1. 生成protoc.exe文件,该文件用于编译proto生成对应的代码脚本
先拉取项目:https://github.com/protocolbuffers/protobuf.git
找到根目录下的:CMakeLists.txt文件,该文件用于编译项目
cmd命令行:cmake CMakeLists.txt, 执行完成cmd相对路径会生成项目文件:protobuf.sln
打开项目后编译之后生成对应的protoc.exe文件

2. cmake安装:https://cmake.org/download/  安装里面最好直接设置一下 System Path
我是Windows平台使用PowerShell编译会报编译错误(改用cmd就行了),报错如下:
CMake Error at CMakeLists.txt:3 (project):
  The CMAKE_C_COMPILER:

    cl

  is not a full path and was not found in the PATH.

  To use the NMake generator with Visual C++, cmake must be run from a shell
  that can use the compiler cl from the command line.  This environment is
  unable to invoke the cl compiler.  To fix this problem, run cmake from the
  Visual Studio Command Prompt (vcvarsall.bat).

  Tell CMake where to find the compiler by setting either the environment
  variable "CC" or the CMake cache entry CMAKE_C_COMPILER to the full path to
  the compiler, or to the compiler name if it is in the PATH.3. 生成Google.Protobuf.dll文件,该文件放入实际项目中使用
打开项目csharp\src\Google.Protobuf.sln直接编译生成,vs版本不新会报:无法解析global.json中制定的.NET SDK 版本。
修改global.json为自己的.NET版本即可:cmd运行 dotnet --info 查看.NET版本

4. 写一个批处理文件:Gen.bat ,自己修改生成路径把
@echo off

set toolPath=%cd%\proto2cs\protoc.exe
set protoPath=%cd%\proto\
set outputPath=%cd%\output\

for /f "delims=" %%i in ('dir /b "%protoPath%\*.proto"') do (
        echo %toolPath% --proto_path=%protoPath% %%i --outputPath=%outputPath%
        %toolPath% --proto_path=%protoPath% %%i --csharp_out=%outputPath%
)

echo %copyToolPath% --proto_path=%protoPath% --outputPath=%outputPath%
start %copyToolPath% %protoPath% %outputPath%

@echo Export Completed!
pause5. 解决UNITY反序列化GC问题:
修改:CodedInputStream.cs,新增函数Reset,把其中用到的readonly都去掉
public void Reset(byte[] buffer, int bufferPos, int bufferSize)
{
        this.input = null;
        this.buffer = buffer;
        this.state.bufferPos = bufferPos;
        this.state.bufferSize = bufferSize;
        this.state.sizeLimit = DefaultSizeLimit;
        this.state.recursionLimit = DefaultRecursionLimit;
        this.state.bufferSizeAfterLimit = 0;
        this.state.lastTag = 0;
        this.state.nextTag = 0;
        this.state.hasNextTag = false;
        this.state.totalBytesRetired = 0;

        SegmentedBufferHelper.Initialize(this, out this.state.segmentedBufferHelper);
        this.leaveOpen = true;
        this.state.currentLimit = int.MaxValue;
}再修改:MessageExtensions.cs,把其中用到new CodedInputStream()的全替换成使用一个全局的CodedInputStream重复使用
6. 解决Proto生成文件无法直接重复使用的问题:直接生成的文件没有Reset重置函数,导致重复使用的消息值无法直接清空可参考路径:UnityGC优化 - Protobuf 3的优化 ,还可以做其他扩展找到:csharp_message.cc文件,修改MessageGenerator::Generate函数,其中添加以下代码即可
printer->Print("public void Clear()\n{\n");
for (int i = 0; i < descriptor_->field_count(); i++)
{
        const FieldDescriptor* fieldDescriptor = descriptor_->field(i);
    std::string fieldName = UnderscoresToCamelCase(fieldDescriptor->name(), false);
        if (fieldDescriptor->is_repeated() || fieldDescriptor->type() == FieldDescriptor::Type::TYPE_MESSAGE || fieldDescriptor->type() == FieldDescriptor::Type::TYPE_BYTES)
        {
                printer->Print(" if($field_name$_ != null)$field_name$_.Clear();\n", "field_name", fieldName);
        }
        else if (fieldDescriptor->type() == FieldDescriptor::Type::TYPE_ENUM)
        {
                printer->Print(
                        " $field_name$_ = $field_type$.$default_value$;\n", "field_type", GetClassName(fieldDescriptor->enum_type()), "field_name", fieldName, "default_value", fieldDescriptor->default_value_enum()->name());
        }
        else
        {
                printer->Print(
                        " $field_name$_ = $default_value$;\n", "field_name", fieldName, "default_value", "default");
        }
}
printer->Print("}\n");7. 根据项目需要,再写一份自动生成绑定文件的功能:通常网络通信中每个消息都需要标识Id,而这个标识Id通常也是用自动化工具生成的,我目前的项目是定义:msg_id.proto,然后枚举名与消息名保持一致,以下是生成代码,可自己根据项目做生成(PS:针对字符串规则做的生成)
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace ProtobufBindGenerate
{
    class Program
    {
        public static string ProtoPath = "../../Proto/";
        public static string GeneratePath = "../../output_cs/";
        public struct Line
        {
            public string zhushi;
            public string className;
        }

        public struct OneProto
        {
            public List<Line> reader;
            public string protoName;
            public string nameSpace;
        }


        static void Main(string[] args)
        {
            if (args != null && args.Length == 2)
            {
                ProtoPath = args[0];
                GeneratePath = args[1];
            }

            if (!Directory.Exists(ProtoPath) || !Directory.Exists(GeneratePath))
            {
                return;
            }
            DirectoryInfo info = Directory.CreateDirectory(ProtoPath);
            FileInfo[] fileInfos = info.GetFiles("*", SearchOption.AllDirectories);

            List<string> msgIds = new List<string>();
            foreach (var fileInfo in fileInfos)
            {
                if (fileInfo.FullName.Contains("msg_id"))
                {
                    // 解析一下msg_id
                    using (FileStream fs = File.OpenRead(fileInfo.FullName))
                    {
                        using (StreamReader sr = new StreamReader(fs))
                        {
                            bool bEnter = false;
                            while (!sr.EndOfStream)
                            {
                                string line = sr.ReadLine();

                                if (!bEnter)
                                {
                                    if (line.Contains("enum"))
                                    {
                                        bEnter = true;
                                    }
                                }
                                else
                                {
                                    line = line.Trim().Replace(" ", "");
                                    if (!line.StartsWith("//"))
                                    {
                                        int index = line.IndexOf("=");
                                        if (index >= 0)
                                        {
                                            msgIds.Add(line.Substring(0, index));
                                        }
                                    }
                                }
                            }
                        }
                    }
                    break;
                }
            }

            List<OneProto> list = new List<OneProto>();
            foreach (var fileInfo in fileInfos)
            {
                List<Line> reader = new List<Line>();
                string protoName = Path.GetFileName(fileInfo.FullName);
                string nameSpace = ReadProto(reader, fileInfo);

                list.Add(new OneProto { reader = reader, protoName = protoName, nameSpace = nameSpace });
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("// 工具自动生成:ProtobufBindGenerate");
            sb.AppendLine("using System;");
            sb.AppendLine("using System.Collections.Generic;");
            sb.AppendLine("using Google.Protobuf;");
            sb.AppendLine("");

            sb.AppendLine("public static class ProtobufBindGenerate");
            sb.AppendLine("{");
            sb.AppendLine("\tpublic static void InitBinding(Action<Type, ulong> Bind)");
            sb.AppendLine("\t{");
            foreach (var proto in list)
            {
                sb.AppendLine("\t\t// " + proto.protoName);
                for (int i = 0; i < proto.reader.Count; i++)
                {
                    if (!msgIds.Contains(proto.reader.className)) continue;
                    sb.AppendLine(string.Format("\t\tBind(typeof({0}.{1}), (ulong)NetMsg.msg_id.{2});", proto.nameSpace, proto.reader.className, LineToHump(proto.reader.className)));
                }
                sb.AppendLine("\t\t");
            }
            sb.AppendLine("\t}");
            sb.AppendLine("}");

            using FileStream txt = new FileStream(GeneratePath + "ProtoBufBindGenerate.cs", FileMode.Create, FileAccess.ReadWrite);
            using StreamWriter sw = new StreamWriter(txt);
            sw.Write(sb.ToString());
        }

        /// <summary>
        /// 下划线转驼峰
        /// </summary>
        static string LineToHump(string str)
        {
            StringBuilder stringBuilder = new StringBuilder();
            string[] strs = str.Split('_');
            for (int i = 0; i < strs.Length; i++)
            {
                string tempStr = strs;
                if (tempStr[0] <= 'z' && tempStr[0] >= 'a')
                {
                    stringBuilder.Append((char)(tempStr[0] - 32));
                    stringBuilder.Append(tempStr.Substring(1, tempStr.Length - 1));
                }
                else
                {
                    stringBuilder.Append(tempStr);
                }
            }
            return stringBuilder.ToString();
        }

        static string ReadProto(List<Line> reader, FileInfo fileInfo)
        {
            string zhushi = null;
            string nameSpace = "";
            using (FileStream fs = File.OpenRead(fileInfo.FullName))
            {
                using (StreamReader sr = new StreamReader(fs))
                {
                    while (!sr.EndOfStream)
                    {
                        string line = sr.ReadLine();

                        if (line.Contains("message"))
                        {
                            int index = line.IndexOf("message") + 8;
                            int zhushiIndex = line.LastIndexOf("//");
                            if (zhushiIndex > 0)
                                line = line.Substring(index, zhushiIndex - index).Trim();
                            else
                                line = line.Substring(index, line.Length - index).Trim();

                            reader.Add(new Line { zhushi = zhushi, className = line });
                        }
                        else if (line.Contains("//"))
                        {
                            zhushi = line;
                        }
                        else if (line.Contains("package"))
                        {
                            int index = line.IndexOf("package") + 8;
                            int lastIndex = line.IndexOf(';');
                            nameSpace = line.Substring(index, lastIndex - index).Trim();
                        }
                    }
                }
            }
            return LineToHump(nameSpace);
        }
    }
}
8. 结合以上生成文件我们可以把它放入编译proto的批处理流程里
@echo off

set toolPath=%cd%\proto2cs\protoc.exe
set protoPath=%cd%\proto\
set outputPath=%cd%\output\
set bindToolPath=%cd%\proto2cs\netcoreapp3.1\ProtobufBindGenerate.exe

for /f "delims=" %%i in ('dir /b "%protoPath%\*.proto"') do (
        echo %toolPath% --proto_path=%protoPath% %%i --outputPath=%outputPath%
        %toolPath% --proto_path=%protoPath% %%i --csharp_out=%outputPath%
)

echo %bindToolPath% --proto_path=%protoPath% --outputPath=%outputPath%
start %bindToolPath% %protoPath% %outputPath%

@echo Export Completed!
pause
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-21 18:44 , Processed in 0.177029 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表