IT圈老男孩1 发表于 2023-2-14 08:05

Unreal 序列化与google protobuf以及标准序列化的兼容

使用Unreal的开发者想必在协议数据这块有着相当头疼的问题以待解决,当网络数据过来时如何让蓝图或者lua能够使用,可能有各种不同的解决设想,下面我们来聊聊我的一些粗浅的想法。
   假如我们客户端使用的是unreal引擎,服务器是一个Custom Server,或者我们的配置并不是Unreal DataTable方式,而是自定义结构,再或者是一个老项目使用UE来换皮之类的情形。这时候我们会极为迫切的需要一种兼容数据格式来兼容这些结构,以便我们在开发过程中无障碍的特别是减少内存的时候的使用。
   万事第一步,我们先梳理一下数据结构,我们先定义一个proto文件
   TestData.proto文件,结构如下:
   syntax = "proto3";

message FPersonStandard {
        string Name = 1;
        int32Id = 2;
        string Email = 3;

        enum PhoneTypeStandard {
                eMOBILE = 0;
                eHOME = 1;
                eWORK = 2;
        }
        message FPhoneNumberStandard {
                string Number = 1;
                PhoneTypeStandard Type = 2;
        }

        repeated FPhoneNumberStandard Phones = 4;
        repeated int32 IntNumbers = 5;
        repeated string Addrs = 6;
        repeated float FloatNum = 7;
}
用googleprotobuf导出的文件内容我这里就不贴了,读者可以自行导出,我贴一下导出C++简化版的
// Generated by the protocol buffer compiler.DO NOT EDIT!
// source: TestData.proto

#ifndef GOOGLE_PROTOBUF_INCLUDED_TestData_2eproto
#define GOOGLE_PROTOBUF_INCLUDED_TestData_2eproto

#include <limits>
#include <string>

#include <vector>
#include "TypeConvert.h"
// @@protoc_insertion_point(includes)

enum FPersonStandard_PhoneTypeStandard : int {
FPersonStandard_PhoneTypeStandard_eMOBILE = 0,
FPersonStandard_PhoneTypeStandard_eHOME = 1,
FPersonStandard_PhoneTypeStandard_eWORK = 2,
FPersonStandard_PhoneTypeStandard_MAX,
};

struct FPersonStandard_FPhoneNumberStandard {
std::string Number;
FPersonStandard_PhoneTypeStandard Type;
void Serialize(char* buffer,int& offset)
{
    int len = 0;

    len = Number.length();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    TypeConvert::StringToBuffer(Number,buffer,offset);

    TypeConvert::Int32ToBuffer((int32_t&)Type,buffer,offset);

}
void Deserialize(char* buffer,int& offset)
{
    int len = 0;

    len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    TypeConvert::BufferToString(Number,buffer,offset,len);

    TypeConvert::BufferToInt32((int32_t&)Type,buffer,offset);

}
};

struct FPersonStandard {
std::string Name;
int32_t Id;
std::string Email;
std::vector< FPersonStandard_FPhoneNumberStandard > Phones;
std::vector< int32_t > IntNumbers;
std::vector<std::string> Addrs;
std::vector< float > FloatNum;
void Serialize(char* buffer,int& offset)
{
    int len = 0;

    len = Name.length();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    TypeConvert::StringToBuffer(Name,buffer,offset);

    TypeConvert::Int32ToBuffer(Id,buffer,offset);

    len = Email.length();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    TypeConvert::StringToBuffer(Email,buffer,offset);

    len = Phones.size();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    for(int i = 0; i <Phones.size();++i)
    {

    Phones.Serialize(buffer,offset);

    }

    len = IntNumbers.size();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    for(int i = 0; i <IntNumbers.size();++i)
    {

    TypeConvert::Int32ToBuffer(IntNumbers,buffer,offset);

    }

    len = Addrs.size();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    for(int i = 0; i <Addrs.size();++i)
    {

    len = Addrs.length();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    TypeConvert::StringToBuffer(Addrs,buffer,offset);

    }

    len = FloatNum.size();
    TypeConvert::Int32ToBuffer(len,buffer,offset);
    for(int i = 0; i <FloatNum.size();++i)
    {

    TypeConvert::FloatToBuffer(FloatNum,buffer,offset);

    }

}
void Deserialize(char* buffer,int& offset)
{
    int len = 0;

    len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    TypeConvert::BufferToString(Name,buffer,offset,len);

    TypeConvert::BufferToInt32(Id,buffer,offset);

    len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    TypeConvert::BufferToString(Email,buffer,offset,len);

   len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    Phones.resize(len);
    for(int i = 0; i <Phones.size();++i)
    {
    Phones.Deserialize(buffer, offset);

    }

   len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    IntNumbers.resize(len);
    for(int i = 0; i <IntNumbers.size();++i)
    {

    TypeConvert::BufferToInt32(IntNumbers,buffer,offset);

    }

   len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    Addrs.resize(len);
    for(int i = 0; i <Addrs.size();++i)
    {

    len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    TypeConvert::BufferToString(Addrs,buffer,offset,len);

    }

   len = 0;
    TypeConvert::BufferToInt32(len, buffer, offset);
    FloatNum.resize(len);
    for(int i = 0; i <FloatNum.size();++i)
    {

    TypeConvert::BufferToFloat(FloatNum,buffer,offset);

    }

}
};

#endif// GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_TestData_2eproto
这里大家可以看到里面有Serialize和Deserialize两个函数,这两个函数分别进行序列化和反序列化使用。
下面是导出的UE版本的struct内容
#pragma once
#include "CoreMinimal.h"

#include "TypeConvert.h"
#include "TestData.generated.h"
// @@protoc_insertion_point(includes)

UENUM(BlueprintType)
enum class FPerson_PhoneType : uint8 {
    FPerson_PhoneType_MOBILE = 0,
    FPerson_PhoneType_HOME = 1,
    FPerson_PhoneType_WORK = 2,
    FPerson_PhoneType_MAX,
};

USTRUCT()
struct FPerson_FPhoneNumber {
    GENERATED_BODY()
      UPROPERTY()
      FString Number;
    UPROPERTY()
      int32 Type;
    void Serialize(char* buffer, int& offset)
    {
      int len = 0;

      std::string NumberStandard = std::string(TCHAR_TO_UTF8(*Number));
      len = NumberStandard.length();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      TypeConvert::StringToBuffer(NumberStandard, buffer, offset);

      TypeConvert::Int32ToBuffer((int32_t&)Type, buffer, offset);

    }
    void Deserialize(char* buffer, int& offset)
    {
      int len = 0;

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      std::string NumberStandard = "";
      TypeConvert::BufferToString(NumberStandard, buffer, offset, len);
      Number = UTF8_TO_TCHAR(NumberStandard.c_str());

      TypeConvert::BufferToInt32((int32_t&)Type, buffer, offset);

    }
};

USTRUCT()
struct FPerson {
    GENERATED_BODY()
      UPROPERTY()
      FString Name;
    UPROPERTY()
      int32 Id;
    UPROPERTY()
      FString Email;
    UPROPERTY()
      TArray< FPerson_FPhoneNumber > Phones;
    UPROPERTY()
      TArray< int32 > IntNumbers;
    UPROPERTY()
      TArray<FString> Addrs;
    UPROPERTY()
      TArray< float > FloatNum;
    void Serialize(char* buffer, int& offset)
    {
      int len = 0;

      std::string NameStandard = std::string(TCHAR_TO_UTF8(*Name));
      len = NameStandard.length();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      TypeConvert::StringToBuffer(NameStandard, buffer, offset);

      TypeConvert::Int32ToBuffer(Id, buffer, offset);

      std::string EmailStandard = std::string(TCHAR_TO_UTF8(*Email));
      len = EmailStandard.length();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      TypeConvert::StringToBuffer(EmailStandard, buffer, offset);

      len = Phones.Num();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      for (int i = 0; i < Phones.Num(); ++i)
      {

            Phones.Serialize(buffer, offset);

      }

      len = IntNumbers.Num();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      for (int i = 0; i < IntNumbers.Num(); ++i)
      {

            TypeConvert::Int32ToBuffer(IntNumbers, buffer, offset);

      }

      len = Addrs.Num();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      for (int i = 0; i < Addrs.Num(); ++i)
      {

            std::string AddrsStandard = std::string(TCHAR_TO_UTF8(*Addrs));
            len = AddrsStandard.length();
            TypeConvert::Int32ToBuffer(len, buffer, offset);
            TypeConvert::StringToBuffer(AddrsStandard, buffer, offset);

      }

      len = FloatNum.Num();
      TypeConvert::Int32ToBuffer(len, buffer, offset);
      for (int i = 0; i < FloatNum.Num(); ++i)
      {

            TypeConvert::FloatToBuffer(FloatNum, buffer, offset);

      }

    }
    void Deserialize(char* buffer, int& offset)
    {
      int len = 0;

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      std::string NameStandard = "";
      TypeConvert::BufferToString(NameStandard, buffer, offset, len);
      Name = UTF8_TO_TCHAR(NameStandard.c_str());

      TypeConvert::BufferToInt32(Id, buffer, offset);

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      std::string EmailStandard = "";
      TypeConvert::BufferToString(EmailStandard, buffer, offset, len);
      Email = UTF8_TO_TCHAR(EmailStandard.c_str());

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      Phones.SetNum(len);
      for (int i = 0; i < Phones.Num(); ++i)
      {
            Phones.Deserialize(buffer, offset);

      }

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      IntNumbers.SetNum(len);
      for (int i = 0; i < IntNumbers.Num(); ++i)
      {

            TypeConvert::BufferToInt32(IntNumbers, buffer, offset);

      }

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      Addrs.SetNum(len);
      for (int i = 0; i < Addrs.Num(); ++i)
      {

            len = 0;
            TypeConvert::BufferToInt32(len, buffer, offset);
            std::string AddrsStandard = "";
            TypeConvert::BufferToString(AddrsStandard, buffer, offset, len);
            Addrs = UTF8_TO_TCHAR(AddrsStandard.c_str());

      }

      len = 0;
      TypeConvert::BufferToInt32(len, buffer, offset);
      FloatNum.SetNum(len);
      for (int i = 0; i < FloatNum.Num(); ++i)
      {

            TypeConvert::BufferToFloat(FloatNum, buffer, offset);

      }

    }
};
大家可以对比一下内容,其实和C++简化版的区别不大,仅仅是在FString部分有区分。这个原因是FString在unreal引擎使用的是UTF-16来表达的,所以这块需要特别处理一下即可。
上面两个文件是我改了protobuf来导出的,大家有兴趣可以去尝试改改,改成自己需要的结构或者序列化方式。
下面我们谈谈googleprotobuf的反射部分
C++本身没有反射机制,在使用C++版本Protobuf时,protobuf通过proto文件产生响应的message和service,protobuf可以通过proto文件提供反射机制,程序在运行时可以通过proto获取任意message和任意service的属性和方法,也可以在运行时调用message的属性和方法。
google protobuf通过Descriptor获取任意message或service的属性和方法,Descriptor主要包括了一下几种类型:
FileDescriptor获取Proto文件中的Descriptor和ServiceDescriptorDescriptor获取类message属性和方法,包括FieldDescriptor和EnumDescriptorFieldDescriptor获取message中各个字段的类型、标签、名称等EnumDescriptor获取Enum中的各个字段名称、值等ServiceDescriptor获取service中的MethodDescriptorMethodDescriptor获取各个RPC中的request、response、名称等Reflection结构数据首先我们先拿到描述信息
const google::protobuf::Descriptor* pDescriptor = pPb->GetDescriptor();
接着拿反射结构
const google::protobuf::Reflection* pReflection = pPb->GetReflection();
通过描述信息我们可以拿到具体字段



通过描述结构拿字段信息

拿到具体字段后我们就可以去判断是什么类型
const google::protobuf::Reflection* pReflection = pPb->GetReflection();
pFieldDescriptor->cpp_type()



PB类型

拿到后我们就可以做具体的定制序列化数据了,比如double



double自定义序列化

这些就是我们把PB数据对象定制为自定义数据结构的方式,相反方向,把自定义数据转为PB数据对象也是同样方法。
此外,可以把google protobuf导出的文件添加上面C++简化版格式,从而也可以进行序列化(需要注意描述数据长度)。
我们再说说Unreal结构的反射
其实Unreal的反射和googleprotobuf有着类似的设定,由于我近期有一块1T固态损坏,不少资料丢失我拿一下别人的图(侵删)



unreal数据结构图(侵删)

   由于我只关心struct类型,所以使用的是FProperty,如果是UObject去序列化,则用UProperty
   Unreal 的数据类型在UnrealType.h文件
FFloatPropertyFBoolPropertyFStructPropertyFStrPropertyFArrayPropertyFMapPropertyFIntPropertyFInt8PropertyFInt16PropertyFInt64PropertyFUInt32PropertyFUInt16PropertyFUInt64PropertyFNumericProperty

通过UScriptStruct我们可以拿到Field字段内容,然后就可以遍历字段拿到FProperty
然后就可以得到值


反之从自定义结构数据到UE结构序列化可以反方向来进行反序列化。这个可以对比文章开头时添加序列化函数方案。
好了,以上便是自定义结构在unreal结构,google protobuf上进行互通兼容的方案。如果有其他更好的方式,可以在评论处发一下,学习一下。
页: [1]
查看完整版本: Unreal 序列化与google protobuf以及标准序列化的兼容