ReplicationGraph浅析

简介

UnrealEngine 基本的网络同步在进行同步时需要对每个连接判断每个 Actor 是否需要同步,开销较大;

可以通过实现 Replication ,实现一个不同的 ReplicationDriver 来优化性能;

ReplicationWorld 分为多个区域 Grid,把 Actor 根据所在 Grid 进行分类编组,可以快速找出需要同步的 Actor,同时确定哪些区域需要进行同步复制。

数据结构

classDiagram
    class UReplicationGraph
  1. FGlobalActorReplicationInfo :同步的 Actor 信息,包括

SettingsFClassReplicationInfo,每个 Actor Class 对应的同步配置,包括 CullDistanceReplicationPeriodFramePriorityScale 等;

EventsFGlobalActorReplicationEvents Events,记录休眠状态的改变时,从休眠列表中添加或者移除;

FConnectionReplicationActorInfo:对于 Connection 同步的 Actor 信息;

  1. ReplicationGraphNodeGraphNode 基类

  2. ReplicationGraphNode_ActorList:记录同步的 Actors;在 StreamingLevelCollection (以 SubLevelNameKeyList)中记录 SubLevelActor,否则在 ReplicationActorList 中记录。

  3. 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
  1. GridSpatalization2D:将世界划分为 2D 网格,按位置把 Actor 分到不同的 GridCell 中,按空间管理Actor 是否进行同步,每帧更新 GridCell 内的 Actor
  2. GridCellReplicationActorList 缓存着在该 GridCell 中的所有静态 ActorDynamicNodes 里记录动态的 ActorDormancyNode 里记录休眠的Actor
  3. AlwaysRelevant :处理总是发送 Net Updates 给 所有 ConnectionsActors
  4. AlwaysRelevant_ForConnection:处理总是发送 Net Updates 给 特定 ConnectionActors ,一般是同步给 PlayerControllerViewTarget
  5. 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:设置 CulltDistanceReplicationPeriodFrame;注册 Actor 对应的 ClassReplicationInfoGlobalActorReplicationInfoMap

InitGlobalGraphNodes:创建 GridSpatialization2DAlwaysRelevantGraphNode

InitConnectionGraphNodes:创建 AlwaysRelevantForConnectionGraphNode

RouteAddNetworkActorToNodes :生成 Actor 时,添加 NetworkActor,分发 ActorGraphNode

RouteRemoveNetworkActorToNodes :销毁 Actor 时,删除 NetworkActor,通知GraphNode 移除 Actor

Repliate

graph TB
Start(ServerReplicateActors) --> A(PrepareForReplication)
A --> B(GatherActorListsForConnection)
B --> C(ReplicateActorListsForConnections_Default)
C --> D[ReplicateSingleActor]
  1. PrepareForReplication : 调用 GraphNodePrepareForReplication

    对于 GridSpatialization2D,会遍历 Actor,更新其所在的 Grid

    对于 AlwaysRelevant,记录需要同步给所有连接的 Actor

  2. GatherActorListsForConnection : 遍历 Connections 收集 ReplicationActorList

    针对每个 Connection 遍历 GlobalGraphNodesConnectionConnectionGraphNodes,调用其 GatherActorListsForConnection,收集需要同步给这个ConnectionActor

    对于 GridSpatialization2D,通过其 GridCellNode 根据 ActorViewLocation 收集;

    收集的 Actor 默认加到 GatheredReplicationListsForConnection 里的 EActorRepListTypeFlags.Default List

  3. ReplicateActorListsForConnections_Default:进行 Replicate 的同步检测与排序

    GatheredReplicationListsForConnection 里的 Actor,进行检测;

    首先排除 Dormancy、不满足ReplicateFrameActor,然后根据优先级排序(DistanceStarvation逻辑判定Owner & ViewTarget),将结果缓存在 PrioritizedReplicationList 中;

  4. ReplicateSingleActor:对排好序的 PrioritizedReplicationList 调用 ReplicateSingleActor 进行同步。

简单应用

显然,ReplicationGraphNode_GridSpatialization2DActorWorld 中按照 Cell 进行了划分;这样的结构可以直接借用来做一些基本的通用需求:比如快速查询某个 Location 一定范围内的所有 Pawn(正常需要对空间额外维护一棵四叉树来处理);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 获取 Player 视野范围内的 Actor

TArray<FActorBaseRepListType> UReplicationGraphNode_GridSpatialization2D::GetActorsByPlayer(const APawn* Player)
{
if (!IsValid(Player)) return {};

TArray<FActorBaseRepListType> Result;

FVector ClampedViewLoc = Player->GetActorLocation();
if (GridBounds.IsValid)
{
ClampedViewLoc = GridBounds.GetClosestPointTo(ClampedViewLoc);
}
else
{
ClampedViewLoc = ClampedViewLoc.BoundToCube(HALF_WORLD_MAX);
}


int32 CellX = UE::LWC::FloatToIntCastChecked<int32>((ClampedViewLoc.X - SpatialBias.X) / CellSize);
if (CellX < 0)
{
CellX = 0;
}

int32 CellY = UE::LWC::FloatToIntCastChecked<int32>((ClampedViewLoc.Y - SpatialBias.Y) / CellSize);
if (CellY < 0)
{
CellY = 0;
}

if (UReplicationGraphNode_GridCell* CellNode = GetCell(GetGridX(CellX), CellY))
{
CellNode->GetAllActorsInNode_Debugging(Result);
}

return Result;
}
1
2
3
4
5
6
7
8
// 获取坐标范围内的 Cell 中的 Actor

void UReplicationGraphNode_GridSpatialization2D::GetGridActorsByLocationAndRange(FActorBaseRepListType Actor, const FVector& Location3D, float Radius, TArray<UReplicationGraphNode_GridCell*>& OutGatheredNodes)
{
const FActorCellInfo& CellInfo = GetCellInfoForActor(Actor, Location3D, Radius);
GetGridNodesForActor(Actor, CellInfo, OutGatheredNodes);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 获取 Position 范围 Radius(cm) 内的 Pawn;如果 bAccurate,则需要严格判定,否则模糊判定在 Cell 内即可;

TArray<APawn*> AOIUtils::GetAllPawnsInPosGrid(UWorld* World, const FVector& Position, float Radius, bool bAccurate)
{
if (!IsValid(World) || !IsValid(World->GetNetDriver())) return {};

auto RepGraph = World->GetNetDriver()->GetReplicationDriver();
if (!IsValid(RepGraph)) return {};

auto GridNode = RepGraph->GridNode;
if (!IsValid(GridNode)) return {};

float RadiusSquared = Radius * Radius;

TArray<UReplicationGraphNode_GridCell*> OutGatheredNodes;
GridNode->GetGridActorsByLocationAndRange(nullptr, Position, Radius, OutGatheredNodes);

TArray<APawn*> Result;
for (const auto Cell : OutGatheredNodes)
{
TArray<TWeakObjectPtr<AActor>>& LocatedActors = Cell->GetLocationNode();
if (LocatedActors.Num() <= 0) continue;

for (auto WeakActorPtr : LocatedActors)
{
auto Pawn = Cast<APawn>(WeakActorPtr);
if (!IsValid(Pawn)) continue;
if (!bAccurate || FVector::DistSquared2D(Pawn->GetActorLocation(), Position) <= RadiusSquared)
{
Result.Add(Pawn);
}
}
}

return Result;
}

参考

UE ReplicationGraph Document