[UE]Subobject同步优化
Subobject同步优化
基于 PushModel 的 Subobject 同步优化;
基本机制
UE 提供了 PushModel 机制:
对于 Property 这一层,通过 UEPushModelPrivate::MarkPropertyDirty 主动将其标记为 Dirty;
在 FObjectReplicator::ReplicateProperties_r 进行属性同步时,通过 PushModelState->IsPropertyDirty 判断属性是否 Dirty ;
既然可以判断一个同步对象上的同步 Property 是否有变更,就可以筛选出没有属性变化的对象;可以在 UActorChannel::ReplicateActor、UActorChannel::WriteSubObjectInBunch 时,判断 FObjectReplicator::CanSkipUpdate 跳过这些对象,加快同步速度;
1 | bCanSkip = true; |
针对 Subobject 进一步优化
可以观察到 PushModel 在 AActor::ReplicateSubobjects 时,内部针对每个 SubObject 时,进行了一系列优化(CanSkip 的判定);
但是还是每帧在遍历 ReplicatedComponents 进行判定,并且判定的逻辑本身比较复杂,常数较大,也会造成很大的开销;
这意味着即使没有任何 Component 的 Property 被 MarkDirty,本身 ReplicatedComponents 越多,开销也会随之越大;
基本思路
需要一个机制更加精确地进行 ReplicateSubobject,做到对于非 Dirty 的 Component 直接不遍历;
显然,对于一个 Actor,对于所有需要同步这个 Actor 的 Connection,可以直接记录下 DirtyComponents,即发生了属性变化但还未同步更新的 Subobject;在每次 Dirty 时加入,在同步后移除;
NotifyDirty
首先需要可以知道一个 Component 被 MarkDirty;
在 PushModel 中新增 Delegate:
1 | class NETCORE_API FNetPushModelDelegate |
修改 PushModel::MarkPropertyDirty:
1 | void MarkPropertyDirty(const FNetLegacyPushObjectId ObjectId, const int32 RepIndex) |
Connection
对于每个 Actor,维护一份 Data,其中维护对其可见的 Connection;
显然可以在 AActor::ReplicateSubobjects 时,将 Connection 加入存储列表;
在 RemoveClientConnection 时,将 Connection 从所有的 Actor 的 Data 中移除;
DirtyComponents
在每次收到 FNetPushModelDelegate::OnMarkPropertyDirty 时,记录该 Subobject 到其 Parent 维护的 DirtyComponents 数据中;
同时,当 ActorComponent 进行 Register、Unregister 时,也需要将其加入 DirtyComponents;
特别地,为了考虑丢包的情况,判断取出 FSendingRepState* SendingRepState = Channel->ActorReplicator->RepState->GetSendingRepState(),然后判定 SendingRepState->NumNaks > 0 时(即存在丢包时),将所有数据重新传一遍(即所有的 ReplicatedComponents 都加入 DirtyComponents);
每次 ReplicateSubobjects 后,清空 DirtyComponents;
这样就可以保证 DirtyComponents 的个数限制,未 MarkDirty 的不参与 Replicate;


