# Unreal Iris(二)ReplicationState
ReplicationState 是 Unreal Iris Replication System 中用于描述对象同步状态。要完成对于状态数据的同步需要多个模块的分工合作:
- NetSerializer:针对网络同步对象的序列化和反序列化规则。
- ReplicationStateDescriptor:针对 UObject 中各个成员,在 Replication System 中的如何说明以及应用何种规则的描述。
- Protocol:上述两者结合,对 UObject 的成员进行描述组织,使其成为可以进行网络同步的数据载体,再通过特有的序列化规则将数据发送出去的协议。
# FPropertyNetSerializerInfo && FNetSerializer
FNetSerializer
是整个同步流程中数据处理的核心,负责数据的序列化、反序列化、增量序列化、增量反序列化等操作,里面的函数会在打包解包等过程中使用:
struct FNetSerializer | |
{ | |
uint32 Version; | |
ENetSerializerTraits Traits; | |
NetSerializeFunction Serialize; // 序列化 | |
NetDeserializeFunction Deserialize; // 反序列化 | |
NetSerializeDeltaFunction SerializeDelta; // 增量序列化 | |
NetDeserializeDeltaFunction DeserializeDelta; // 增量反序列化 | |
NetQuantizeFunction Quantize; // 对象转 pod | |
NetDequantizeFunction Dequantize; //pod 转对象 | |
NetIsEqualFunction IsEqual; | |
NetValidateFunction Validate; | |
NetCloneDynamicStateFunction CloneDynamicState; | |
NetFreeDynamicStateFunction FreeDynamicState; | |
NetCollectNetReferencesFunction CollectNetReferences; // 引用收集 | |
const FNetSerializerConfig* DefaultConfig; | |
uint16 QuantizedTypeSize; | |
uint16 QuantizedTypeAlignment; | |
uint16 ConfigTypeSize; | |
uint16 ConfigTypeAlignment; | |
const TCHAR* Name; | |
}; |
FPropertyNetSerializerInfo
则用于描述单个 UClass 的数据加工规则,一个 UClass 可以支持多个 FPropertyNetSerializerInfo
,通过 IsSupported 接口对具体的 UClass 实例采用不同的加工方案。
序列化器的声明:
UE_NET_DECLARE_SERIALIZER(FInt8NetSerializer, IRISCORE_API); | |
UE_NET_IMPLEMENT_SERIALIZER(FInt8NetSerializer); | |
#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(); \ | |
}; | |
/** 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(); }; |
RegisterDefaultPropertyNetSerializerInfos
注册时机在 IrisCoreModule 加载时触发:
void RegisterPropertyNetSerializerSelectorTypes() | |
{ | |
using namespace UE::Net; | |
using namespace UE::Net::Private; | |
FPropertyNetSerializerInfoRegistry::Reset(); | |
FInternalNetSerializerDelegates::BroadcastPreFreezeNetSerializerRegistry(); | |
RegisterDefaultPropertyNetSerializerInfos(); | |
FPropertyNetSerializerInfoRegistry::Freeze(); | |
FInternalNetSerializerDelegates::BroadcastPostFreezeNetSerializerRegistry(); | |
} | |
void RegisterDefaultPropertyNetSerializerInfos() | |
{ | |
// Register supported types | |
// Integer types | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FInt8Property); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FInt16Property); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FIntProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FInt64Property); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FUint8PropertyNetSerializerInfo); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FUInt16Property); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FUInt32Property); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FUInt64Property); | |
// Enum types | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FEnumAsBytePropertyNetSerializerInfo); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FEnumPropertyNetSerializerInfo); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FNetRoleNetSerializerInfo); | |
// Float types | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FFloatProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FDoubleProperty); | |
// Object and field types | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FObjectProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FWeakObjectProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FScriptInterfacePropertyNetSerializerInfo); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FSoftObjectProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FFieldPathProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_SoftObjectPath); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_SoftClassPath); | |
// String types | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FNameProperty); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FStrProperty); | |
// Special types | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FNativeBoolPropertyNetSerializerInfo); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(FBitFieldPropertyNetSerializerInfo); | |
// Named structs that we support | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_Guid); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Vector); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Vector3f); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Vector3d); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Rotator); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Quat); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Quat4f); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(NAME_Quat4d); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_Vector_NetQuantize100); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_Vector_NetQuantize10); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_Vector_NetQuantize); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_Vector_NetQuantizeNormal); | |
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_DateTime); | |
} |
如果定义了复杂的结构体,但是其中各个成员都有支持的序列化器,那么该结构体也是可以被自动识别并序列化的。
如果结构体重写了 bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
函数,需要在配置项中进行声明:
[/Script/IrisCore.ReplicationStateDescriptorConfig] | |
+SupportsStructNetSerializerList=(StructName=GameplayCueParameters) | |
+SupportsStructNetSerializerList=(StructName=GameplayAbilityTargetData_LocationInfo) | |
+SupportsStructNetSerializerList=(StructName=GameplayAbilityTargetData_ActorArray) | |
+SupportsStructNetSerializerList=(StructName=GameplayAbilityTargetData_SingleTargetHit) | |
+SupportsStructNetSerializerList=(StructName=LyraGameplayAbilityTargetData_SingleTargetHit) | |
+SupportsStructNetSerializerList=(StructName=NetLevelVisibilityTransactionId) | |
+SupportsStructNetSerializerList=(StructName=Vector2D) | |
+SupportsStructNetSerializerList=(StructName=GameplayDebuggerNetPack) |
/** Metadata about a gameplay cue execution */ | |
USTRUCT(BlueprintType, meta = (HasNativeBreak = "/Script/GameplayAbilities.AbilitySystemBlueprintLibrary.BreakGameplayCueParameters", HasNativeMake = "/Script/GameplayAbilities.AbilitySystemBlueprintLibrary.MakeGameplayCueParameters")) | |
struct GAMEPLAYABILITIES_API FGameplayCueParameters | |
{ | |
/** Optimized serializer */ | |
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); | |
//... | |
}; |
# FPropertyReplicationStateDescriptorBuilder && FReplicationStateDescriptorBuilder
FPropertyReplicationStateDescriptorBuilder
用来生成对象的 FPropertyReplicationStateDescriptorBuilder
。生成过程中会把 UClass 内的成员分为四类:
FunctionsPropertyReplicationStateBuilde:RPC Function。
InitPropertyReplicationStateBuilder:只用于 Init 的成员属性。
LifetimeConditionalsReplicationStateBuilder:有特定的生命周期同步条件的成员属性。
RegularPropertyReplicationStateBuilder:常规属性,既可以在对象初始化的时候同步,也可以在生命周期的各个阶段同步。
每类成员会通过单独的 FPropertyReplicationStateDescriptorBuilder
生成对应的 FReplicationStateDescriptor
。 FReplicationStateDescriptor
大致结构如下:
可能为了提高寻址效率吧, FPropertyReplicationStateDescriptorBuilder
在创建 FReplicationStateDescriptor
的时候,会把指针内的对象和 FReplicationStateDescriptor
本身分配在一块连续的内存中,在接下里要介绍的结构中也有广泛运用。
# LifetimeConditionalsReplicationState
对象中需要参与同步的属性,需要提前在对象中声明其所需的同步周期:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const |
并且指明哪些属性的同步规则,Unreal 提供了很多的宏用来干这些事情:
#define DOREPLIFETIME_WITH_PARAMS(c,v,params) \ | |
{ \ | |
FProperty* ReplicatedProperty = GetReplicatedProperty(StaticClass(), c::StaticClass(),GET_MEMBER_NAME_CHECKED(c,v)); \ | |
PRAGMA_DISABLE_DEPRECATION_WARNINGS \ | |
RegisterReplicatedLifetimeProperty(ReplicatedProperty, OutLifetimeProps, FixupParams<decltype(c::v)>(params)); \ | |
PRAGMA_ENABLE_DEPRECATION_WARNINGS \ | |
} | |
#define DOREPLIFETIME(c,v) DOREPLIFETIME_WITH_PARAMS(c,v,FDoRepLifetimeParams()) | |
#define DOREPLIFETIME_CONDITION(c,v,cond) \ | |
{ \ | |
static_assert(cond != COND_NetGroup, "COND_NetGroup cannot be used on replicated properties. Only when registering subobjects"); \ | |
FDoRepLifetimeParams LocalDoRepParams; \ | |
LocalDoRepParams.Condition = cond; \ | |
DOREPLIFETIME_WITH_PARAMS(c,v,LocalDoRepParams); \ | |
} | |
#define DOREPLIFETIME_WITH_PARAMS_FAST(c,v,params) \ | |
{ \ | |
static const bool bIsValid_##c_##v = ValidateReplicatedClassInheritance(StaticClass(), c::StaticClass(), TEXT(#v)); \ | |
const TCHAR* DoRepPropertyName_##c_##v(TEXT(#v)); \ | |
const NetworkingPrivate::FRepPropertyDescriptor PropertyDescriptor_##c_##v(DoRepPropertyName_##c_##v, (int32)c::ENetFields_Private::v, 1); \ | |
\ | |
PRAGMA_DISABLE_DEPRECATION_WARNINGS \ | |
RegisterReplicatedLifetimeProperty(PropertyDescriptor_##c_##v, OutLifetimeProps, FixupParams<decltype(c::v)>(params)); \ | |
PRAGMA_ENABLE_DEPRECATION_WARNINGS \ | |
} |
定义好之后,提取出来大概长这个样子:
FMemberProperty
用于描述某个 UClass 中成员的基本信息,也就是每个 Property 的相关信息:
const FProperty* Property; //property 本身 | |
const UFunction* PropertyRepNotifyFunction; // 触发属性复制时的回调函数 | |
const FPropertyNetSerializerInfo* SerializerInfo; //property 数据加工器 | |
CreateAndRegisterReplicationFragmentFunc CreateAndRegisterReplicationFragmentFunction; // 创建 Fragement 函数(FPropertyNetSerializerInfo) | |
EMemberPropertyTraits Traits; // Traits property | |
ELifetimeCondition ReplicationCondition; // 同步生命周期条件 | |
uint16 ChangeMaskBits; // 记录成员内部的脏标记 | |
FMemoryLayoutUtil::FSizeAndAlignment ExternalSizeAndAlignment; // 访问结构化对象时所需的内存偏移 |
FReplicationStateDescriptorBuilder
职责其实是把 UClass 内的 MetaData 提取出来,归类之后存储在 FPropertyReplicationStateDescriptorBuilder
的 FMemberProperty 和 FMemberFunction 中。然后调用 FPropertyReplicationStateDescriptorBuilder
的 Build 函数获取创建好的 FReplicationStateDescriptor
。
常用函数:
FReplicationStateDescriptorBuilder::CreateDescriptorsForClass
FReplicationStateDescriptorBuilder::CreateDescriptorForStruct
FReplicationStateDescriptorBuilder::CreateDescriptorForFunction
转换流程:
# FReplicationStateDescriptor && FPropertyReplicationState
FReplicationStateDescriptor
用来描述一组成员的 MetaData,可以通过 FReplicationStateDescriptor
对对象属性进行偏移寻址、序列化反序列化等操作。是所有对象打解包的重要依仗,并且 FReplicationStateDescriptor
本身是根据类型绑定的,一个 UClass 可以关联多个存储了不同 MemberDescriptor 的 FReplicationStateDescriptor
,但是这个 UClass 所对应的 UObject 都可以通过这几个 FReplicationStateDescriptor
进行描述和访问。
FPropertyReplicationState
是 FReplicationStateDescriptor
的「实例化」。按照 FReplicationStateDescriptor
描述的规则构建出的网络层面的对象,其主要负责对 UObject 上的属性值做临时性的存储。
# FReplicationFragment
FReplicationFragment
是比 FPropertyReplicationState
更上一层级的抽象。负责关联 FReplicationStateDescriptor
和 FPropertyReplicationState
,并在此基础上关联 UObject 的实例,这样就可以通过 Fragment 快速对 UObject 进行各项操作。
这里以用的最多的 FPropertyReplicationFragment 展开一下:
- 通过
FPropertyReplicationFragment
把网络数据回写 UObject 实例:通过 FReplicationReader 读取网络中收到的数据包,交由FPropertyReplicationState::PushPropertyReplicationState
把数据回写入 UObject 实例,然后把旧的 UObject 数据存储在 PrevReplicationState 中。
- 通过
FPropertyReplicationFragment
把 UObject 实例中的脏数据提取:通过FPropertyReplicationFragment::PollReplicatedState
再每帧开始同步前,根据脏标记从 UObject 实例上获取最新的脏数据,并且存储在 SrcReplicationState,后面再根据优先级序列化到 SendStateBuffer 中。
# FReplicationInstanceProtocol && FReplicationProtocol
FReplicationInstanceProtocol
结构图(简单以 FPropertyReplicationFragment
举例):
FReplicationProtocol
结构图(简单以 FPropertyReplicationFragment
举例):
两者是基于 FPropertyReplicationFragment
更上层级的抽象,并且也是对外暴露的最开放的结构。
FReplicationProtocol
更倾向于存储配置或者规则性质的内容。FReplicationInstanceProtocol
则更多的存储和 Instance 有关的 State 信息。
# 总结
首先,除开 NetSerializerInfo 是在 Module 启动的时候就完成了所有类型的注册。其余的内容包括: Protocol、Fragment、ReplicationStateDescriptor、ReplicationState 都是在当有对象触发同步的时候以懒加载的方式创建并注册的。我们也可以简单整理出上述结构的关系图:
- FPropertyNetSerializerInfoRegistry 管理所有的 FPropertyNetSerializerInfo 进而管理所有的 FNetSerializer。它们在 IrisModule 启动的时候就被完全初始化好,以供后面的系统使用。
- FReplicationStateDescriptorRegistry 管理所有的 FReplicationStateDescriptor。
- FReplicationStateDescriptor 由存储若干个 FReplicationStateMemberSerializerDescriptor。并且保存了对应于 UClass 中 FProperty 的引用。
- FReplicationStateMemberSerializerDescriptor 关联该 FProperty 的 FNetSerializer 用来处理打解包等序列化操作。
- FReplicationProtocol 存储了 UClass 的全部描述信息(若干个 FReplicationStateDescriptor)
- FPropertyReplicationState 保存若干个 FProperty 的描述(FReplicationStateDescriptor)和对应的数据(StateBuffer)。
- FPropertyReplicationFragment 再次基础上关联了某个 UObject 实例,并实现了 StateBuffer <-> UObject 实例数据交换的逻辑。
- FReplicationInstanceProtocol 管理多个
FPropertyReplicationFragment
,也就是一个完整 UObject 实例的全部数据的定义和存储。 - FReplicationProtocolManager 负责缓存所有已经创建的 FReplicationProtocol 避免重复调用构造函数。
除去 FNetSerializer 以外,其他结构的创建都从对象的 BeginReplication 开始:
整个 Replication System 其实是完全解耦于 Gameplay 的,不依赖 Gameplay 通过事件推送获取对象脏信息,而是通过 Poll 的轮询方式定期进行检查。数据的交互通过 ReplicationState 完成,除此以外其他任何模块都有内部独立的索引,引用关系等。