Mapz's Blog

可以递归的函数指针

UE5-2-IrisReplicationSystem初探(二):关于Iris的自定义序列化

关于 Iris 自定义序列化

由于 Iris 的同步逻辑发生了变更,所以从前的使用 TStructOpsTypeTraits WithNetSerializer Trait

并定义

1
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);

的方式也被废弃

我们需要使用新的方式来实现对象的自定义序列化

Iris 使用船新的 FNetSerializer 来进行自定义序列化

如果我们声明了 WithNetSerializer 的 Trait

但是又没有注册 Iris 的 FNetSerializer

会在启动后 Log Warning

1
Generating descriptor for struct XXX that has custom serialization.

提示你要重新搞个 Serializer,如果没有搞的话

会使用默认的 StructSerializer 来进行处理

默认的 StructSerializer 会遍历所有的 Property 来使用其 Member Serializer 来进行同步

但是由于我们在使用旧版的自定义序列化的时候,通常会有其他的逻辑,不光是同步,所以使用默认的 Iris 同步一般会出现逻辑问题

所以大概率仍需要重新编写 NetSerializer

NetSerializer 主要做的事情大致可以看做下面除去传输过程外的内容

游戏数据->压缩装箱->序列化->[传输过程]->反序列化->开箱解压缩->游戏数据

下面我们以 FHitResultNetSerializer 为例,继续从自定义同步的实现来入手分析 Iris 这部分的工作原理

由于系统中使用了很多编译期逻辑

所以阅读代码需要掌握一些编译期模版元编程的知识

FHitResultNetSerializer 的实现

HitResultNetSerializer 用于实现 HitResult 的同步

NetSerializer 的定义

先查看头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Iris/Serialization/NetSerializer.h"
#include "HitResultNetSerializer.generated.h"

USTRUCT()
struct FHitResultNetSerializerConfig : public FNetSerializerConfig
{
GENERATED_BODY()
};

namespace UE::Net
{

UE_NET_DECLARE_SERIALIZER(FHitResultNetSerializer, ENGINE_API);

}

先是继承 FNetSerializerConfig 实现一个 Config

而后使用宏 UE_NET_DECLARE_SERIALIZER 定义一个 NetSerializer 以及导出

其中宏定义

1
2
3
4
5
6
7
8
/** Declare a serializer. */
#define UE_NET_DECLARE_SERIALIZER(SerializerName, Api) struct Api SerializerName ## NetSerializerInfo \
{ \
static const UE::Net::FNetSerializer Serializer; \
static uint32 GetQuantizedTypeSize(); \
static uint32 GetQuantizedTypeAlignment(); \
static const FNetSerializerConfig* GetDefaultConfig(); \
};

其中声明了一个 FHitResultNetSerializerNetSerializerInfo 的 struct,其成员为

  • static 的 FNetSerializer,这是通用的 Serializer 的内部数据结构,会根据实现类的信息在编译期设置内容
  • 获取压缩装箱后的类型内存大小的函数
  • 获取压缩装箱后的类型内存对齐的函数
  • 获取同步 Config 的函数

NetSerializer 的静态初始化

那么这些内容是如何初始化的呢?

我们接着查看源文件

1
2
3
4
5
6
7
8
9
namespace UE::Net
{

struct FHitResultNetSerializer
{
...
}
}
UE_NET_IMPLEMENT_SERIALIZER(FHitResultNetSerializer);

声明了我们在头文件中使用的那个 FHitResultNetSerializer 实现类

然后使用宏 UE_NET_IMPLEMENT_SERIALIZER 实现其初始化

1
2
3
4
5
6
/** Implement a serializer using the struct named SerializerName. */
#define UE_NET_IMPLEMENT_SERIALIZER(SerializerName) const UE::Net::FNetSerializer SerializerName ## NetSerializerInfo::Serializer = UE::Net::TNetSerializer<SerializerName>::ConstructNetSerializer(TEXT(#SerializerName)); \
uint32 SerializerName ## NetSerializerInfo::GetQuantizedTypeSize() { return UE::Net::TNetSerializerBuilder<SerializerName>::GetQuantizedTypeSize(); }; \
uint32 SerializerName ## NetSerializerInfo::GetQuantizedTypeAlignment() { return UE::Net::TNetSerializerBuilder<SerializerName>::GetQuantizedTypeAlignment(); }; \
const FNetSerializerConfig* SerializerName ## NetSerializerInfo::GetDefaultConfig() { return UE::Net::TNetSerializerBuilder<SerializerName>::GetDefaultConfig(); };

首先是内部 Serializer 的初始化

使用了模版

1
UE::Net::TNetSerializer<FHitResultNetSerializer>::ConstructNetSerializer("FHitResultNetSerializer"); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
template<typename NetSerializerImpl>
class TNetSerializer
{
public:
static constexpr FNetSerializer ConstructNetSerializer(const TCHAR* Name)
{
TNetSerializerBuilder<NetSerializerImpl> Builder;
Builder.Validate();

FNetSerializer Serializer = {};
Serializer.Version = Builder.GetVersion();
Serializer.Traits = Builder.GetTraits();

Serializer.Serialize = Builder.GetSerializeFunction();
Serializer.Deserialize = Builder.GetDeserializeFunction();
Serializer.SerializeDelta = Builder.GetSerializeDeltaFunction();
Serializer.DeserializeDelta = Builder.GetDeserializeDeltaFunction();
Serializer.Quantize = Builder.GetQuantizeFunction();
Serializer.Dequantize = Builder.GetDequantizeFunction();
Serializer.IsEqual = Builder.GetIsEqualFunction();
Serializer.Validate = Builder.GetValidateFunction();
Serializer.CloneDynamicState = Builder.GetCloneDynamicStateFunction();
Serializer.FreeDynamicState = Builder.GetFreeDynamicStateFunction();
Serializer.CollectNetReferences = Builder.GetCollectNetReferencesFunction();

Serializer.DefaultConfig = Builder.GetDefaultConfig();

Serializer.QuantizedTypeSize = Builder.GetQuantizedTypeSize();
Serializer.QuantizedTypeAlignment = Builder.GetQuantizedTypeAlignment();

Serializer.ConfigTypeSize = Builder.GetConfigTypeSize();
Serializer.ConfigTypeAlignment = Builder.GetConfigTypeAlignment();

Serializer.Name = Name;
return Serializer;
}
};

此模版类的用处是使用 NetSerializer 的实现类,来静态初始化 NetSerizlierInfo 中的 Serializer 内容

初始化过程都是在编译期执行的

查看 Builder 的模版类,这是静态初始化的具体逻辑内容

1
2
3
4
5
template<typename NetSerializerImpl>
class TNetSerializerBuilder
{
...
}

其中使用了大量的编译期逻辑来给不同类型的 Serializer 赋值不同的参数

例如 Version ,各种 Traits 等

1
2
3
// Version check
template<typename U> static ETrueType TestHasVersion(typename TEnableIf<std::is_same_v<decltype(&FVersion::Version), decltype(&U::Version)>>::Type*);
template<typename> static EFalseType TestHasVersion(...);

NetSerializer 的内容简析

我们来看看 FHitResultNetSerializer 的内容

Version

自定义序列化的版本号

1
2
// Version
static const uint32 Version = 0;

编译期如果有这个成员,则 Builder 中 TestHasVersion 为 True

则 Builder 的 Etraits 中的 HasVersion 会设置为 True

1
2
3
4
5
enum ETraits : unsigned
{
HasVersion = unsigned(decltype(TestHasVersion<NetSerializerImpl>(nullptr))::Value),
...
}

从而 Builder 的 GetVersion 函数可得其值

1
2
3
4
5
template<typename T = void, typename U = typename TEnableIf<HasVersion, T>::Type, bool V = true>
static constexpr uint32 GetVersion() { return NetSerializerImpl::Version; }

template<typename T = void, typename U = typename TEnableIf<!HasVersion, T>::Type, char V = 0>
static constexpr uint32 GetVersion() { return ~0U; }

然后在静态初始化的时候,设置到类型的 NetSerializerInfo 的 NetSerialzier 中

当前 Version 参数在同步中我没有找到使用的位置

预计是用于同步数据的版本管理

bIsForwardingSerializer

是否是转发序列化器

1
static constexpr bool bIsForwardingSerializer = true; // Triggers asserts if a function is missing

如果有这个成员

在 Builder 中设置后,获取函数为

1
2
3
4
5
template<typename T = void, typename U = typename TEnableIf<IsForwardingSerializerIsBool, T>::Type, bool V = true>
static constexpr bool IsForwardingSerializer() { return NetSerializerImpl::bIsForwardingSerializer; }

template<typename T = void, typename U = typename TEnableIf<!IsForwardingSerializerIsBool, T>::Type, char V = 0>
static constexpr bool IsForwardingSerializer() { return false; }

如果设置为 true,则需要在 NetSerialzer 实现中实现下面所有的检查所检查的成员,否则编译会失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename T = void, typename U = typename TEnableIf<IsForwardingSerializer(), T>::Type, bool V = true>
static void ValidateForwardingSerializer()
{
static_assert(HasSerialize, "Forwarding FNetSerializer must implement Serialize.");
static_assert(HasDeserialize, "Forwarding FNetSerializer must implement Deserialize.");
static_assert(HasSerializeDelta, "Forwarding FNetSerializer must implement SerializeDelta.");
static_assert(HasDeserializeDelta, "Forwarding FNetSerializer must implement DeserializeDelta.");
static_assert(HasQuantize, "Forwarding FNetSerializer must implement Quantize.");
static_assert(HasDequantize, "Forwarding FNetSerializer must implement Dequantize.");
static_assert(HasIsEqual, "Forwarding FNetSerializer must implement IsEqual.");
static_assert(HasValidate, "Forwarding FNetSerializer must implement Validate.");
static_assert(HasCloneDynamicState, "Forwarding FNetSerializer must implement CloneDynamicState.");
static_assert(HasFreeDynamicState, "Forwarding FNetSerializer must implement FreeDynamicState.");
static_assert(HasCollectNetReferences, "Forwarding FNetSerializer must implement CollectNetReferences.");
}

template<typename T = void, typename U = typename TEnableIf<!IsForwardingSerializer(), T>::Type, char V = 0>
static void ValidateForwardingSerializer()
{
}

bHasCustomNetReference

是否有自定义的 NetReference,如果有的话,可以自己收集需要同步的 NetReference

1
static constexpr bool bHasCustomNetReference = true;

如果设置了此参数,会在 Validate 静态检查

1
static_assert(!HasCustomNetReference() || (HasCustomNetReference() && HasCollectNetReferences), "FNetSerializer with bHasCustomNetReference = true must implement CollectNetReferences method.");

如果序列化器没有实现 CollectNetReferences 函数,则会编译失败

bHasDynamicState

是否有动态 State
State 是序列化器用到的数据 Buffer
如果有动态 State 意味着需要手动处理 State 的拷贝和释放

1
static constexpr bool bHasDynamicState = true;

如果设置了此参数,会在 NetSerializer 中增加 Trait

1
Traits |= (HasDynamicState() ? ENetSerializerTraits::HasDynamicState : ENetSerializerTraits::None);

并且必须添加 CloneDynamicState 和 FreeDynamicState 成员函数

1
2
static_assert(!HasDynamicStateIsBool || (HasFreeDynamicState && HasCloneDynamicState), "FNetSerializer must implement CloneDynamicState and FreeDynamicState when it has dynamic state.");

定义需要同步的内容数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Types
enum EReplicationFlags : uint32
{
BlockingHit = 1U,
StartPenetrating = BlockingHit << 1U,
ImpactPointEqualsLocation = StartPenetrating << 1U,
ImpactNormalEqualsNormal = ImpactPointEqualsLocation << 1U,
InvalidItem = ImpactNormalEqualsNormal << 1U,
InvalidFaceIndex = InvalidItem << 1U,
NoPenetrationDepth = InvalidFaceIndex << 1U,
InvalidElementIndex = NoPenetrationDepth << 1U,
InvalidMyItem = InvalidElementIndex << 1U,
};

static constexpr uint32 ReplicatedFlagCount = 9U;

struct FQuantizedType
{
alignas(16) uint8 HitResult[316];
uint32 ReplicationFlags;
};

typedef FHitResult SourceType;
typedef FQuantizedType QuantizedType;
typedef FHitResultNetSerializerConfig ConfigType;

static const ConfigType DefaultConfig;

Flags 用于标记需要同步的内容
而后面的 FQuantizedType 用于存储压缩后的数据
其中使用了一个 uint8 数组来存储压缩后的数据,其大小是通过计算原 Struct 所需的最大内存来计算的
而后面的 ReplicationFlags 是用于存储标记位的
SourceType 是原始数据类型
QuantizedType 是装箱后的数据类型
ConfigType 是同步配置类型

实现的成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Serialize(FNetSerializationContext&, const FNetSerializeArgs& Args);
static void Deserialize(FNetSerializationContext&, const FNetDeserializeArgs& Args);

static void SerializeDelta(FNetSerializationContext&, const FNetSerializeDeltaArgs& Args);
static void DeserializeDelta(FNetSerializationContext&, const FNetDeserializeDeltaArgs& Args);

static void Quantize(FNetSerializationContext&, const FNetQuantizeArgs& Args);
static void Dequantize(FNetSerializationContext&, const FNetDequantizeArgs& Args);

static bool IsEqual(FNetSerializationContext&, const FNetIsEqualArgs& Args);
static bool Validate(FNetSerializationContext&, const FNetValidateArgs& Args);

static void CloneDynamicState(FNetSerializationContext&, const FNetCloneDynamicStateArgs&);
static void FreeDynamicState(FNetSerializationContext&, const FNetFreeDynamicStateArgs&);

static void CollectNetReferences(FNetSerializationContext&, const FNetCollectReferencesArgs&);

Serialize

用于从 Internal Buffer 到 比特流的 序列化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void FHitResultNetSerializer::Serialize(FNetSerializationContext& Context, const FNetSerializeArgs& Args)
{
// 注 :Args.Source 是指向序列化数据的指针
const QuantizedType& Value = *reinterpret_cast<QuantizedType*>(Args.Source);

FNetBitStreamWriter* Writer = Context.GetBitStreamWriter();

// 注 :直接写入标记位
const uint32 ReplicationFlags = Value.ReplicationFlags;
Writer->WriteBits(ReplicationFlags, FHitResultNetSerializer::ReplicatedFlagCount);

// We need to manually serialize the properties as there are some replicated properties that don't need to be replicated depending on the data
// as is the case with some of the bools that are covered by the ReplicationFlags.

const FReplicationStateDescriptor* Descriptor = StructNetSerializerConfigForHitResult.StateDescriptor.GetReference();
const FReplicationStateMemberDescriptor* MemberDescriptors = Descriptor->MemberDescriptors;
const FReplicationStateMemberSerializerDescriptor* MemberSerializerDescriptors = Descriptor->MemberSerializerDescriptors;
const FReplicationStateMemberDebugDescriptor* MemberDebugDescriptors = Descriptor->MemberDebugDescriptors;

const uint32 MemberCount = Descriptor->MemberCount;

// Initalize mask as all dirty
uint32 MemberMaskStorage = ~0U;
// 注 :获得需要同步的可选 Struct 成员
FNetBitArrayView MemberMask = GetMemberChangeMask(&MemberMaskStorage, MemberCount, ReplicationFlags);

for (uint32 MemberIndex = 0; MemberIndex < MemberCount; ++MemberIndex)
{
if (!MemberMask.GetBit(MemberIndex))
{
continue;
}

// 注 : 序列化需要同步的成员
const FReplicationStateMemberDescriptor& MemberDescriptor = MemberDescriptors[MemberIndex];
const FReplicationStateMemberSerializerDescriptor& MemberSerializerDescriptor = MemberSerializerDescriptors[MemberIndex];

UE_NET_TRACE_DYNAMIC_NAME_SCOPE(MemberDebugDescriptors[MemberIndex].DebugName, *Context.GetBitStreamWriter(), Context.GetTraceCollector(), ENetTraceVerbosity::Verbose);
UE_NET_TRACE_DYNAMIC_NAME_SCOPE(MemberSerializerDescriptor.Serializer->Name, *Context.GetBitStreamWriter(), Context.GetTraceCollector(), ENetTraceVerbosity::VeryVerbose);

FNetSerializeArgs MemberSerializeArgs;
MemberSerializeArgs.NetSerializerConfig = MemberSerializerDescriptor.SerializerConfig;
MemberSerializeArgs.Source = reinterpret_cast<NetSerializerValuePointer>(&Value.HitResult) + MemberDescriptor.InternalMemberOffset;
// 注 :序列化成员
MemberSerializerDescriptor.Serializer->Serialize(Context, MemberSerializeArgs);
}
}

Deserialize

用于从头 比特流 反序列化数据 到 Internal Buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void FHitResultNetSerializer::Deserialize(FNetSerializationContext& Context, const FNetDeserializeArgs& Args)
{
QuantizedType& Target = *reinterpret_cast<QuantizedType*>(Args.Target);

FNetBitStreamReader* Reader = Context.GetBitStreamReader();

// 注:先读取标记位
const uint32 ReplicationFlags = Reader->ReadBits(FHitResultNetSerializer::ReplicatedFlagCount);
Target.ReplicationFlags = ReplicationFlags;

// We need to manually serialize the properties as there are some replicated properties that don't need to be replicated depending on the data
// as is the case with some of the bools that are covered by the ReplicationFlags.
const FReplicationStateDescriptor* Descriptor = StructNetSerializerConfigForHitResult.StateDescriptor.GetReference();
const FReplicationStateMemberDescriptor* MemberDescriptors = Descriptor->MemberDescriptors;
const FReplicationStateMemberSerializerDescriptor* MemberSerializerDescriptors = Descriptor->MemberSerializerDescriptors;
const FReplicationStateMemberDebugDescriptor* MemberDebugDescriptors = Descriptor->MemberDebugDescriptors;

const uint32 MemberCount = Descriptor->MemberCount;

// Initalize mask as all dirty
uint32 MemberMaskStorage = ~0U;
// 获取要反序列化的内容
FNetBitArrayView MemberMask = GetMemberChangeMask(&MemberMaskStorage, MemberCount, ReplicationFlags);

for (uint32 MemberIndex = 0; MemberIndex < MemberCount; ++MemberIndex)
{
if (!MemberMask.GetBit(MemberIndex))
{
continue;
}

const FReplicationStateMemberDescriptor& MemberDescriptor = MemberDescriptors[MemberIndex];
const FReplicationStateMemberSerializerDescriptor& MemberSerializerDescriptor = MemberSerializerDescriptors[MemberIndex];

UE_NET_TRACE_DYNAMIC_NAME_SCOPE(MemberDebugDescriptors[MemberIndex].DebugName, *Context.GetBitStreamReader(), Context.GetTraceCollector(), ENetTraceVerbosity::Verbose);
UE_NET_TRACE_DYNAMIC_NAME_SCOPE(MemberSerializerDescriptor.Serializer->Name, *Context.GetBitStreamReader(), Context.GetTraceCollector(), ENetTraceVerbosity::VeryVerbose);

FNetDeserializeArgs MemberDeserializeArgs;
MemberDeserializeArgs.NetSerializerConfig = MemberSerializerDescriptor.SerializerConfig;
MemberDeserializeArgs.Target = reinterpret_cast<NetSerializerValuePointer>(&Target.HitResult) + MemberDescriptor.InternalMemberOffset;
MemberSerializerDescriptor.Serializer->Deserialize(Context, MemberDeserializeArgs);
}
}

SerializeDelta 和 DeserializeDelta

用于序列化和反序列化 Delta 数据

可以在前面的 State 的基础上,做增量序列化

这样可以只同步变化内容,进一步减少同步所需数据量

1
2
3
4
5
6
7
8
9
10
// For now just use normal serialization for delta as this struct typically is a one off.
void FHitResultNetSerializer::SerializeDelta(FNetSerializationContext& Context, const FNetSerializeDeltaArgs& Args)
{
NetSerializeDeltaDefault<Serialize>(Context, Args);
}

void FHitResultNetSerializer::DeserializeDelta(FNetSerializationContext& Context, const FNetDeserializeDeltaArgs& Args)
{
NetDeserializeDeltaDefault<Deserialize>(Context, Args);
}

阅读模版函数 NetSerializeDeltaDefault 和 NetDeserializeDeltaDefault

1
2
3
4
5
6
7
8
9
10
11
12
13
template<NetSerializeFunction Serialize>
void
NetSerializeDeltaDefault(FNetSerializationContext& Context, const FNetSerializeDeltaArgs& Args)
{
Serialize(Context, Args);
};

template<NetDeserializeFunction Deserialize>
void
NetDeserializeDeltaDefault(FNetSerializationContext& Context, const FNetDeserializeDeltaArgs& Args)
{
Deserialize(Context, Args);
}

可知本 Serializer 中直接使用了 Serialize 和 Deserialize 来处理 Delta Serialize 的内容,并没有做增量优化

Quantize 和 Dequantize

用于从 External Buffer 到 Internal Buffer 的 “量子化” 和 “逆量子化”
可以理解为装箱和开箱,装箱指把原始数据处理成可用来同步的数据,开箱指把同步数据处理成原始数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
在 HitResult 中,做了一次全量的量子化和逆量子化,并处理了 Flag 的更新
由于 Quantize 是在 Serialize 之前调用的,所以在 Serialize 中可以直接使用量子化后的数据

```cpp
void FHitResultNetSerializer::Quantize(FNetSerializationContext& Context, const FNetQuantizeArgs& Args)
{
const SourceType& SourceValue = *reinterpret_cast<const SourceType*>(Args.Source);
QuantizedType& TargetValue = *reinterpret_cast<QuantizedType*>(Args.Target);

uint32 ReplicationFlags = 0;

// Update flags based on SourceValue
// 注:根据 SourceValue 的内容更新标记位
ReplicationFlags |= SourceValue.ImpactPoint == SourceValue.Location ? EReplicationFlags::ImpactPointEqualsLocation : 0U;
ReplicationFlags |= SourceValue.ImpactNormal == SourceValue.Normal ? EReplicationFlags::ImpactNormalEqualsNormal : 0U;
ReplicationFlags |= SourceValue.MyItem == INDEX_NONE ? EReplicationFlags::InvalidMyItem : 0U;
ReplicationFlags |= SourceValue.Item == INDEX_NONE ? EReplicationFlags::InvalidItem : 0U;
ReplicationFlags |= SourceValue.FaceIndex == INDEX_NONE ? EReplicationFlags::InvalidFaceIndex : 0U;
ReplicationFlags |= (SourceValue.PenetrationDepth == 0.0f) ? EReplicationFlags::NoPenetrationDepth : 0U;
ReplicationFlags |= SourceValue.ElementIndex == INDEX_NONE ? EReplicationFlags::InvalidElementIndex : 0U;
ReplicationFlags |= SourceValue.bBlockingHit ? EReplicationFlags::BlockingHit : 0U;
ReplicationFlags |= SourceValue.bStartPenetrating ? EReplicationFlags::StartPenetrating : 0U;

TargetValue.ReplicationFlags = ReplicationFlags;

// We do a full quantize even though we wont necessarily serialize them.
// 注:做了一次 Full Quantize
FNetQuantizeArgs HitResultQuantizeArgs = {};
HitResultQuantizeArgs.NetSerializerConfig = &StructNetSerializerConfigForHitResult;
HitResultQuantizeArgs.Source = Args.Source;
HitResultQuantizeArgs.Target = NetSerializerValuePointer(&TargetValue.HitResult);
StructNetSerializer->Quantize(Context, HitResultQuantizeArgs);
}

void FHitResultNetSerializer::Dequantize(FNetSerializationContext& Context, const FNetDequantizeArgs& Args)
{
const QuantizedType& SourceValue = *reinterpret_cast<const QuantizedType*>(Args.Source);
SourceType& TargetValue = *reinterpret_cast<SourceType*>(Args.Target);

const uint32 ReplicatonFlags = SourceValue.ReplicationFlags;

// Dequantize all and fixup afterwards
FNetDequantizeArgs HitResultQuantizeArgs = {};
HitResultQuantizeArgs.NetSerializerConfig = &StructNetSerializerConfigForHitResult;
HitResultQuantizeArgs.Source = NetSerializerValuePointer(&SourceValue.HitResult);
HitResultQuantizeArgs.Target = Args.Target;
StructNetSerializer->Dequantize(Context, HitResultQuantizeArgs);

if (ReplicatonFlags & EReplicationFlags::ImpactPointEqualsLocation)
{
TargetValue.ImpactPoint = TargetValue.Location;
}

if (ReplicatonFlags & EReplicationFlags::ImpactNormalEqualsNormal)
{
TargetValue.ImpactNormal = TargetValue.Normal;
}

if (ReplicatonFlags & EReplicationFlags::InvalidMyItem)
{
TargetValue.MyItem = INDEX_NONE;
}

if (ReplicatonFlags & EReplicationFlags::InvalidItem)
{
TargetValue.Item = INDEX_NONE;
}

if (ReplicatonFlags & EReplicationFlags::InvalidFaceIndex)
{
TargetValue.FaceIndex = INDEX_NONE;
}

if (ReplicatonFlags & EReplicationFlags::NoPenetrationDepth)
{
TargetValue.PenetrationDepth = 0.f;
}

if (ReplicatonFlags & EReplicationFlags::InvalidElementIndex)
{
TargetValue.ElementIndex = INDEX_NONE;
}

// Calculate distance
TargetValue.Distance = (TargetValue.ImpactPoint - TargetValue.TraceStart).Size();

// Set the bBlockingHit and bStartPenetrating from the flags
TargetValue.bBlockingHit = (ReplicatonFlags & EReplicationFlags::BlockingHit) != 0U ? 1 : 0;
TargetValue.bStartPenetrating = (ReplicatonFlags & EReplicationFlags::StartPenetrating) != 0U ? 1 : 0;
}

IsEqual

用于对比两个 Internal Buffer 的内容是否相同,用于确认对象是否 Dirty 从而确认是否需要同步
这里分别对比了 State 是 装箱 后的数据的情况和 State 是原始数据的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
bool FHitResultNetSerializer::IsEqual(FNetSerializationContext& Context, const FNetIsEqualArgs& Args)
{
if (Args.bStateIsQuantized)
{
const QuantizedType& Value0 = *reinterpret_cast<const QuantizedType*>(Args.Source0);
const QuantizedType& Value1 = *reinterpret_cast<const QuantizedType*>(Args.Source1);

if (Value0.ReplicationFlags != Value1.ReplicationFlags)
{
return false;
}

// Do a per member compare of relevant members
const FReplicationStateDescriptor* Descriptor = StructNetSerializerConfigForHitResult.StateDescriptor.GetReference();
const FReplicationStateMemberDescriptor* MemberDescriptors = Descriptor->MemberDescriptors;
const FReplicationStateMemberSerializerDescriptor* MemberSerializerDescriptors = Descriptor->MemberSerializerDescriptors;
const FReplicationStateMemberDebugDescriptor* MemberDebugDescriptors = Descriptor->MemberDebugDescriptors;

const uint32 MemberCount = Descriptor->MemberCount;

// Initalize mask as all dirty
uint32 MemberMaskStorage = ~0U;
FNetBitArrayView MemberMask = GetMemberChangeMask(&MemberMaskStorage, MemberCount, Value0.ReplicationFlags);

FNetIsEqualArgs MemberArgs;
MemberArgs.Version = 0;
MemberArgs.bStateIsQuantized = Args.bStateIsQuantized;

for (uint32 MemberIt = 0; MemberIt < MemberCount; ++MemberIt)
{
if (!MemberMask.GetBit(MemberIt))
{
continue;
}

const FReplicationStateMemberDescriptor& MemberDescriptor = MemberDescriptors[MemberIt];
const FReplicationStateMemberSerializerDescriptor& MemberSerializerDescriptor = MemberSerializerDescriptors[MemberIt];
const FNetSerializer* Serializer = MemberSerializerDescriptor.Serializer;

MemberArgs.NetSerializerConfig = MemberSerializerDescriptor.SerializerConfig;
const uint32 MemberOffset = MemberDescriptor.InternalMemberOffset;
MemberArgs.Source0 = reinterpret_cast<NetSerializerValuePointer>(&Value0.HitResult) + MemberDescriptor.InternalMemberOffset;
MemberArgs.Source1 = reinterpret_cast<NetSerializerValuePointer>(&Value1.HitResult) + MemberDescriptor.InternalMemberOffset;

if (!Serializer->IsEqual(Context, MemberArgs))
{
return false;
}
}
}
else
{
FNetIsEqualArgs HitResultEqualArgs = Args;
HitResultEqualArgs.NetSerializerConfig = &StructNetSerializerConfigForHitResult;
HitResultEqualArgs.Source0 = NetSerializerValuePointer(Args.Source0);
HitResultEqualArgs.Source1 = NetSerializerValuePointer(Args.Source1);

if (!StructNetSerializer->IsEqual(Context, HitResultEqualArgs))
{
return false;
}
}

return true;
}

Validate

校验正确性,我暂时没有找到使用的地方

FreeDynamicState 和 CloneDynamicState

用于自定义动态的 State 数据的拷贝和释放

释放一般调用于 NetBlob 的释放的时候

例如发送收到临时的传输,比如 Unreliable 信息的传输,就会在发送或者收到后立即释放其 State

又例如在 Array 中,如果有元素移除,也会释放其 State

而克隆会在需要拷贝 State 的时候调用

例如发送信息后,会把当前的 State 拷贝一份作为基准,用于后续的 Delta 比较

具体实现内容略

CollectNetReferences

用于收集需要同步的内容

例如发送和接收 RPC 的参数

发送数据的时候

HitResult 内此函数只做了转发,所以内容略去

注册同步类型到自定义 Serializer

我们可以看到在 Serializer 的实现中,声明了一个内部类

并指定了一个静态成员用于注册类型

这个内部类用于处理注册 Serializer 到系统中,以及初始化相关 Serializer 的相关参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct FHitResultNetSerializer
{
...
private:
// 用于注册的内部类
class FNetSerializerRegistryDelegates final : private UE::Net::FNetSerializerRegistryDelegates
{
public:
virtual ~FNetSerializerRegistryDelegates();

private:
// 委托的挂载会在和卸载会在父类的构造函数和析构函数中自动执行,这里只需要重写两个虚函数即可
virtual void OnPreFreezeNetSerializerRegistry() override;
virtual void OnPostFreezeNetSerializerRegistry() override;
};
...
// 静态成员
static FHitResultNetSerializer::FNetSerializerRegistryDelegates NetSerializerRegistryDelegates;
...
};
...
// 创建内部类的静态成员实例
FHitResultNetSerializer::FNetSerializerRegistryDelegates FHitResultNetSerializer::NetSerializerRegistryDelegates;


具体的注册过程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 注册一个名称,这个名称和 Struct 的名称一致
static const FName PropertyNetSerializerRegistry_NAME_HitResult("HitResult");

// 使用这个宏,会实现一个 FPropertyNetSerializerInfo ,其中包含其类型的序列化名称和其 Serializer
UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_HitResult, FHitResultNetSerializer);

// 在 FNetSerializerRegistryDelegates 的析构函数中,我们注销 NetSerializer Info
FHitResultNetSerializer::FNetSerializerRegistryDelegates::~FNetSerializerRegistryDelegates()
{
// 这个宏用于注销类型的序列化器,必须依托 UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO
UE_NET_UNREGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_HitResult);
}

// 在 OnPreFreezeNetSerializerRegistry 中,我们注册 NetSerializer Info
void FHitResultNetSerializer::FNetSerializerRegistryDelegates::OnPreFreezeNetSerializerRegistry()
{
// 这个宏用于注册类型的序列化器,必须依托 UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_HitResult);
}

// 在 OnPostFreezeNetSerializerRegistry 中,我们可以配置序列化器
// 并初始化一些需要使用的内容
// 通常是写自己的 StateDescriptor
// Desriptor 属性包括
// 是否包括 Super Class 的 Descriptor
// GetLifeTimeProperties 的属性 (class 适用)
// 是否启用 FastArraySerializer
// 等
void FHitResultNetSerializer::FNetSerializerRegistryDelegates::OnPostFreezeNetSerializerRegistry()
{
// Setup serializer
{
// In this case we want to build a descriptor based on the struct members rather than the serializer we try to register
FReplicationStateDescriptorBuilder::FParameters Params;
Params.SkipCheckForCustomNetSerializerForStruct = true;

const UStruct* HitResultStruct = FHitResult::StaticStruct();

// Had do comment this out as the size differs between editor and non-editor builds.
//if (HitResultStruct->GetStructureSize() != 240 || HitResultStruct->GetMinAlignment() != 8)
//{
// LowLevelFatalError(TEXT("%s Size: %d Alignment: %d"), TEXT("FHitResult layout has changed. Need to update FHitResultNetSerializer."), HitResultStruct->GetStructureSize(), HitResultStruct->GetMinAlignment());
//}

// 设置 StateDescriptor
StructNetSerializerConfigForHitResult.StateDescriptor = FReplicationStateDescriptorBuilder::CreateDescriptorForStruct(HitResultStruct, Params);
const FReplicationStateDescriptor* Descriptor = StructNetSerializerConfigForHitResult.StateDescriptor.GetReference();
check(Descriptor != nullptr);
HitResultStateTraits = Descriptor->Traits;

// 用于检查 Serializer 是否适配当前版本的 Struct,这里粗暴的使用了 MemberCount
if (Descriptor->MemberCount > 32U)
{
LowLevelFatalError(TEXT("%s Has more than 32 replicated members."), TEXT("FHitResult has changed. Need to update FHitResultNetSerializer."), Descriptor->MemberCount);
}

// Build property -> Member index lookup table
FName PropertyNames[EPropertyName::PropertyName_ConditionallyReplicatedPropertyCount];

PropertyNames[PropertyName_FaceIndex] = FName("FaceIndex");
PropertyNames[PropertyName_Distance] = FName("Distance");
PropertyNames[PropertyName_ImpactPoint] = FName("ImpactPoint");
PropertyNames[PropertyName_ImpactNormal] = FName("ImpactNormal");
PropertyNames[PropertyName_PenetrationDepth] = FName("PenetrationDepth");
PropertyNames[PropertyName_MyItem] = FName("MyItem");
PropertyNames[PropertyName_Item] = FName("Item");
PropertyNames[PropertyName_ElementIndex] = FName("ElementIndex");
PropertyNames[PropertyName_bBlockingHit] = FName("bBlockingHit");
PropertyNames[PropertyName_bStartPenetrating] = FName("bStartPenetrating");

uint32 FoundPropertyMask = 0;

// Find all replicated properties of interest.
// 这里遍历所有同步成员,制作了一个 PropertyToMemberIndex,可用于标记 MemberMark ,标识哪一个成员发生了变化,并只同步变化的内容
// 使用见 GetMemberChangeMask 和 Serializer 函数
const FProperty*const*MemberProperties = Descriptor->MemberProperties;
for (uint32 PropertyIndex = 0; PropertyIndex != EPropertyName::PropertyName_ConditionallyReplicatedPropertyCount; ++PropertyIndex)
{
for (const FProperty*const& MemberProperty : MakeArrayView(MemberProperties, Descriptor->MemberCount))
{
const SIZE_T MemberIndex = &MemberProperty - MemberProperties;
if (MemberProperty->GetFName() == PropertyNames[PropertyIndex])
{
FoundPropertyMask |= 1U << PropertyIndex;

FHitResultNetSerializer::PropertyToMemberIndex[PropertyIndex] = MemberIndex;
break;
}
}
}

if (FoundPropertyMask != (1U << EPropertyName::PropertyName_ConditionallyReplicatedPropertyCount) - 1U)
{
LowLevelFatalError(TEXT("%s"), TEXT("Couldn't find expected replicated members in FHitResult."));
}

// 校验空间大小是否正确
// Validate our assumptions regarding quantized state size and alignment.
constexpr SIZE_T OffsetOfHitResult = offsetof(FQuantizedType, HitResult);
if ((sizeof(FQuantizedType::HitResult) < Descriptor->InternalSize) || (((OffsetOfHitResult/Descriptor->InternalAlignment)*Descriptor->InternalAlignment) != OffsetOfHitResult))
{
LowLevelFatalError(TEXT("FQuantizedType::HitResult has size %u but requires size %u and alignment %u."), uint32(sizeof(FQuantizedType::HitResult)), uint32(Descriptor->InternalSize), uint32(Descriptor->InternalAlignment));
}
}

// Verify traits
// 校验生成的 Traits 是否正确
ValidateForwardingNetSerializerTraits(&UE_NET_GET_SERIALIZER(FHitResultNetSerializer), HitResultStateTraits);
}

至此,我们的 HitResult 的自定义同步编写和注册完成了

结论

在新的架构下,编写自定义序列化,功能更加齐全,也可以全流程掌控和优化

但是当前版本中

编写自定义序列化的工作,变得异常复杂,成本陡增,甚至需要专人去做维护

有时候我们只是希望省去几个字节的流量,却要编写一大堆公式化的内容

并且由于结构体和 Serializer 内容的逻辑分开了

维护也产生了困难

例如结构体发生改变后,Serializer 并没有更新,有可能在没有任何提示的情况下,出现未知的问题

希望在 Iris 继续迭代后

能在用户侧简化 NetSerializer 的编写或者定制