June 26, 2017

浅谈《守望先锋》中的 ECS 构架

今天读了一篇 《守望先锋》架构设计与网络同步 。这是根据 GDC 2017 上的演讲 Overwatch Gameplay Architecture and Netcode 视频翻译而来的,所以并没有原文。由于是个一小时的演讲,不可能讲得面面俱到,所以理解起来有些困难,我反复读了三遍,然后把英文视频找来(订阅 GDC Vault 可以看,有版权)看了一遍,大致理解了 ECS 这个框架。写这篇 Blog 记录一下我对 ECS 的理解,结合我自己这些年做游戏开发的经验,可能并非等价于原演讲中的思想。

Entity Component System (ECS) 是一个 gameplay 层面的框架,它是建立在渲染引擎、物理引擎之上的,主要解决的问题是如何建立一个模型来处理游戏对象 (Game Object) 的更新操作。

传统的很多游戏引擎是基于面向对象来设计的,游戏中的东西都是对象,每个对象有一个叫做 Update 的方法,框架遍历所有的对象,依次调用其 Update 方法。有些引擎甚至定义了多种 Update 方法,在同一帧的不同时机去调用。

这么做其实是有极大的缺陷的,我相信很多做过游戏开发的程序都会有这种体会。因为游戏对象其实是由很多部分聚合而成,引擎的功能模块很多,不同的模块关注的部分往往互不相关。比如渲染模块并不关心网络连接、游戏业务处理不关心玩家的名字、用的什么模型。从自然意义上说,把游戏对象的属性聚合在一起成为一个对象是很自然的事情,对于这个对象的生命期管理也是最合理的方式。但对于不同的业务模块来说,针对聚合在一起的对象做处理,把处理方法绑定在对象身上就不那么自然了。这会导致模块的内聚性很差、模块间也会出现不必要的耦合。

我觉得守望先锋之所以要设计一个新的框架来解决这个问题,是因为他们面对的问题复杂度可能到了一个更高的程度:比如如何用预测技术做更准确的网络同步。网络同步只关心很少的对象属性,没必要在设计同步模块时牵扯过多不必要的东西。为了准确,需要让客户端和服务器跑同一套代码,而服务器并不需要做显示,所以要比较容易的去掉显示系统;客户端和服务器也不完全是同样的逻辑,需要共享一部分系统,而在另一部分上根据分别实现……

阅读全文 "浅谈《守望先锋》中的 ECS 构架" »

June 19, 2017

skynet 网络线程的一点优化

skynet 是一个注重并行业务处理的框架,设计它的初衷是可以充分利用多核 CPU 更好的处理那些比较消耗 CPU 的,天然可以并行的业务,比如网络游戏。网络 I/O 并不是优化重点。

基于这个设计动机,skynet 的网络层使用单线程实现。因为我认为,即使是代码量稍大一些的单线程程序,也会比代码量较小的多线程程序更容易理解,出 bug 的机会也更少。而且经典的网络服务程序,如 redis nginx 并没有因为单线程处理网络 IO 而变现得不堪,反而有不错的口碑。

所以,skynet 的 epoll 循环并不像 erlang 那样,只关注读写事件,而让每个 actor 自己去处理真正的 socket 读写。那样固然可以获得更高的网络处理能力,但势必让网络 API (由存在多个工作线程里的多个 actor 分别调用)依赖锁来保证正确性。这是我不太希望看到的。目前的设计是,所有网络请求,都通过把指令写到一个进程内的 pipe ,串行化到网络处理线程,依次处理,然后再把结果投递到 skynet 的其它服务中。

这个做法未必最好,但也恰恰能用,一般网络游戏服务器,根据我们的实际项目数据,在其它业务处理的 CPU 占用到极限时,单台机器网络带宽不大会超过 30MB 左右的上下行带宽。一个核每秒处理 60MB 的数据是绰绰有余的。

阅读全文 "skynet 网络线程的一点优化" »

June 18, 2017

支持部分共享的树结构

因为图形引擎中的对象天然适合用树 (n-ary tree) 表达,所以它在图形引擎中被广泛使用。通常,子节点会继承父节点的一些状态,比如变换矩阵,在渲染或更新的时候,可以通过先序遍历逐级相乘。

在 PC 内存充裕的条件下,我们通常不必考虑树结构储存的开销,所以大多数图形引擎通常会为每个渲染对象独立生成一个树结构,比如 Unity 中的 GameObject 就是这么一个东西。在 Ejoy2D 中,从节约内存的角度考虑,把树节点上的一部分可共享的状态信息(不变的矩阵、纹理坐标等)移到了资源数据块中,但是树结构的拓扑关系还是在新创建出每个 sprite 时复制了一份。

随着游戏制作的工艺提高,而大众使用的移动设备的内存增长有限,这部分的开销慢慢变得显著。在我们正在开发的几个项目中,渲染对象本身,而不算图形资源(贴图、模型等),也占据了以 M 为单位计算的可用内存。比如在一个项目中,某个巨型对象由多达 2000+ 个节点构成,创建速度和内存开销都成为一个不可忽视的问题。由于是于自研引擎,所以同事尝试过一些优化的尝试。

图形引擎处理的大多数的树结构对象,其实仅在编辑环境会对树的拓扑关系进行调整:增加、移动、复制节点。而运行环境下,树结构本身几乎是不变的。一般的树结构的实现是用指针来相互引用,编辑好的资源是树结构的序列化结果,而创建过程是一个反序列化过程,在内存中重建整棵树,并用指针重新建立节点间的联系。比如在 Unity 中,就把这种编辑好的树对象叫做 prefab 预制件。如果预制件比较复杂,加载预制件和从预制件中构建对象的时间成本都不算低。

一个优化方法是去掉运行时内存中的指针,改用 id 或资源数据块中的偏移量,这样,预制件可以直接从资源文件中成块读入,创建内存对象时也只需要用指针引用即可。可以节省大量在内存中重建的时间。Ejoy2D 现在就是这么做的。去掉指针的额外好处是在 64 位系统下,可以从 8 字节的引用开销减少到 4 字节(或更少)。Ejoy2D 当初做此修改,为一个项目一举减少了 10M+ 的运行期内存占用。而且资源文件可以在暂时不用的时候移出内存,等需要的时候再加载回来,而不用担心数据的内存地址发生了变化。

阅读全文 "支持部分共享的树结构" »

June 06, 2017

sharedata 的替代品:datasheet

skynet 中有一个用来在多个服务间共享数据表的模块,叫做 sharedata

它的设计动机是:当我们有很多服务时,如果需要共享一份只读的数据表,把数据表分别在每个服务类加载会很浪费内存。而且,一旦数据表有热更新的需求,分散在多个服务中的数据更新起来会比较麻烦。

我试过很多方案来达成这个需求,一直都不是特别满意。目前的 sharedata 模块是用的最久、使用项目最多的一个。虽然它基本可用,但使用它的同学也提出了一些问题,我对这些问题做了一些思考。

阅读全文 "sharedata 的替代品:datasheet" »

June 02, 2017

skynet 模块命名空间调整

前段时间有同学抱怨说 skynet 下提供的 lua 模块都没有名字空间,平坦的命名,容易和自己项目开发的模块命名冲突。虽然自己项目开发的模块可以单独给一个名字空间,但混杂在一起使用还是不美观。

我考虑了几天,决定在 skynet 1.1 版本中把大部分的模块都加上 skynet 前缀。

阅读全文 "skynet 模块命名空间调整" »

May 27, 2017

epoll 的一个设计问题

问题的起因是 skynet 上的一个 issue ,大概是说 socket 线程陷入了无限循环,有个 fd 不断的产生新的消息,由于这条消息既不是 EPOLLIN 也不是 EPOLLOUT ,导致了 socket 线程不断地调用 epoll_wait 占满了 cpu 。

我在自己的机器上暂时无法重现问题,从分析上看,这个制造问题的 fd 是 0 ,也就是 stdin ,猜想和重定向有关系。

skynet 当初并没有处理 EPOLLERR 的情况(在 kqueue 中似乎没有对应的东西),这个我今天的 patch 补上了,不过应该并不能彻底解决问题。

我做了个简单的测试,如果强行 close fd 0 ,而在 close 前不把 fd 0 从 epoll 中移除,的确会造成一个不再存在的 fd (0) 不断地制造 EPOLLIN 消息(和 issue 中提到的不同,不是 EPOLLERR)。而且我也再也没有机会修复它。因为 fd 0 被关闭,所以无法在出现这种情况后从 epoll 移除,也无法读它(内核中的那个文件对象),消息也就不能停止。

阅读全文 "epoll 的一个设计问题" »

May 18, 2017

Lua 表的差异同步

最近同事碰到的一个需求:需要频繁把一组数据在 skynet 中跨网络传递,而这组数据实际变化并不频繁,所以做了大量重复的序列化和传输工作。

更具体一点说,他在 skynet 中设计了一个网关节点,这个网关服务可以负责把一条消息广播给一组客户端,每个客户端由内部的一个 uuid 串识别,而每条消息都附带有客户端 uuid 列表。而实际上这些 uuid 列表组有大量的重复。每条广播消息都重复打包了列表组,且列表组有大量重复信息。

一开始我想的方法是专门针对这个需求设计一组协议,给发送过的数据组编上 id ,然后在发送方和接收方都根据 id 压缩通讯数据。即,第一次发送时,发送全量信息,之后再根据数据变化发送差异;如果完全没有变化,则只需要发送 id 。

之后我想,能不能设计一种较为通用的差异同步方法,可以在跨节点传递数据组的时候,避免将相同的数据重复传输,而采用差异同步的方法同步对象。

阅读全文 "Lua 表的差异同步" »

Misc

Categories

Archives

Recent Comments