November 21, 2022

间断储存的字符串

绝大部分的基础数据结构都是定长的,很容易针对优化它们的内存管理。但字符串是一个例外。

内存管理和其它资源管理有明显的不同。管理内存有点像切蛋糕,从整块蛋糕上切下需要的那块,但归还的时候却因为支离破碎难以合并起来满足后续用途。举个极端的例子:如果内存堆有 2G 大小,如果碰巧在正中间分配了几个字节而从未释放,这个堆就被永久的分成了两个不足 1G 的分片。之后再无可能从这个 2G 大小的堆中分配出 1G 的内存块。

改进内存分配算法或许可以减轻内存碎片的危害,但即使是在此方面做了相当多努力的 jemalloc ,其表现也大大低于一般用户的预期。以我的经验,一个 16G 的内存堆,对于长期运行,需要大量反复分配释放内存的程序,通常能做到 10G 左右的峰值有效内存占用就不错了。这里说的有效内存使用,指你调用 malloc 传入的字节数之和。根据应用程序使用内存的方式不同,这个数字会有很大的不同。

60% 的内存使用率其实已经很好了,但还是会有很多人问,我的内存都去哪了?

阅读全文 "间断储存的字符串" »

November 17, 2022

skynet 1.6.0

最近半个月,因为广州防疫政策,我一直居家办公。找了点时间,给 skynet 做了一年一度的 release 。

这次的 1.6.0 版,相比去年的 1.5.0 版,没有太大的变化。主要是平时积累的一些 bugfix 。它所依赖的第三方库,例如 Lua , jemalloc 我都更新到了最新的版本。

值得一提的是,mongo 的 driver 也更新了。因为 mongo 在最新的版本中已经淘汰了旧的 wire protocol ,如果再不更新,就无法连接新版 mongo server 了。具体讨论可以参看 PR #1649 。因为 mongo 自己的设计原因,我们无法给出一个同时兼容新老版本协议的方案。从这点来看,我个人认为 redis 的底层协议设计就稳定的多。 这么多年更迭基本不需要修改。

阅读全文 "skynet 1.6.0" »

November 09, 2022

被干眼症结膜炎困扰的这几年

这几年我一直被干眼症和结膜炎困扰。疫情之前,每次洗澡后就双眼通红。但似乎并无其他影响,也没怎么在意。后来慢慢的,眼睛就容易发干,经常疲劳。严重的时候眼睛几乎睁不开。最受影响是在开车的时候,因为无法休息眼睛,特别难受。到晚上环境光线不足的时候,感觉远处看不清楚。

然后,视力突然断崖式下降。三年时间,每年体检,从 1.2 直落 0.7 左右。而我在 35 岁之前一直是双眼 1.5 的视力。本来 2020 年就想约眼科医生看看的,因为疫情去看五官科不方便就一直拖着,直到 2021 年底才去看了医生。

初步诊断结果是结膜炎、干眼症、视疲劳。另外,验光结果表明我有 150 度近视加 50 度散光。因为并不影响日常生活,便没有配眼镜。而且,我的视力是时好时坏。从办公室眺望珠江对面的楼顶招牌时,有时那些字清晰可见,有时则是重影。我感觉如果以后能解决干眼症和结膜炎的话,视力能稳定一些。

阅读全文 "被干眼症结膜炎困扰的这几年" »

October 17, 2022

电信宽带 ipv6 折腾记

周末在家里折腾 BT 下载,发现 UPnP 无法工作,便去光猫那里看看是什么情况。

我发现现在电信宽带默认已经是由光猫来拨号,家中的路由器是作为一个二级路由在使用,如果想让 UPnP 或 DMZ 工作起来,需要在光猫里设置。

查看光猫设备上印的默认管理员密码登录上去,没有找到太多设置选项。在网上搜了一下,需要一个超级管理员身份才能看到全部设置菜单。一般的说法是打电话问一下装宽带的师傅,但天色已晚,不想麻烦人。转念一想,有这么多家宽带设备要管理,电信应该用的是统一密码,或是有一些简单手段可以拿到这个密码才对。否则建立一整套管理系统成本实在太高了。

阅读全文 "电信宽带 ipv6 折腾记" »

October 08, 2022

引擎 IO 模块的变化和发展

我们游戏引擎的 IO 模块其实一直在修改。最初的版本到现在有四年多了

一直没有定稿的一部分原因是因为我们给引擎设定了一个比较高的需求:引擎本身也是可以从网络自更新的。而更新引擎本身必然依赖 IO ,包括 IO 模块自身。而我们引擎又基于一个多线程版本的 Lua 框架 ltask ,ltask 本身也是需要依赖 IO 模块启动的。这些后续的设计要晚于最初 IO 模块的设计,反复重构也就是必然了。

阅读全文 "引擎 IO 模块的变化和发展" »

September 30, 2022

货物和货车的匹配

我们在制作一款类似 Factorio 以自动化为主题的游戏。在游戏开发中遇到了不少算法上的问题,解决过程都非常有趣。比如之前就写过一篇 流体系统

和 Factorio 体验的最大不同在于,我们不使用传送带做主要物流手段,而是依靠公路网和货车。这种形式并不新,我在 20 多年前的凯撒3 (以及同系列的法老王、宙斯、龙之崛起等)中就玩到过。采用这种物流方式的游戏一直不曾间断,这几年我玩过放逐之城、缺氧、环世界、海狸浮生记也是这种。

这类游戏的共性是需要靠城市资源供养一批工人,然后这些工人干活来供养城市,其中一个重要的工作就是物流。一般受资源产能影响,不会养太多工人,不然很难产生正循环。所以物流所占工作比例并不大。大部分工人的工作(在生产建筑中)从事生产。

阅读全文 "货物和货车的匹配" »

September 07, 2022

多线程串行运行 Lua 虚拟机

ltask 是一个功能类似 skynet 的库。我主要将它用于客户端环境。由于比 skynet 后设计,所以我在它上面实验了许多新想法。

最近碰到一个需求:我们的游戏引擎中嵌入的第三方 C/C++ 模块以 callback 形式提供了若干接口。在把这些模块嵌入 ltask 的服务中运行时,这些 callback 函数是难以使用全部 ltask 的特性。比如,我们的 IO 操作全部在一个独立服务中,引擎读取文件时,很可能是通过网络异步远程加载数据的。这些第三方模块通常没有考虑异步 IO 操作,都是以同步 IO 方式给出一个读文件的 callback 函数让使用者填写。

那么,怎样才能在这个 C callback 中挂起当前任务,等待 IO 的异步完成呢?

阅读全文 "多线程串行运行 Lua 虚拟机" »

September 02, 2022

路网中路径的储存

我们正在制作的游戏中,交通和物流是基于公路网的。公路网其实是以路口为顶点,路为边构成的(无向)图。因为我们有大量的车辆行驶在这个路网中,所以,我需要一个空间高效的方法储存这些车辆的路径。

在大部分情况下,我们的车辆都选择唯一的最短路径,也就是说如果从 A 路口到 B 路口存在一条制定好的路径的话,所有的车都会走这条路。基于这一点,我们可以对多条路径合并储存。

我选择用当前路口 id 和目的地路口 id 做 key ,把下一站路口 id 作为 value ,保存在一张 hash 表中。这样,先用 dijkstra 算法求出起点到终点的最短路线后,再把每一段路线用该结构保存下来即可。

当车辆到达某个路口,只需要用当前路口和它自己的目的地就可以索引到应该往哪个方向开。这个结构很适合保存在 Lua table 中,用元表触发尚未计算过的路径。而且这样一个路径表只是一个 cache ,随时可以清理重建。

阅读全文 "路网中路径的储存" »

August 12, 2022

空间优先的 protobuffer / json 解码器

今天同事给我转了一个帖子 ,说的是 golang 在处理大量并发 json / protobuffer unmarshaling 事务时,可能产生大量的 (10GB) 临时内存,无法及时回收的问题。

我的观点是:如果一个系统的某个模块有可能使用 10G 这个量级的内存,那么它必然是一个核心问题需要专门对待。核心问题应该有核心问题的考量方法,这不是 GC 之错,也并非手动管理内存一条解决之道。即使手工管理内存,也无非是把内存块之管理转嫁到一个你平常不太想关心的“堆”这个数据结构上,期待有人实现了一个通用方案尽可能的帮你解决好。如果随意使用,一样有类似内存碎片无法合并之类的问题,吃掉你额外的内存。如果它存在于你的核心模块,你一样需要谨慎考量。

阅读全文 "空间优先的 protobuffer / json 解码器 " »

August 05, 2022

为 luaecs 增加内置 64bit ID

去年设计 luaecs 时遗留了一个问题:如何引用特定 entity。一开始,我认为可以灵活使用 tag 系统来解决大部分问题;或者用户自己构建一个 64bit ID 的 component ,采用二分查找的方法来引用特定 Entity 。

今年初,我发现我们的自己的使用者和外部使用者都无法避免引用特定 Entity ,所以增加了一个非侵入式的引用方案

最近又围绕它做了一些讨论,让我重新考虑内置一个 Entity 引用的方案。

阅读全文 "为 luaecs 增加内置 64bit ID" »

July 28, 2022

RmlUI 的 style 缓存

我们的游戏引擎的 GameUI 使用的 RmlUI 。我的想法是用成熟的 CSS 来描述 UI 的呈现,借鉴 web 前端开发的方法来制作游戏的 UI 。

但我不想嵌入太复杂的 Web 渲染引擎,而且游戏的需求也会有所不同,所以我选择了轻量的 RmlUI 。同时,为了和游戏的开发语言一致,我们使用 Lua 而不是 javascript 来控制 CSS 。

在使用 RmlUI 的过程中,一开始我们尽量和上游保持一致,修复了不少 Bug ,并合并到了上游。后来发现我们有很多需求和上游不同,需要大刀阔斧的做一些改动,所以就 fork 了一个自己的分支。

最重要的两个改动是:第一,完全实现了 Lua Binding ,因为原有的实现非常低效和复杂,很多接口设计不合理。做大规模的接口变化必须破坏向前兼容,这是我们 fork 分支的主要动机。

第二,废弃了 RmlUI 自己实现的排版模块,换成了 Facebook 维护的 Yoga

阅读全文 "RmlUI 的 style 缓存" »

July 20, 2022

重构数学库

我们引擎中使用的数学库已经修修补补很久了。期间积累了很多想法。最近在对引擎做性能优化,把一些找到的热点系统用 C/C++ 改写,顺便就重构了一下数学库,让它更好的兼顾 Lua API 和 C API 。

上一次对数学库的改进是三年前的事情了。这三年的使用,我们发现,基于一个栈的 DSL 虽然可以减轻 Lua 和 C 之间的通讯成本,但是使用起来并不方便。大部分时候,我们还是倾向更传统的接口:一次数学运算调用一次函数。而高复杂度的数学运算完全可以放在独立的 C 模块中完成。结合 ECS 系统,我们可以在 C side 集中批量处理相同但数量巨大的数学运算。

我们在很早以前就放弃了原来设计的 DSL ,只使用数学库的部分功能。趁这次重构,我打算把这些已经废弃的部分彻底删掉,并重新设计底层的数据结构,让它能更好的适应其核心特性。

阅读全文 "重构数学库" »

July 08, 2022

ECS 系统中 Entity 的生命期管理

我们的游戏场景是由若干场景节点构成的,每个场景节点是一个 Entity 的 Component 。而一个复杂的场景可以在编辑器中生成一个预制件 Prefab,像搭建乐高积木那样堆砌已经做好的部件。关于预制件的设计,之前有过两篇 blog 讨论。分别是:游戏引擎中预制件的设计预制件和对象集的管理

就目前的使用经验来看,几乎所有游戏中的 Entity 都是从 Prefab 实例化得来的。一个 Prefab 会实例化出 n 个 Entity ,但这 n 个 Entity 的生命期管理却很麻烦。

最自然的想法是:所有的 Entity 都必须是场景树上的节点(拥有场景组件),当我们删除一个场景节点时,它所有的子孙都一起移除。

阅读全文 "ECS 系统中 Entity 的生命期管理" »

Misc

Categories

Archives

Recent Comments