UE代码-TArray的属性同步
1. 相关技术文档
- https://zhuanlan.zhihu.com/p/264799345
- https://blog.csdn.net/u012999985/article/details/78384199
- http://www.aclockworkberry.com/custom-struct-serialization-for-networking-in-unreal-engine/
- https://ikrima.dev/ue4guide/networking/network-replication/fast-tarray-replication/
2. 属性同步相关源码位置
- NetDiver.h/cpp: 服务器在NetDiver的TickFlush里面,每一帧都会去执行ServerReplicateActors来同步Actor的相关内容。
- UActorChannel.h/cpp: UActorChannel::ReplicateActor执行真正的Actor同步以及内部数据的同步。
3. TArray如何同步
对于一个数组的同步,会先对其进行一个判断,在初始化RepLayOut的Cmds数组的时候,就会判断当前的属性类型是否是动态数组(UArrayProperty),并会给其cmd.type做上标记REPCMD_DynamicArray。
- 对于静态数组,是将每个元素都作为一个单独的属性,进行同步。
- 对与动态数组,则是先进行一个长度的判断,比如服务器上数组长度发生变化,客户端在接收同步过来的数组时,会执行FRepLayout::ReceiveProperties_DynamicArray_r来处理动态数组,这个函数里面会矫正当前对象同步数组的大小。而若进行删除或插入操作时,由于TArray是一个连续的数组,所以可能导致后缀一长串的元素往后移或往前移动,而UE4的属性同步机制会认为,一连串的元素都发生改变,从而同步一长串的数据。
在这个影响下,使用插入删除TArray的元素时可能会导致一个效率的下降。
解决方法:
- 官方给的方案就是用FastTArray来替代TArray的属性同步了。FastTArray的使用方法见UE4引擎头文件源码:NetSerialization.h。
- 贪心策略:
- 可以减少使用插入删除操作,用其他方式代替;
- 还可以牺牲少部分时间对TArray进行一个优先级的排序,经常插入删除的元素尽量放在数组后面,这样受到其影响的后缀长度会尽可能少。
4. FastTArray相关
4.1 原理:
对TArray这种动态属性实现一个增量序列化,增量序列化是通过比较初始状态和当前状态并生成的,并生成一个差异状态与完全状态,并更新其为新的初始状态。
4.2 优缺点:
- 优点:没有通过常规的TArray方式同步,避免了从中间删除元素,会使得后面所有元素都需要重新同步的情况,减少大量开销。
- 缺点:当数据发生改变后,不能保证服务端与客户端中TArray元素顺序一致。
4.3 如何使用FastTArray:
/** Step 1: Make your struct inherit from FFastArraySerializerItem */
USTRUCT()
struct FExampleItemEntry : public FFastArraySerializerItem
{
GENERATED_USTRUCT_BODY()
// Your data:
UPROPERTY()
int32 ExampleIntProperty;
UPROPERTY()
float ExampleFloatProperty;
/** Optional functions you can implement for client side notification of changes to items */
void PreReplicatedRemove();
void PostReplicatedAdd();
void PostReplicatedChange();
};
/** Step 2: You MUST wrap your TArray in another struct that inherits from FFastArraySerializer */
USTRUCT()
struct FExampleArray: public FFastArraySerializer
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
TArray<FExampleItemEntry> Items; /** Step 3: You MUST have a TArray named Items of the struct you made in step 1. */
/** Step 4: Copy this, replace example with your names */
bool NetDeltaSerialize(FNetDeltaSerializeInfo & DeltaParms)
{
return FastArrayDeltaSerialize<FExampleItemEntry>( Items, DeltaParms );
}
};
/** Step 5: Copy and paste this struct trait, replacing FExampleArray with your Step 2 struct. */
template<>
struct TStructOpsTypeTraits< FExampleArray > : public TStructOpsTypeTraitsBase
{
enum
{
WithNetDeltaSerializer = true,
};
};