[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
;