[UE]ReplicationGraph应用
ReplicationGraph应用
简介
UnrealEngine
基本的网络同步在进行同步时需要对每个连接判断每个 Actor
是否需要同步,开销较大;
可以通过实现 Replication
,实现一个不同的 ReplicationDriver
来优化性能;
Replication
将 World
分为多个区域 Grid
,把 Actor
根据所在 Grid
进行分类编组,可以快速找出需要同步的 Actor
,同时确定哪些区域需要进行同步复制。
基本原理
对于需要按照空间划分(Spatialize
)的 Actor
来说:
首先将 World
按照 CellSize
抽象成一个个 Cell
,一个 Cell
对应一个 GridNode
,这里的 GridNode
只会在具体用到的时候再创建;
那么对于一个 Actor
,根据 Location
、CellSize
、SpatialBias
,就可以计算出其所在的具体 Cell
,即 GetCellInfoForActor
;
构建
构建 ReplicationGraph
时,在 GridNode
中记录 Actor
;
对于 Actor
,就可以根据 CellInfo
、以及 CullDistance
找到需要同步该 Actor
的 Cell
,这里 Cell
对应的 Grid
中会把该 Actor
记录下来;
也就是上述的粉色区域部分,每个 Grid
都会记录下该 Actor
;
对于 Spatialize_Static
,即静态物体,显然只需要 Add
时计算一次即可;
对于 Spatialize_Dynamic
,即动态物体,会在每次开始 PrepareForReplication
时,根据 DynamicActor
的 PreviousCellInfo
、NewCellInfo
,更新对应的 Grid
存储信息;
查询
在对于一个 Connection
查询哪些 Actor
需要同步时:
UpdateGatherLocationsForConnection
更新 Connection
对应的 Location
根据该 Location
计算出该 Connection
对应的 Cell
,把 Cell
对应的 GridNode
中的 Actors
同步下去即可;
源码浅析
数据结构
classDiagram class UReplicationGraph
FGlobalActorReplicationInfo
:同步的Actor
信息,包括
Settings
:FClassReplicationInfo
,每个Actor Class
对应的同步配置,包括CullDistance
、ReplicationPeriodFrame
、PriorityScale
等;
Events
:FGlobalActorReplicationEvents Events
,记录休眠状态的改变时,从休眠列表中添加或者移除;
FConnectionReplicationActorInfo
:对于Connection
同步的Actor
信息;
-
ReplicationGraphNode
:GraphNode
基类; -
ReplicationGraphNode_ActorList
:记录同步的Actors
;在StreamingLevelCollection
(以SubLevelName
为Key
的List
)中记录SubLevel
的Actor
,否则在ReplicationActorList
中记录; -
GlobalGraphNodes
:维护GridSpatialization2D
;
GraphNode
classDiagram class UReplicationGraphNode { TArray~UReplicationGraphNode*~ AllChildNodes GatherActorListsForConnection() RouteAddNetworkActorToNodes() RouteRemoveNetworkActorToNodes() } UReplicationGraphNode <|-- UReplicationGraphNode_ActorList class UReplicationGraphNode_ActorList { FActorRepListRefView ReplicationActorList } UReplicationGraphNode <|-- UReplicationGraphNode_GridSpatialization2D class UReplicationGraphNode_GridSpatialization2D { TArray~TArray[UReplicationGraphNode_GridCell*]~ Grid; } UReplicationGraphNode <|-- UReplicationGraphNode_AlwaysRelevant class UReplicationGraphNode_AlwaysRelevant{ TArray~UClass*~AlwaysRelevantClasses; } UReplicationGraphNode <|-- UReplicationGraphNode_ActorListFrequencyBuckets UReplicationGraphNode_ActorList <|-- UReplicationGraphNode_GridCell UReplicationGraphNode_ActorList <|-- UReplicationGraphNode_AlwaysRelevant_ForConnection
GridSpatalization2D
:将世界划分为2D
网格,按位置把Actor
分到不同的GridCell
中,按空间管理Actor
是否进行同步,每帧更新GridCell
内的Actor
;GridCell
:ReplicationActorList
缓存着在该GridCell
中的所有静态Actor
,DynamicNodes
里记录动态的Actor
,DormancyNode
里记录休眠的Actor
;AlwaysRelevant
:处理总是发送Net Updates
给 所有Connections
的Actors
;AlwaysRelevant_ForConnection
:处理总是发送Net Updates
给 特定Connection
的Actors
,一般是同步给PlayerController
和ViewTarget
;ActorListFrequencyBuckets
:记录地图格子上的 动态Actor
;
生命周期
Init
graph TD Start(UNetDriver::InitBase) --> A("UNetDriver::SetReplicationDriver") A --> B("UReplicationGraph::InitializeActorsInWorld") B --> | 将World中的同步对象添加到对应GraphNode | C("UReplicationGraph::InitializeForWorld") C --> D("UReplicationGraph::AddNetworkActor(AActor* Actor)") A --> E(InitForNetDriver) E --> F(InitGlobalActorClassSettings) E --> G(InitGlobalGraphNodes)
InitGlobalActorClassSettings
:设置 CulltDistance
、ReplicationPeriodFrame
; 等信息注册 Actor
对应的 ClassReplicationInfo
到 GlobalActorReplicationInfoMap
;
InitGlobalGraphNodes
:创建 GridSpatialization2D
、AlwaysRelevant
的 GraphNode
;
InitConnectionGraphNodes
:创建 AlwaysRelevantForConnection
的 GraphNode
;
RouteAddNetworkActorToNodes
:生成 Actor
时,添加 NetworkActor
,分发 Actor
到 GraphNode
;
RouteRemoveNetworkActorToNodes
:销毁 Actor
时,删除 NetworkActor
,通知GraphNode
移除 Actor
;
Repliate
graph TB Start(ServerReplicateActors) --> A(PrepareForReplication) A --> B(GatherActorListsForConnection) B --> C(ReplicateActorListsForConnections_Default) C --> D[ReplicateSingleActor]
-
PrepareForReplication
: 调用GraphNode
的PrepareForReplication
:对于
GridSpatialization2D
,会遍历DynamicActor
,更新其所在的Grid
;对于
AlwaysRelevant
,记录需要同步给所有连接的Actor
; -
GatherActorListsForConnection
: 遍历Connections
收集ReplicationActorList
针对每个
Connection
遍历GlobalGraphNodes
和Connection
的ConnectionGraphNodes
,调用其GatherActorListsForConnection
,收集需要同步给这个Connection
的Actor
;对于
GridSpatialization2D
,通过其GridCellNode
根据Actor
的ViewLocation
收集;收集的
Actor
默认加到GatheredReplicationListsForConnection
里的EActorRepListTypeFlags.Default List
; -
ReplicateActorListsForConnections_Default
:进行Replicate
的同步检测与排序,对GatheredReplicationListsForConnection
里的Actor
,进行检测;首先排除
Dormancy
、不满足ReplicateFrame
的Actor
,然后根据优先级排序(Distance
、Starvation
、逻辑判定
、Owner & ViewTarget
),将结果缓存在PrioritizedReplicationList
中; -
ReplicateSingleActor
:对排好序的PrioritizedReplicationList
调用ReplicateSingleActor
进行同步,将对象属性序列化到流中;
具体应用
业务划分
可以按照这些逻辑区分各个 Actor
:
Node_AlwaysRelevant_ForConnection
:一直同步的Actor
,比如GameState
、全局同步对象;Node_GridSpatialization2D
:用2D Grid
划分,按照区域同步,比如Pawn
、Character
、SceneItem
等;Node_PlayerStateFrequencyLimiter
:用于PlayerState
,控制同步频率;
初始化
在一个尽早的合适的地方注册UReplicationDriver::CreateReplicationDriverDelegate()
;
Lyra
在 ULyraReplicationGraph
的构造函数中(创建 CDO
时)进行;
后续 UNetDriver::InitBase -> SetReplicationDriver
时,会使用该方法进行自定义 ReplicationGraph
的注册:
1 | UReplicationDriver* UReplicationDriver::CreateReplicationDriver(UNetDriver* NetDriver, const FURL& URL, UWorld* World) |
配置化
可以参考 Lyra
注册 ULyraReplicationGraphSettings : UDeveloperSettingsBackedByCVars
在 Project Settings
中,也可以自定义一个 Blueprint
用于记录所有相关配置(在初始化时,重载为初始化该 BP
);
InitGlobalActorClassSettings
进行 Class
相关设置:
SetPolicy
记录 TClassMap<EClassRepNodeMapping> ClassRepNodePolicies
用于表示 Class
对应的 Node Policy
;
在 RouteAddNetworkActorToNodes
、RouteRemoveNetworkActorToNodes
时,通过 GetMappingPolicy(UClass* Class)
找到对应 Policy
的 Node
对应方法;
1 | EClassRepNodeMapping* PolicyPtr = ClassRepNodePolicies.Get(Class); |
可以提供自定义 Policy
的配置,同时需要通过 Class
的一些基础配置设置好 Policy
,比如 bAlwaysRelevant && !bOnlyRelevantToOwner
显然是 RelevantAllConnections
;
SetClassInfo
FGlobalActorReplicationInfoMap GlobalActorReplicationInfoMap
记录着所有 Class
对应的同步数据,
通过将自定义数据 GlobalActorReplicationInfoMap.SetClassInfo(Class, Info)
,并记录 ExplicitlySetClasses
表示该 Class
为自定义类型,防止后续被重载;
可以提供配置化的 Class->FClassReplicationInfo Map
;
InitGlobalGraphNodes
进行 ReplicationNode
相关设置,CreateNode
并进行参数设置:
- 设置
GridNode
的CellSize
(每个Grid
的大小)、SpatialBias
(边界值,ActorLocation
超出时会调用HandleActorOutOfSpatialBounds
设置bNeedReubild
并重构Grid
),是否EnableSpatialRebuilds
(决定AddToClassRebuildDenyList
); - 设置
PlayerStateNode
的TargetActorsPerFrame
;
同时可以进行一些额外的参数设置,比如自定义了上层同步频率等 ;
同步频率控制
控制同步频率和总量;
ClassLimit
进行 ReplicateActorListsForConnections_Default
,在 ReplicateActorsForConnection
时,可以针对 RepItem.Actor
的类型进行限制,直接限制该次同步该 ActorClass
可同步的总数量;
1 | bool IsClassLimitExceeded(AActor* Actor) |
Node_PlayerStateFrequencyLimiter
引入 Node_PlayerStateFrequencyLimiter
用于控制 PlayerState
的同步频率,本质是对某一类 Class
(可能是不被 AOI
管理,需要一直同步的),进行同步降频;
维护 TArray<FActorRepListRefView> ReplicationActorLists
,每个 FActorRepListRefView
中记录最多 TargetActorsPerFrame
个 Actor
,在同步时,对 ReplicationActorLists[ Params.ReplicationFrameNum % ReplicationActorLists.Num() ]
进行同步(帧号 % ListNum
);
需要特别注意:当 NotifyAddNetworkActor
时,进行 ReplicationActorLists
的扩张,当 NotifyRemoveNetworkActor
时,也需要补充上空位(可以将最后的 Actor
,补充到被删去的 Actor
的位置);
同时,新增的 Actor
应该在 ForceNetUpdateReplicationActorList
中;
1 | void UReplicationGraphNode_PlayerStateFrequencyLimiter::NotifyAddNetworkActor(const FNewReplicatedActorInfo& ActorInfo) |
其它应用
快速范围查询
显然,ReplicationGraphNode_GridSpatialization2D
将 Actor
在 World
中按照 Cell
进行了划分;这样的结构可以直接借用来做一些基本的通用需求:比如快速查询某个 Location
一定范围内的所有 Pawn
(正常需要对空间额外维护一棵四叉树来处理);
1 | // 获取 Player 视野范围内的 Actor |
1 | // 获取坐标范围内的 Cell 中的 Actor |
1 | // 获取 Position 范围 Radius(cm) 内的 Pawn;如果 bAccurate,则需要严格判定,否则模糊判定在 Cell 内即可; |
参考
Lyra (UE5.0-5.3) ReplicationGraph 部分源码