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 &#34;TypeConvert.h&#34;
// @@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 &#34;CoreMinimal.h&#34;
#include &#34;TypeConvert.h&#34;
#include &#34;TestData.generated.h&#34;
// @@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 = &#34;&#34;;
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 = &#34;&#34;;
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 = &#34;&#34;;
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 = &#34;&#34;;
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]