关于 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 #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 #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 #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 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 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 ;
如果有这个成员
在 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 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) { const QuantizedType& Value = *reinterpret_cast <QuantizedType*>(Args.Source); FNetBitStreamWriter* Writer = Context.GetBitStreamWriter (); const uint32 ReplicationFlags = Value.ReplicationFlags; Writer->WriteBits (ReplicationFlags, FHitResultNetSerializer::ReplicatedFlagCount); 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; 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.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; 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; 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 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 ; 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; 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; 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; } TargetValue.Distance = (TargetValue.ImpactPoint - TargetValue.TraceStart).Size (); 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 ; } 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; 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 static const FName PropertyNetSerializerRegistry_NAME_HitResult ("HitResult" ) ;UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO (PropertyNetSerializerRegistry_NAME_HitResult, FHitResultNetSerializer);FHitResultNetSerializer::FNetSerializerRegistryDelegates::~FNetSerializerRegistryDelegates () { UE_NET_UNREGISTER_NETSERIALIZER_INFO (PropertyNetSerializerRegistry_NAME_HitResult); } void FHitResultNetSerializer::FNetSerializerRegistryDelegates::OnPreFreezeNetSerializerRegistry (){ UE_NET_REGISTER_NETSERIALIZER_INFO (PropertyNetSerializerRegistry_NAME_HitResult); } void FHitResultNetSerializer::FNetSerializerRegistryDelegates::OnPostFreezeNetSerializerRegistry (){ { FReplicationStateDescriptorBuilder::FParameters Params; Params.SkipCheckForCustomNetSerializerForStruct = true ; const UStruct* HitResultStruct = FHitResult::StaticStruct (); StructNetSerializerConfigForHitResult.StateDescriptor = FReplicationStateDescriptorBuilder::CreateDescriptorForStruct (HitResultStruct, Params); const FReplicationStateDescriptor* Descriptor = StructNetSerializerConfigForHitResult.StateDescriptor.GetReference (); check (Descriptor != nullptr ); HitResultStateTraits = Descriptor->Traits; if (Descriptor->MemberCount > 32U ) { LowLevelFatalError (TEXT ("%s Has more than 32 replicated members." ), TEXT ("FHitResult has changed. Need to update FHitResultNetSerializer." ), Descriptor->MemberCount); } 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 ; 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." )); } 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)); } } ValidateForwardingNetSerializerTraits (&UE_NET_GET_SERIALIZER (FHitResultNetSerializer), HitResultStateTraits); }
至此,我们的 HitResult 的自定义同步编写和注册完成了
结论 在新的架构下,编写自定义序列化,功能更加齐全,也可以全流程掌控和优化
但是当前版本中
编写自定义序列化的工作,变得异常复杂,成本陡增,甚至需要专人去做维护
有时候我们只是希望省去几个字节的流量,却要编写一大堆公式化的内容
并且由于结构体和 Serializer 内容的逻辑分开了
维护也产生了困难
例如结构体发生改变后,Serializer 并没有更新,有可能在没有任何提示的情况下,出现未知的问题
希望在 Iris 继续迭代后
能在用户侧简化 NetSerializer 的编写或者定制