September 21, 2020

skynet 版的 cache server 引出的一点改进

我们自己做的 cache server 已经工作了很长时间了。上次出问题是在 2 月在家工作期间

这个月又出了一起事故,依旧是 OOM 导致的崩溃。一开始,我百思不得其解,感觉上次已经处理完了所有极限情况,按道理,这是个重 IO 而轻内存的业务,不太可能出现 OOM 的。

通过增加一些 log 以及事后的分析,我才理解了问题。并对应做了修改。

阅读全文 "skynet 版的 cache server 引出的一点改进" »

September 03, 2020

为 skynet 增加并行多请求的功能

skynet 在设计时,就拒绝使用 callback 的形式来实现请求回应模式。我认为,callback 会导致业务中回应的流程和请求的流程割裂到两个执行序上,这不利于实现复杂的业务逻辑。尤其是对异常的支持不好。

所以,在 skynet 中发起请求时,当前执行序是阻塞的,一直等到远端回应,再继续在同一个执行序上延续。

如果依次发起请求,会有不该有的高延迟。因为在同一个执行序上,你必须等待前一个请求回应后,才可以提起下一个请求。而原本这个过程完全可以同时进行。

但是,如果我们想同时发起多个不相关的请求就会比较麻烦。为每个请求安排一个执行序的话,最后汇总所有请求回到一个执行序上又是一个问题。目前,只能用 fork/wait/wakeup 去实现,非常繁琐。

这类需求一直都存在。我一直想找到一个合适的方法来实现这样一类功能:同时对外发起 n 个请求,依回应的次序来处理这些请求,直到所有的请求都回应后,再继续向后延续业务。实现这样的功能在目前的 skynet 框架下并不复杂,难点在于提供怎样的 api 形式给用户使用。

阅读全文 "为 skynet 增加并行多请求的功能" »

August 19, 2020

银河竞逐的设计

银河竞逐 RFTG 是我最喜欢的桌游之一。我认为直到今天,它仍旧是最棒的引擎建造类卡牌游戏。最近,我看了银河竞逐的作者 Tom Lehmann 在 GDC2018 上的演讲 非常有收获,所以写这篇 Blog 分享一下。

策略竞争类的游戏一定要设计多种结束条件。RFTG 的基础版设计了建造出 12+ 张卡结束和分完 12n 个 VP 结束。基础策略就可区分为快速打出一堆小分卡,还是构造一个 VP 引擎。我最近玩得比较多的五扩(Xeno Invasion)还增加了击败(或被击溃)Xeno 结束。

这样可以使得创造出 strategic tension 。

阅读全文 "银河竞逐的设计" »

August 04, 2020

Evidence-based software engineering

《The New C Standard》 是我很喜欢的一本书。因为这本书,我有幸结识了作者 Derek M Jones 。

在 2018 年的时候,他就写 email 给我介绍了他正在写的一本关于软件工程的书。

这是一本非常特别的书。里面有大量的、从公开渠道获得的关于软件工程方面的数据。作者对这些数据做了统计和分析。我之前从未看过如此全篇基于数据去讨论软件工程领域问题的著作(通常在社会学领域更多一些)。

数据摆在那里,或许不同的人,不同的方法会得到不同的解读。这本书不在于给出结论,而是展示了分析过程。让你来从中发现数据透露出的信息。

当时我就很喜欢这本书,陆陆续续读了一些。

最近,这本书《Evidence-based software engineering —— based on the publicly available data》终于快完成了。我很高兴能分享给大家,有兴趣的同学可以在这里下载电子版

如果有同学有兴趣公开自己的一些有关软件工程方面的数据,还可以看看这里

July 21, 2020

裁剪和空间管理

今天想谈谈游戏引擎中 Culling 模块。

当场景中的可渲染对象很多,而当前会被渲染的对象相较甚少的时候,我们通常会启用一个 culling 的过程。Culling 会想办法剔除一些当前不必渲染的对象,避免这些对象提交到 GPU ,让 GPU 承担剔除的过程。这样可以减少 CPU 到 GPU 的带宽。

最基本的 Culling 是用相机的视锥体和对象做一个相交测试,如果对象和视锥体不相交,则可判定它不必渲染;复杂的 Culling 还包括遮挡测试,如果一个对象完全被墙体挡住,那么也不必渲染。这篇只谈前者。

很容易得知,Culling 算法的复杂度上限为 O(n) 。即,我们把场景中的每个对象逐一和视锥体做一次相交判断即可。这里的 n 为场景中元素的个数。

当 n 特别大的时候,通过巧妙地设计数据结构,我们则有可能把复杂度降低。但如何做到呢?

阅读全文 "裁剪和空间管理" »

July 16, 2020

动态字模的管理

在上一篇 blog 中,我谈到了 UI 模块。而 UI 模块中绕不开的一个问题就是怎么实现文字渲染。

和西方文字不同,汉字的数量多达数万。想把所有文字的字模一次性烘培到贴图上未尝不可,但略显浪费。如果游戏只是用有限几种字体倒也不失一种简单明了的方法。但如果使用字体丰富,而多数字体只使用几个汉字,那么就不太妥当了。

我在设计 ejoy2d 的时候实现过一版动态字模的管理。但我觉得略显简陋。最近重新做了一版。

阅读全文 "动态字模的管理" »

July 09, 2020

游戏 UI 模块的选择

在游戏(包括引擎)开发的过程中,谈及 UI 模块,通常所指有二:

  1. 开发工具所用到的 UI 。
  2. 游戏本身所用到的 UI 。

这两者很多时候都是共用的一个模块,比如之前的 Unity 就直接把引擎开发用的 UI 模块扔给开发者开发游戏使用。但很快,开发者就发现,适合工具开发的 UI 模块,在做游戏时就不那么顺手了。所以就有了第三方 UI 插件的流行,以至于最后又倒逼 Unity 官方重新制作游戏 UI 模块。

开发工具面临的需求和游戏场景面临的需求很不一样:

开发工具需要的时候更好的将内部数据以可视化方式呈现出来,供用户浏览和修改,以适应数据驱动的开发。UI 的呈现需要的一致性,风格统一有利于减少学习成本,同时需要清晰的表达复杂的数据结构。有时还需要将内部数据的变化过程同步的动态呈现,给开发者更直观的感受。

游戏 UI 是游戏过程的情感体验的一部分,外观和交互需要根据游戏设计专门化。它往往并不需要表达游戏内部复杂的数据结构,而是将那些数据以额外面对玩家的形式展现出来。玩家通过界面下达的指令也并非直接对数据的修改,而是以指令流的形式传递过去。另外,HUD 也是很大的一个部分,和 UI 对话框在设计上也有很大的不同。

他们两者之间在技术上的共性其实很小,针对这些共性的技术实现可能也只有几百到上千行代码的规模,远少于差异部分需要的代码量。我比较倾向于把这两个东西分开实现。

阅读全文 "游戏 UI 模块的选择" »

June 16, 2020

skynet 并发模型的一点改进思路

skynet 的内核是一个多线程的消息分发器。每个服务有一个消息队列,任何服务都可以向其它任意服务的消息队列投递消息,而每个服务只可以读自己的消息队列,并处理其中的消息。

目前的工作原理是,在任意消息队列不为空的那一刻,将该消息队列关联的服务对象放在一个全局队列中。框架启动固定数量的工作线程,每个工作线程分头从全局队列中获取一个服务对象,并从关联的消息队列中获取若干条消息,顺序调用服务设置的回调函数。如果处理完后消息队列仍不为空,则将服务对象重新放回全局队列。

这样,就完成了尽量多(远超过工作线程数量)的并发服务的调度问题。

我这些年一直在考虑这个模型可否有改进之处。能不能设计得更简单,却还能在简化设计的基础上进一步提高并发性。同时,还可以更好的处理消息队列过载问题。

阅读全文 "skynet 并发模型的一点改进思路" »

June 10, 2020

内存块对象的 Lua 封装

最近给 bgfx 的 lua binding 做了一点改进,因为修改了原有的 api 语义,所以需要做一点记录。

对于 3d 库来说,API 涉及大量的内存块的操作。创建 Buffer ,贴图,shader ,都需要输入一个数据块。大多数数据块是只读的,少部分是需要回写的。对于只读数据块,封装层可以用 lua string 替代,可写的用 userdata 。

bgfx 自己抽象了一个叫做 Memory 的结构,用来统一描述这类内存块对象。按 bgfx 的定义,Memory 的构造由用户决定,而释放通常由 bgfx 管理,而非调用者。

即,用户负责构造出 Memory 对象,将数据拷贝进去,然后再传递给 bgfx 的 api 后就可以撒手不管了。但是,如果你构造出 Memory 对象不传递给 bgfx 则会造成内存泄漏(因为没有任何直接释放它的方法);也不可以将一个 Memory 对象使用多次(传递给 bgfx 多次),因为一旦传给 bgfx ,就失去了对象的控制权。

阅读全文 "内存块对象的 Lua 封装" »

June 05, 2020

层次结构和状态继承

在 blog 上,我写过好几篇关于场景管理模块的树结构的文章。这些也是我这两年在做游戏引擎中对象管理的思考历程。

通常游戏引擎中会把可渲染对象以树结构储存,这是场景管理模块最常见的作法。顺便说一句,GUI 界面也是用类似的方式。但是,我始终认为,从 gameplay 的层面上来看,游戏逻辑需要关注的对象并不需要用层次结构的方式管理。因为,空间结构上的层次很可能发生变化,从而引起关注的对象的层次路径变化。我们最终关注的那些东西不变,但它们在空间中的位置却会经常改变。

我一直在思考的问题是:为什么一定要用树结构组织可渲染对象?树结构到底带来了什么好处?

最直接的好处是,减少矩阵运算的次数。因为,渲染层最终需要对象在整个世界中的位置,而每个被渲染的部件本身却是逐级组合起来的(为了减少数据重复,我们不能因为一个部件换了个位置,就复制一次),部件只会记录相对整体的一个局部空间变换。如果我们平坦的保存没有可渲染部件,势必在计算它最终被渲染到屏幕时的世界矩阵的时候,需要连乘一长串局部矩阵。而组织成树结构,以一定的次序计算,可以大大减少最终矩阵乘法的数量。

但这一点好处,我认为还没有触及本质。表达空间位置的矩阵,仅仅是可渲染对象的一个属性而已。

层次结构的本质是让属性可以用继承的方式优化储存,并方便批量修改。对于每种属性,会定义一种对应的继承方法。

阅读全文 "层次结构和状态继承" »

May 26, 2020

lua hash 函数的一点讨论

最近一段时间,lua 的邮件列表中有好几个主题讨论 hash 表的设计。我读下来受益匪浅。比如 前两周的这个主题 中,有同学主张去掉 lua hash table 中的链表指针,而改成固定步长的冲突链表。具体这里就不展开了,有兴趣的同学可以自己看。

这两天的讨论是围绕 lua 的 hash 函数的,暂时还没有固定链接,我把我的理解和思考记录下来,不一定正确,如有行家发现错误,请不吝赐教。

unsigned int
luaS_hash (const char *str, size_t l, unsigned int seed, size_t step) {
  unsigned int h = seed ^ cast_uint(l);
  for (; l >= step; l -= step)
    h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1]));
  return h;
}

来看这样一个 hash 函数:

阅读全文 "lua hash 函数的一点讨论" »

May 19, 2020

记一次生不如死的经历

前几天晚上 21 点洗澡的时候感觉左腹部疼痛。因为前一天有过一次腹泻,便以为是吹空调着凉还没有好。继续蹲在马桶上又拉了一次肚子,但和之前不同,并没有减少痛苦的感觉,反而加重了。

我转而怀疑是吃错了什么东西引起的肠胃问题,但是努力回忆也想不出什么来。只觉得如果能把腹中的东西排空应该就好了。想了一下就抠了一下喉咙,趴在马桶上将肚子里没消化完的食物吐了出来。可是依然不见好转。

这个时候已经是坐立不安了,找不到一个姿势可以让自己好受一点。

我自觉还是对疼痛忍耐指数挺高的,攀岩的时候手指肚上拉下一块皮也不觉得有什么,可以继续爬;有次拔智齿的时候甚至没打麻药,牙科医生都赞我是看过的病人里最能忍痛的。可这次真的有点受不了。

阅读全文 "记一次生不如死的经历" »

May 08, 2020

《程序员修炼之道》中的一段废稿

我在翻译《程序员修练之道》第二版时,一开始拿到的版本并不是现在出版的这个。所以在中途更换过一个版本,即最后英文版的最终版。前后两个版本都不是原始 markdown ,而是 pdf 格式的。试过几个 diff 软件都无法很好的比对,只好花了不少时间人肉校对了一遍。

虽然两个版本先后只差了几个月,但是增加,调整的段落非常之多,可见作者维护的非常频繁。我当时特别想一观他们的内部写作仓库。记忆比较深刻的是有一大段谈团队的文字被删掉了。不知道原因。但我觉得挺可惜的,这是我很喜欢的一个段落。为了忠于原著,我也从中译版里删除。

下面记录一下这段废稿:

阅读全文 "《程序员修炼之道》中的一段废稿" »

Misc

Categories

Archives

Recent Comments