UpdateChangelistMgr详解
关键变量
FRepChangedPropertyTracker
FRepChangedPropertyTracker
存储着一帧对应Object Parent Properties
是否Dirty
.
每个Object
只有一份FRepChangedPropertyTracker
,
所有Connection
共享,
它标志着该Object内Parent层级的属性是否Dirty,
并存储在UNetDriver.RepChangedPropertyTrackerMap
中(关键函数UNetDriver::FindOrCreateRepChangedPropertyTracker
).
并且FRepChangedPropertyTracker.Parents
初始化时候默认为1,
即Dirty. 而且不会自动清理, 即如果使用就要自己维护.
类图
创建流程
在创建ObjectReplicator时,
在DS上同时创建对应的FRepChangedPropertyTracker
,
关键堆栈:
初始化
FRepChangedPropertyTracker
最为关键的变量就是FRepChangedPropertyTracker.Parents
,
FRepChangedPropertyTracker.Parents
初始化时候默认为1,
即Dirty.
修改
目前只有如下位置使用了该功能. 表示如果某一个标志没有开启,
对整个结构体都不用检测了.
AActor.bReplicateMovement
控制AActor.ReplicatedMovement
是否同步.
FRepRootMotionMontage.bIsActive
控制ACharacter.RepRootMotion
是否同步.
只使用未修改
虽然每个Object只有一份, 所有Connection
共享,
但是它却是在FSendingRepState.RepChangedPropertyTracker
中被使用,
原因如下:
1.用于检测Parent层级的属性是否Dirty. 如果不为Dirty就不会进行比较, 直接skip. 关键函数``.
2.虽有引用(如下图), 但是并无真正使用.
清理
FRepChangedPropertyTracker.Parents
初始化时候默认为1,
即Dirty. 所有不使用该功能的成员都默认为Dirty,
即每次都需要对比子成员. 对于需要使用该功能的成员则需要自己维护.
示例(ACharacter.RepRootMotion
):
注意
该功能是有bug的, 一个属性发生变化需要将最终结果同步到远端,
即也需要一次同步.
针对ACharacter.RepRootMotion
的修改如下.
思考:
为什么AActor.ReplicatedMovement
和AActor.AttachmentReplication
不用进行如下修改呢?
对比二者OnRep的不同:
可以发现,
RepRootMotion.bIsActive
是放在RepRootMotion
里面的,
即如果不同步,
针对某些Connection
远端永远不会接收最后一次的属性修改,
即永远不会为false;
而另一些Connection
远端却永远为false
,
都不会为true
.
某些Connection bIsActive永远为true的原因:最后一次的属性变化没同步. 而false变成true时, shadowData中为true, 所以也不会同步, 造成远端永远为true.
某些Connection bIsActive永远为false的原因: 因为丢包. 发生重传永远是重传最新数据, 而不是Shadow中的, 所以会传到远端为false, 而DS上的bIsActive变为true后, 和ShadowData(也为true)相同, 所以不会传送给远端, 最终远端永远为fasle, 除非发生丢包重传.
FRepChangelistState.StaticBuffer
每个UObject只有一份, 所有Connection共用一份. 它存储着上次比较后的数据.
创建
分配内存, 并拷贝CDO数据.
关键函数FRepLayout.InitRepStateStaticBuffer
+FRepLayout.ConstructProperties
+FRepLayout.CopyProperties
1 | void FRepLayout::InitRepStateStaticBuffer(FRepStateStaticBuffer& ShadowData, const FConstRepObjectDataBuffer Source) const |
ShadowData最初来源
用CDO的数据进行初始化.
调用堆栈:
ShadowData更新
每个Object
独一份, 所有Connection
共享.
它存储着上次比较不相等后的最新值.
在CompareProperties_r
函数中, 通过比较发现如果真的不相等,
就会更新ShadowData.Data
数据.
流程
UpdateChangelistMgr
流程图
Merge NotSend
合并没有发送的ChangedList
.
Actor
所有Connection
共享的, 每个Actor
只有一份FObjectReplicator.ChangelistMgr
, 它也是所有Connection共享的.- 每个Connection的
Actor Replicate
是相互独立的, 即允许Actor在某个Connection上跨帧同步.
基于以上背景, 某些Actor可能因为跨帧, 导致某些帧的Changed还没有加入到同步队列中, 此时需要将其合并到Changed中.
合并没有发送的ChangedList
相关代码:
Merge Resend data
Merge Resend Data在FRepLayout.ReplicateProperties
中,
这里不赘述. 关键代码:
问
新的Connection
,
在第一次同步场景中某个Actor, 是怎么处理的?
背景: 某个Actor已经经过很多次修改,
这时候新来了一个Connection
, 怎么同步此时的Actor?
答案: 如果在记录History时,
有一种方法能记录某个时间点之前的全部变化,
那么针对新的Connection
到来时,
只要将之前的变化全部发送给对应的Connection
就可以了.
UE的方式也是如此, UE使用一个大小为64的buffer, 此buffer不是环形buffer,
它是累积buffer, 如果超过了目前使用的ChangedList超过了64,
那么就会将第一个合并到第二个上, 然后第一个会空余出来,
第二个变成新序列的第一个,
即FRepChangelistState.HistoryStart++
.
UE的理念第一个元素表示某个时间点之前的所有变化,
而其他的Item依然存储当时Compare的Changed结果.
关键函数FRepLayout.CompareProperties
.
最终发送的数据是ShadowData
还是Object
本身
答:最终发送的数据是Object
本身.
所以ShadowData
只用于对比,
最终发送给各个端的数据还是Object
本身.
在函数FRepLayout.SendProperties_r
中序列化Property
.
可以看出拷贝的数据源为SourceData
进一步追踪,
查到其来源为在函数FObjectReplicator.ReplicateProperties
中,
传入了Channel
所对应的Object
本身.
执行堆栈: