Unreal--Replication Graph Note
前言Replication Graph是Unreal于4.20新添加的特性,用于优化服务器的性能,本文将从下列几个问题入手来探讨其作用:
比起传统的Replication流程,Graph是如何优化的?Graph的具体流程是怎么样的?如果要自定义某些流程,如何接入?
性能问题
查看官方文档中,对于传统复制流程的描述是:
The standard network replication strategy, which is to require each replicated Actor to determine whether or not it should send an update to each connected client, performs poorly in cases like this and will bottleneck the server's CPU.
注意上述的着重点是 determine whether or not is should send an update to each connected client. 即真正的性能消耗点是在判断该Actor是否应该同步到所有的client中,换句话说性能瓶颈出在对整个Actorlist的迭代。我们假设Acotrlist长度为M,connection数量为N,那么最后的时间复杂度就为O(MN).
那Epic是使用什么结构来加速更新判断呢?这里引入一个概念: Replication Graph Nodes,之前所有需要同步的Actor都在一个List容器中,而引入Node后,首先对不同的数据类型进行分类,每类节点将负责不同的同步数据。如下图(图片有些糊,官网这里找原图 https://www.unrealengine.com/zh-CN/tech-blog/replication-graph-overview-and-proper-replication-methods?sessionInvalidated=true )
然后在上图中除去Grid Spatialization节点外的公共节点需要每次都迭代,但GridSpatialization2D只需要循环当前所在的Grid Cell内的信息。且在GridSpatialization2D内还对Actor做了详细分类,细节节点可以参考文章末尾文章。 总结来说就是,一个GridSpatialization2D节点有多个grid cell,grid cell只是GridSpatialization2D的子结构,同步时判断的位置信息也是根据Grid Cell判断的 。
简单分析第一层节点的分类类型如下:
Grid Spatialization2D
按照2D空间划分的同步节点 ,也是真正性能优化的地方,其本质还是空间划分。
Playerstate
与状态有关的节点,全局节点与所有连接共享。
Always Relevant(All):
与所有client都总是同步的节点,全局节点与所有连接共享。
Tear off:
红色节点表示每个连接独自拥有的数据,在initForNtDriver时,Graph会为每一个连接创建一个UNetReplicationGraphConnection类型的ConnectionManager用来管理连接独有的数据,所有的ForConnection节点都被其管理。例如对Trar Off来说就是当前连接删除的数据。
Always Relevant(connection):
与上述类似,表示当前连接总是同步的节点数据。
Always Relevant(Team):
与某些连接同步,例如一个Team中的数据需要同步到整个队伍中,仅与队伍中的连接同步,这类可以需要将收集逻辑按照需求改写。
流程问题
在分析完性能优化的原理后,我们继续分析其的详细流程是如何执行。
原始流程:
如果没有配置或者手动创建ReplicationDriver,在进入UNetDriver::ServerReplicateActors后走原始流程:
ServerReplicateActors_PrepConnections:确定连接的客户端。ServerReplicateActors_BuildConsiderList:创建同步列表。ServerReplicateActors_PrioritizeActors:排序列表中同步的优先级(根据Actor的Priority属性)。ServerReplicateActors_ProcessPrioritizedActors:根据最后排序后的数据进行同步。
性能主要消耗点在创建同步列表和优先级排序。
ReplicationGraph流程:
如果创建了ReplicationDriver,进入UReplicationGraph下的ServerReplicateActors。
PrepareForReplication:
执行各个节点同步前的初始化操作,例如GridSpatialization2D Node一个dynamic Acotr从一个cell到另外一个cell的更新、超过SpatialBias时对Cell的重建等。
循环每一个连接,在连接中执行下列逻辑:
创建FConnectionGatherActorListParameters结构。GlobalGraphNodes-----GatherActorListsForConnection:
收集公共节点的Actor数据,例如GridSpatialization节点,Always Relevant(ALL)节点。
ConnectionManager->ConnectionGraphNodes-----GatherActorListsForConnection:
收集仅与当前节点相关的Actor数据,例如Always Relevant(Connection)节点。
ReplicateActorListsForConnection_Default:
对应原始流程的ServerReplicateActors_PrioritizeActors,对收集好的Actor List进行同步优先级排序。
ReplicateActorListsForConnection_FastShared:
对应原始流程的ServerReplicateActors_ProcessPrioritizedActors,同步每个通过当前连接同步要求的Actor。
分发流程:
UReplicationGraph::AddNetworkActor添加同步ActorUReplicationGraph::RouteAddNetworkToNodes,这里为我们自定义的路由函数,所有对具体节点的处理都可以写到这里。
实例分析
以下是官方的一个工程shootGame,其中使用了ReplicationGraph.
重要参数声明
DestructionInfoMaxDist:Replication Graph流程中同步销毁的的Actor距离,大于这个距离的情况下不会将销毁信息同步给client,需要注意的是在Server上Actor已经被销毁掉了,只是还未同步给client。
CellSize:每个Grid的大小(数字代表长度与宽度,数值相同)SpatialBiasX:边界X值,当超出这个值时会调用HandleActorOutofSpaticalBounds重新设置边界值且设置bNeedsRebuild标志重新构建Grid数组。下图为重新构建部分
SpaticalBiasY:边界Y值,同上。
类型容器初始化InitGlobalActorClassSettings
路由类型保存到ClassRepNodePolicies容器中,该容器在我们自定义的ReplicationGraph类中,在对SpatialNode进行路由时用到,用于在RouteAddNetworkActorToNodes或RouteRemoveNetworkActorToNodes时判断类型,下图是部分路由容器添加代码
保存所有同步需要同步的Actor类型到Graph下的GlobalActorReplicationInfoMap下,这里除了APawn与APlayerState类型参数特别设置,其他都使用默认参数。
创建全局节点与对连接节点
全局节点创建函数InitGlobalGraphNodes,如下图这里创建了三种Global节点
部分连接节点创建函数InitConnectionGraphNodes,如下图
最后定义路由函数与各个自定义节点的GatherActorListsForConnection、PrepareForReplication等逻辑函数。
参考资料
https://docs.unrealengine.com/en-US/Engine/Networking/ReplicationGraph/index.html https://cloud.tencent.com/developer/article/1374074 https://www.unrealengine.com/zh-CN/tech-blog/replication-graph-overview-and-proper-replication-methods?sessionInvalidated=true
页:
[1]