UE4.27, 揣摩源码, 网络同步 (二) 同步
3. 同步指的是UE里服务器对客户端的单向数据同步
4. Actor同步
4.1. 堆栈调用
UNetDriver::TickFlush
// 作为服务端的时候将调用该函数
UNetDriver::ServerReplicateActors
ServerReplicateActors_PrepConnections
// 1.ViewTarget从PlayerController赋给NetConnection和ChildNetConnection
// 2.计算这个tick要处理同步的客户端连接数目
// 具体而言,基准是GEngine的一个设定值
// 因为这个设定值按秒计算,自然地乘算一个间隔时长,如果这一帧clientconnection计数为零,会把这帧的间隔时长非持存地向后传递一帧
// Lan的话还会再乘双倍
// 最后的值和总的clientconnection计数取最小值
ServerReplicateActors_BuildConsiderList
// 对FNetworkObjectInfo数组进行处理,其中大部分是记录某actor的这次和下次的同步时间
// 值得注意的是,下次的同步时间是区间随机的,我能想到的理由是 平衡同时同步的流量压力 或 使玩家体验更加正常
/** ClientConnection遍历循环 **/
ServerReplicateActors_PrioritizeActors
// 优先级排序
ServerReplicateActors_ProcessPrioritizedActors
// 处理实际的 Actor 同步
// 一系列的创建ActorChannel
ReplicateActor
UPackageMapClient::SerializeNewActor
/** 循环完毕 **/
/** 5035 ~ 5047 **/
// 将尚未处理的客户端连接放在数组的前面,让下一帧优先处理
4.2. 流程详细介绍
4.2.1. 同步的起点
同步总是先从Actor同步开始的,这一点可以从我们总是SpawnActor在服务器上,然后同步到客户端上的这一流程看出
细致地来说,同步Actor需要将Actor的属性给同步给客户端上的Actor,而第一次同步还需要在客户端上创建Actor
4.2.2. 第一次同步Actor
需要说明的是,服务器上创建了actor,同步之前,客户端上是没有这个actor的,所以第一次同步需要在客户端上spawn一个同样的actor
spawnactor所需的信息里,uobject是必不可少的。
服务器如何告诉客户端这个uobject的信息呢?我们之前已经体会了ue的正反序列化,假如我们处理的uobject是正常序列化存储在本地的,对于服务器和客户端来说,uobject的资源路径,
实质上也就是它们外部链最外层的那个package是在同样的相对路径下的,因此我们不必要传输一整个uobject信息,只需要传递这个资源路径信息path即可
然而第一次同步确实只需要这个path,但是第二次第三次呢?这个path有没有进一步节约流量的可能?更严重的问题,actor实例化出来后,和资源路径的对应关系不必然是一对一的,也可能是多对一的
为了解决对应问题和节约流量,ue设计了UPackageMapClient来管理这些分布在客户端和服务器上的具有同步关系的actor,最关键的对应关系是NetGUID和ClassPath
服务器将首先传递ClassPath和服务器端对这个actor的唯一标识NetGUID给客户端,客户端凭借ClassPath完成spawn,并且建立接受到的NetGUID和这个新actor的映射关系
最后的效果就是,除了第一次需要传递ClassPath来完成spawn,后续的同步都可以凭借NGUID来达到在不同端上指定具有同一同步关系的actor
5. 属性同步
5.1. 堆栈调用
UNetDriver::TickFlush
UNetDriver::ServerReplicateActors
ServerReplicateActors_PrepConnections
ServerReplicateActors_BuildConsiderList
/** ClientConnection遍历循环 **/
ServerReplicateActors_PrioritizeActors
ServerReplicateActors_ProcessPrioritizedActors
ReplicateActor
ReplicateProperties
UpdateChangelistMgr
CompareProperties
// 遍历每个UProperty检查是否有变化
// 如果有变化
SendProperties
/** 循环完毕 **/