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,
};
};