March 25, 2021

服务的创建和退出问题

TL;DR 系统中每个和系统活得一样久的单元(服务),都应该提供一个关闭接口,而不是释放接口。关闭只做必要操作,不必释放资源,不必和其它单元协调。整个系统退出时,只需要命令所有单元关闭,然后让世界戛然而止。


最近在和同事一起完善 ltask 。这个项目可以看成是对 skynet 的一个回顾。我们打算把它用在客户端引擎中。

在 ltask 中,原来在 skynet 里用 C 实现的 timer network 等线程都被移到了 lua 中实现。它们被成为 exclusive service ,会独占一个操作系统线程,但之外的部分和普通服务并没有太大区别。

因为这样一些基础服务也挪到了 lua 中实现,以前在 skynet 中天然而成的层级被模糊了。在 skynet 中,这些独占线程都是先于其它服务启动,而晚于大多数服务退出。它们的启动和退出流程都在 C 代码中固定下来。

阅读全文 "服务的创建和退出问题" »

March 22, 2021

fbx 到 gltf 转换问题

我们的游戏引擎采用的资源格式是 gltf 2.0

gltf 在这几年发展很迅猛,我认为是 3d 文件格式中标准化做的最好的一个。可惜,游戏行业中,美术创作人员常用的 max maya 等工具对其支持还有瑕疵。Autodesk 在 2019 年作为 contributor 成员加入了 Khronos 组织,在 max maya 这些 Autodesk 工具中看到官方的 gltf 支持应该不会等太久。来自官方的消息 ,‎在 2020 的 3 月底,gltf 加入官方支持已经处于 Under Review 状态。希望今年内可以看到。

阅读全文 "fbx 到 gltf 转换问题" »

February 27, 2021

skynet 处理 TCP 连接半关闭问题

TCP 连接是双工的,既可以上行数据,又可以下行数据。连接断开时,两侧通道也是分别关闭的。

从 API 层面看,如果 read 返回 0 ,则说明上行数据已经关闭,后续不再会有数据进来。但此时,下行通道未必关闭,也就是说对端还可能期待收取数据。

同样,如果 write 返回 -1 ,错误是 EPIPE ,则表示下行通道已经关闭,不应再发送数据。但上行通道未必关闭,之后的 read 还可能收到数据。

用 shutdown 指令可以主动关闭单个通道(上行或下行)。

阅读全文 "skynet 处理 TCP 连接半关闭问题" »

February 07, 2021

ltask :Lua 的多任务库

ltask 是我前两周实现的一个 lua 的多任务库

这个项目复用了我之前的一个类似项目的名字 。目的是一样的,但是我做了全新的设计。所以我干脆将以前的仓库移除,以同样的名字创建了新的仓库。

和之前的版本设计不同。在消息通讯机制上,这个更接近 skynet 的模型,但它是一个库而不是一个框架。调度模块是按前段时间的想法实现的,不过只做了一个初步的模型,细节上还有一些工作待完善。

阅读全文 "ltask :Lua 的多任务库" »

January 22, 2021

关于 skynet 调度器的一点想法(续)

这篇是继续上次的一些想法

我最近想重新做一个新的基于消息管道的 N:M 多任务调度器。主要用在我们的客户端上。算是解决在使用 skynet 多年来总结出来的一些问题。

首先,我想改变 skynet 服务直接将待发消息投递到对方消息队列的做法。发出消息会让服务挂起,等待投递。

然后,我设计了一个叫做调度器的模块,用于把带发消息投递到对应的接收管道中。调度器是发送消息的消费者,接收管道的唯一生产者;而每个服务是它自己的发送消息的唯一生产者,接收管道的唯一消费者。这样没有竞争,当消息队列是定长的时候,也无需使用锁(但引入了消息队列满的新状态)。

阅读全文 "关于 skynet 调度器的一点想法(续)" »

January 12, 2021

把 skynet 的原子操作换成了 stdatomic

stdatomic 已经是 C11 的标准,并且成为了 C++ 标准的一部分。msvc 也将会支持 stdatomic 。在 skynet 项目开始的时候,还没有这个可以用,所以我采用的是更早一点的 gcc sync 系列的扩展

我想,用 stdatomic 来实现原子操作,会有利于 skynet 未来的发展。上周花了点时间熟悉这套 api 并在 skynet 中实现。同时保留了之前的实现,如果编译器定义了 __STD_NO_ATOMICS__ 就会切换回老版本。

阅读全文 "把 skynet 的原子操作换成了 stdatomic " »

January 05, 2021

预运算地图寻路的一种方法

上次谈到服务器上寻路算法的优化。文末提到,其实,针对具体需求,我们实际上可以预运算所有的路径,把结果持久化在文件中,运行时用 O(1) 的时间就可以查询到任意路径。

对于半径为 N 的地图网格,选取任意两点作为起点和终点,一共有 N^4 数量级的路径,直接按起点和终点来缓存路径显然不现实。

但实际上,除非整张地图是个复杂的迷宫,大部分路径是重复的。这和人实际上行路是一样的。如果你从家中的卧室去到办公室的工位上,整条路径和你从客厅出发是高度重叠的;仅有出家门前的那一小段略有不同。

在服务器上寻路,通常解决的是 NPC 的小范围内移动绕开障碍(从卧室到家门)的问题,远距离移动的路线(从家到公司)应当在另一个层面去解决。

基于网格寻路无论是用 A star 还是基于图的 dijkstra 算法都非常简单,这里不谈算法,只谈结果的持久化问题。

为了解决起点和终点有 N^2 种可能性,这个数量级太大的问题,我们可以适当的将地图划分成若干区域。如果每个区域都是一个内部没有任何障碍物的凸多边形,那么,当我们的起点和终点都落在同一个区域时,连接两点的直线就是最短路径。

阅读全文 "预运算地图寻路的一种方法" »

December 17, 2020

内存的惰性初始化

这两天和同事讨论一个问题,我写了个小玩意。

事情起因是,我们公司上海的工作室的一个 MMO 项目做服务器压力测试。谈及优化,涉及到服务器中使用的 C 模块。他们把同一套 C++ 加上 namespace 编译了很多份,供多个服务使用。我很好奇,一般来说,Lua 的 C 模块是可以供多个 vm 共用的,并不需要实际链接很多份。仔细探究发现,原来这个代码中用到了一些全局对象(singleton 模式)。

我本能的觉得全局对象的设计中透着糟糕的味道,在逐个分析每个全局对象的必要性时,发现了一个有趣的东西:寻路模块。

阅读全文 "内存的惰性初始化" »

December 04, 2020

粒子系统中的材质组织

粒子系统中,势必会引入多种材质。要么按材质分为不同的管理器对象,要么把所有粒子片放在一个管理器下,但增加材质的属性。

如果是前者,即使粒子的其它属性都有共性,也无法一起处理;而后者,则涉及材质分类的问题。

我们不大可能在渲染阶段无视粒子的材质属性,每个粒子片都单独向渲染器提交材质。因为无论是面片粒子,还是模型粒子,都应该批量提交粒子的空间结构数据,然后一次渲染。如果粒子是面片,那么就应该把一组粒子的顶点信息组织在同一个顶点 buffer 中;如果粒子是模型,就应该把每个个体的空间矩阵组织在 Instance Buffer 中。

如果材质属性只是一个 id 或材质对象指针,作为一个属性关联在粒子对象上的话,不同材质的粒子是无序的,怎样的数据结构可以方便管理呢?

阅读全文 "粒子系统中的材质组织" »

November 27, 2020

粒子管理器的 C++ 封装

这篇接着上一篇 粒子系统的设计

TL;DR 在花了一整个晚上用 C++ 完成了这一块的功能后,我陷入了自我怀疑中。到底花这么多精力做这么一小块功能有意义么?强调类型安全无非是为了减少与之关联的代码的缺陷,提高质量;但代码不那么浅显易懂却降低了质量。

我们用 C 实现了一个基于 ECS 结构的粒子系统的管理器,代码 psystem_manager.h 在这里

先来回顾一下设计:在这个粒子系统中,我期望把粒子对象的不同属性分开管理。

即:传统的面向对象的数据结构中,一个对象 particle 可以有很多属性 a,b,c 。通常是用一个结构体(或类)静态定义出来的,这些属性也可以看作是 a b c 组件,它们构成了粒子对象。而在 ECS 结构中,我们在每个时间点,并非去处理一个对象的多个属性,而是处理同一个属性的多个对象。所以,我们最好按属性分类将多个对象的同一属性聚合起来,而不是按对象,把同一对象的不同属性聚合在一起。

这是因为,在处理单个属性时,往往并不关心别的属性。比如,我们在递减生命期,处理生命期结束的对象时,关心的仅仅是生命期这个属性;在处理粒子受到的重力或其它力的影响时,我们只关心当前的加速度和速度;在计算粒子的空间位置时,只关心上一次的位置和瞬间速度;而在渲染时候,无论是生命期、加速度、速度,这些均不关心。

当数据按属性聚合,代码在批量处理数据时,连续内存对 cache 友好,即使属性只有一个字节,也不会因为对齐问题浪费内存。同一属性的数据尺寸完全相同,处理起来更简单。而且粒子对象相互不受影响,我们只是把同一个操作作用在很多组数据上,次序不敏感。非常适合并行处理。

更重要的是,不同类型的粒子需要自由的根据需要组合属性和行为。有的粒子有物理信息参与刚体碰撞运算,有的则只需要显示不需要这个信息;有的粒子有颜色信息,有的不需要有;有的粒子是一个面片,有的却是一个模型,拥有不同的材质。这导致粒子对象包含的信息量是不同的。及时拥有同一属性,作用在上面的行为也可能不同:例如同样是物理形状信息,可能用于刚体碰撞,改变运动轨迹,也可能只是为了触发一下碰撞事件。

在传统的面向对象的方式中,常用多态(C++ 的虚函数)来实现,或者有大量的 if else switch case 。

如果能按组件和行为聚合,那么就能减少大量的分支。每个粒子的功能组合(打开某个特性关闭某个特性)也方便在运行时决定,而不用生成大量的静态类。

阅读全文 "粒子管理器的 C++ 封装" »

November 19, 2020

粒子系统的设计

这几天在重构引擎中的粒子系统。之前用 lua 做了个原型,这次用 C/C++ 重新实现一次。目前还是基于 CPU 的粒子系统,今后有必要再实现基于 GPU 的版本。

去年写过一篇 blog 也是谈粒子系统的 。 思路大致类似,但这次在数据结构的细节上做了一些专门的设计,有觉得还有点意思,值得写写。

首先,粒子对象本身就是一个集合了多种数据的数据块。我限制了同时最多 64K 个粒子片,这些粒子对象可以放在一块连续内存中,并且可以用 16bit 的 id 进行索引。

阅读全文 "粒子系统的设计" »

November 08, 2020

Lua binding 的一些方法

这几天在给 RmlUi 这个库写 Lua binding 。

这个库原本有一个官方的 lua binding ,但是新特性 Data Model 却没有实现。作者坦承自己对 Lua 不是特别熟悉,这个新特性也在开发中,暂时没想好该怎么做,所以只完成了 C++ 接口,Lua 接口留待这方面跟懂的人来做。

我觉得这个新特性有点意思,打算帮助这个项目实现 Lua 接口。在实现的过程中,发现原版的 Lua binding 做的过于复杂,且那些复杂度完全没有必要。所以打算自己全部重新实现一套。

阅读全文 "Lua binding 的一些方法" »

October 20, 2020

skynet 1.4.0

又是一年过去了,skynet 目前保持着一年一个发布版的开发进度。skynet 1.4.0 发布版将于近期冻结。

这次的主要更新是将 Lua 更新到了 5.4.2 (尚未发布,但 github 仓库中的版本号已经到了 5.4.2 )。可能会让 skynet 的许多项目享受到分代 gc 的好处。如果使用大量 agent 服务的模式,将会降低整体的内存峰值开销(GC 更加及时)。lua 5.4 中 table 的内存开销也比之前的版本要小,运行性能也有所提升。

升级到 lua 5.4 基本不需要修改过往的 Lua 代码。C 库需要重新编译,但基本不需要修改。但如果可以改用新版的 lua_newuserdatanv 取代 lua_newuserdata 会更好。

skynet 依然提供了针对多 vm 共享 proto 的补丁。和以前一样,这是一个可选项,可以自行编译官方版本的 lua 。

阅读全文 "skynet 1.4.0" »

Misc

Categories

Archives

Recent Comments