UE4 ReplicationGraph分析

UE4 ReplicationGraph分析

老版本网络系统

总体思路

  • 所有Actor都会添加到网络列表中,每次更新的时候都是从这个Actor列表中遍历,根据不同的条件,确定每个链接的客户端需要同步的Actor列表,然后同步数据

流程图

老流程图.png

添加

  • 生成Actor的时候直接调用AddNetworkActor函数,添加进workObjectlist中
void UWorld::AddNetworkActor( AActor\* Actor )

{

    if ( Actor == nullptr )

    {

        return;

    }



    if ( Actor->IsPendingKill() ) 

    {

        return;

    }



    if ( !ContainsLevel(Actor->GetLevel()) )

    {

        return;

    }



    ForEachNetDriver(GEngine, this, [Actor](UNetDriver\* const Driver)

    {

        if (Driver != nullptr)

        {

            // Special case the demo net driver, since actors currently only have one associated NetDriverName.

            Driver->GetNetworkObjectList().FindOrAdd(Actor, Driver->NetDriverName);

        }

    });

}

详细同步过程

  • 每个服务器的tick调用同步函数
// 总入口函数

int32 UNetDriver::ServerReplicateActors(float DeltaSeconds)

{

    // 确定当前的连接客户端

    ServerReplicateActors\_PrepConnections



    //确定同步列表(性能瓶颈点)

    ServerReplicateActors\_BuildConsiderList



    //对Actor列表进行优先级排序(性能瓶颈点)

    ServerReplicateActors\_PrioritizeActors    



    //处理排序过后的Actor列表(这里会进行最终的同步操作)

    ServerReplicateActors\_ProcessPrioritizedActors

}

优化方式

  • 裁剪距离降低
  • 降低更新频率

核心问题

  • 每次同步的计算量是ConnectionList*NetObjList,Actor列表庞大,比对计算的时候性能消耗是主要瓶颈点
  • 优化困难,上述优化都是有损优化,降低了客户

新版本网络系统

总体思路

  • 根据类型和状态对Actor进行节点划分,每次针对当前Connection只检查所在Grid内对象的信息来大大降低整个Replication的计算量,节省CPU时间

流程图

新流程图.png

结构图

同步图表.jpg
  • UReplicationGraph: 同步图表,NetDriver的网络分发入口
  • GridSpatialization2D:按照2D空间划分的同步节点
  • Playerstate:与状态有关的节点,比如限制更新频率的对象
  • Always Relevant(All): 与所有的链接都有相关性
  • Tear off:类似于OwnerRelevant,只同步给主客户端,不同步给模拟客户端
  • Always Relevant(Connection):只与某一个链接具有相关性,比如走进某一个区域点亮一盏灯,只对这个链接有作用
  • Always Relevant(team):和队友都有关的链接行,比如复活次数等等
  • Static:静态同步对象(可破坏物件)
  • Dynamic:可以移动的同步对象
  • Dormant: 休眠对象
  • Streaming:和关卡有关的对象,只有关卡加载了才会进行同步

添加

  • 生成Actor的时候通过调用各个类型节点的AddNetworkActor,分发Actor的存储
void UNetDriver::AddNetworkActor(AActor\* Actor)

{

    GetNetworkObjectList().FindOrAdd(Actor, NetDriverName);

    if (ReplicationDriver)

    {

        ReplicationDriver->AddNetworkActor(Actor);

    }

}
  • Gird 2D结构
2D结构.png
- GridSpatialization2D 会按照cellsize划分为多个网格
- AddActo
    - 存在
        - 根据种类添加到对应的列表中,静态,动态,休眠等等
        - 静态动态中有划分为是否是属于streamingLevel中等
    - 不存在网格
        - 创建网格(和裁剪距离,Cellsize都有关系),重复网格存在的添加步骤
- 更新
    - 超过现有的网格边界
        - 创建新的网格
        - 刷新网格中的Actorlist
    - 根据动态Actor位置刷新网格中的actorList

详细同步过程

  • 服务器tick调用网络同步,直接分发到同步图表类中,在这个类里面做同步的Actor同步前的处理操作,但是最终的Actor同步过程还是走原有的逻辑
// 总入口函数

int32 UNetDriver::ServerReplicateActors(float DeltaSeconds)

{

    // 转发到同步图表类中

    ReplicationDriver->ServerReplicateActors(DeltaSeconds);

    int32 UReplicationGraph::ServerReplicateActors(float DeltaSeconds)

    {

        //各个节点的前期同步准备工作(对于空间划分的Grid主要是刷新各个网格中的Actor列表)

        for (UReplicationGraphNode\* Node : PrepareForReplicationNodes)

        {

            Node->PrepareForReplication();

        }

        // 处理各个链接的客户端

        for (UNetReplicationGraphConnection\* ConnectionManager: Connections)

        {

            //公共节点中需要的Actor列表

            for (UReplicationGraphNode\* Node : GlobalGraphNodes)

            {

                Node->GatherActorListsForConnection(Parameters);

            }

            //相关联的节点中的Actor列表

            for (UReplicationGraphNode\* Node : ConnectionManager->ConnectionGraphNodes)

            {

                Node->GatherActorListsForConnection(Parameters);

            }

            //真正Actor进行同步的地方,剩下的和老版本网络流程一致

            ReplicateSingleActo

        }

    }

}

新版本网络使用步骤

  • 自己重写ReplicationGraph
  • 添加自己的网格节点划分类型
  • 两种启动方式
- DefaultEngine.ini 配置自己重写的同步图表类
    - [/Script/OnlineSubsystemUtils.IpNetDriver]

ReplicationDriverClassName="/Script/ProjectName.ClassName"

- 创建委托 
        UReplicationDriver::CreateReplicationDriverDelegate().BindLambda([](UNetDriver\* ForNetDriver, const FURL& URL, UWorld\* World) -> UReplicationDriver\*

            {

                return NewObject<UMyReplicationDriverClass>(GetTransientPackage());

            });

优化方式

  • 通过详细的网格划分减少每次需要进行计算的Actor数量,提升性能

核心问题

  • 对于小场景来说用处不大,很有可能所有的Actor都在同一个网格中
  • 每次同步前都需要刷新网格中acto

新老版本对比(数据相关的需要自己搭建场景获取)

| Type | 标准网络 | Graph网络 |

|---------- |:-------------:|:-------------:|

| ActorList | 全量存储 | 按类型划分存储|

| 自定义网络同步 | 不支持 | 支持|

| 优化方式 | 有损效果,降频,降低裁剪距离 | 细致的网格种类划分|

| 性能 | 性能较差,随着客户端和actor的增长,呈现几何增长的性能消耗 | 性能和网格的划分密切相关(fortnite官方数据此处性能消耗降低75%)|

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏吉浦迅科技

DAY20:阅读Surface Memory

24420
来自专栏为数不多的Android技巧

ASCII Art:使用纯文本流程图

我们使用纯文本写代码,有了Markdown又可以使用纯文本写文档,那么对于更直观的信息表达方式——图片,能不能使用纯文本描述呢?

37820
来自专栏Aloys的开发之路

一个比较全面的java随机数据生成工具包

        最近,由于一个项目的原因需要使用一些随机数据做测试,于是写了一个随机数据生成工具,ExtraRanom。可以看成是Java官方Random类的扩...

30190
来自专栏java系列博客

UML——序列图

21440
来自专栏HansBug's Lab

关于使用lazytag的线段树两种查询方式的比较研究

说到线段树,想来大家并不陌生——最基本的思路就是将其规划成块,然后只要每次修改时维护一下即可。 但是尤其是涉及到区间修改时,lazytag的使用往往能够对于程序...

33170
来自专栏程序员的诗和远方

30分钟QUnit入门教程

30分钟让你了解Javascript单元测试框架QUnit,并能在程序中使用。 QUnit是什么 QUnit是一个强大,易用的JavaScript单元测试框架,...

57090
来自专栏吉浦迅科技

TensorFlow版本号升至1.0,正式版即将到来

2015年11月份,谷歌宣布开源了深度学习框架TensorFlow,一年之后,TensorFlow就已经成长为了GitHub上最受欢迎的深度学习框架,尽管那时候...

38590
来自专栏十月梦想

php代码之网站显示安全运行时间代码

上述就可实现网站计时功能,结合数组函数实现,后续可是使用js获取倒计时,时时显示!

12920
来自专栏奇点大数据

【干货】Pytorch中的DataLoader的相关记录

DataLoader简单介绍 DataLoader是Pytorch中用来处理模型输入数据的一个工具类。通过使用DataLoader,我们可以方便地对数据进行...

1.7K60
来自专栏李海辰的专栏

Unity 引擎资源管理代码分析 ( 1 )

目前网络上已经有很多介绍 Unity 资源管理机制、和 API 使用方法的文章,但少有文章从 Unity源码层面对其实现进行深度解析。作为一名喜欢打破砂锅璺到底...

1.3K10

扫码关注云+社区

领取腾讯云代金券