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

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

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

message FPersonStandard {
        string Name = 1;
        int32  Id = 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和ServiceDescriptor
Descriptor获取类message属性和方法,包括FieldDescriptor和EnumDescriptor
FieldDescriptor获取message中各个字段的类型、标签、名称等
EnumDescriptor获取Enum中的各个字段名称、值等
ServiceDescriptor获取service中的MethodDescriptor
MethodDescriptor获取各个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文件
FFloatProperty
FBoolProperty
FStructProperty
FStrProperty
FArrayProperty
FMapProperty
FIntProperty
FInt8Property
FInt16Property
FInt64Property
FUInt32Property
FUInt16Property
FUInt64Property
FNumericProperty


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


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

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

本版积分规则

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

GMT+8, 2024-11-24 07:30 , Processed in 0.090379 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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