# 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 实例采用不同的加工方案。

image-20240306103056704

image-20240310110505711

序列化器的声明:

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 生成对应的 FReplicationStateDescriptorFReplicationStateDescriptor 大致结构如下:

image-20240306113802555

可能为了提高寻址效率吧, 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 \
}

定义好之后,提取出来大概长这个样子:

image-20240306195715818

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

转换流程:

image-20240306141502363

# FReplicationStateDescriptor && FPropertyReplicationState

FReplicationStateDescriptor 用来描述一组成员的 MetaData,可以通过 FReplicationStateDescriptor 对对象属性进行偏移寻址、序列化反序列化等操作。是所有对象打解包的重要依仗,并且 FReplicationStateDescriptor 本身是根据类型绑定的,一个 UClass 可以关联多个存储了不同 MemberDescriptor 的 FReplicationStateDescriptor ,但是这个 UClass 所对应的 UObject 都可以通过这几个 FReplicationStateDescriptor 进行描述和访问。

FPropertyReplicationStateFReplicationStateDescriptor 的「实例化」。按照 FReplicationStateDescriptor 描述的规则构建出的网络层面的对象,其主要负责对 UObject 上的属性值做临时性的存储。

# FReplicationFragment

FReplicationFragment 是比 FPropertyReplicationState 更上一层级的抽象。负责关联 FReplicationStateDescriptorFPropertyReplicationState ,并在此基础上关联 UObject 的实例,这样就可以通过 Fragment 快速对 UObject 进行各项操作。

image-20240306170813210

这里以用的最多的 FPropertyReplicationFragment 展开一下:

  • 通过 FPropertyReplicationFragment 把网络数据回写 UObject 实例:通过 FReplicationReader 读取网络中收到的数据包,交由 FPropertyReplicationState::PushPropertyReplicationState 把数据回写入 UObject 实例,然后把旧的 UObject 数据存储在 PrevReplicationState 中。

image-20240306163909949

  • 通过 FPropertyReplicationFragment 把 UObject 实例中的脏数据提取:通过 FPropertyReplicationFragment::PollReplicatedState 再每帧开始同步前,根据脏标记从 UObject 实例上获取最新的脏数据,并且存储在 SrcReplicationState,后面再根据优先级序列化到 SendStateBuffer 中。

image-20240306171221158

# FReplicationInstanceProtocol && FReplicationProtocol

FReplicationInstanceProtocol 结构图(简单以 FPropertyReplicationFragment 举例):

image-20240306172120538

FReplicationProtocol 结构图(简单以 FPropertyReplicationFragment 举例):

image-20240306192928846

两者是基于 FPropertyReplicationFragment 更上层级的抽象,并且也是对外暴露的最开放的结构。

  • FReplicationProtocol 更倾向于存储配置或者规则性质的内容。
  • FReplicationInstanceProtocol 则更多的存储和 Instance 有关的 State 信息。

# 总结

首先,除开 NetSerializerInfo 是在 Module 启动的时候就完成了所有类型的注册。其余的内容包括: Protocol、Fragment、ReplicationStateDescriptor、ReplicationState 都是在当有对象触发同步的时候以懒加载的方式创建并注册的。我们也可以简单整理出上述结构的关系图:

image-20240306204200358

  • 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 开始:

image-20240306205219395

image-20240306205318111

整个 Replication System 其实是完全解耦于 Gameplay 的,不依赖 Gameplay 通过事件推送获取对象脏信息,而是通过 Poll 的轮询方式定期进行检查。数据的交互通过 ReplicationState 完成,除此以外其他任何模块都有内部独立的索引,引用关系等。