[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) |
网络相关性
一般的 ReplicationGraph 在启用时,会改变 NetRelevancy,AActor::IsNetRelevantFor 不再被使用;
但是在业务需求中很有可能需要用到这种机制,可以在合适的地方将这个机制添加回去;
- 首先在
ClassReplicationInfo添加bEnableNetRelevancyCheck字段用于判断是否启用该机制:
1 | static FORCEINLINE_DEBUGGABLE bool IsActorRelevantToConnection( const AActor* Actor, const TArray<FNetViewer>& ConnectionViewers ) |
- 在同步
Actor时进行判定:UReplicationGraph::ReplicateActorListsForConnections_Default:
1 | void UReplicationGraph::ReplicateActorListsForConnections_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewerArray& Viewers) |
- 在发送
Multicast RPC时进行判定:UReplicationGraph::ProcessRemoteFunction:
1 | bool UReplicationGraph::ProcessRemoteFunction(class AActor* Actor, UFunction* Function, void* Parameters, FOutParmRec* OutParms, FFrame* Stack, class UObject* SubObject ) |
其它应用
快速范围查询
显然,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 部分源码



