瞧不上 C++ 和 D 语言,国外程序员将 5.8 万行代码迁移到 Jai 语言,到底图什么? | 程序师 - 程序员、编程语言、软件开发、编程技术-迪欧吧

作者 | Simon van Bernem

译者 | 核子可乐

策划 | 冬梅

本文中,我将向大家分享本身把游戏开发成果移植到 jai 语言的经历。我的游戏之前主要是用 D 和部分 C_++ 编写的,总代码有 58620 行(不包罗库)。其实这事我已经筹划了很久,还专门记录下了最初的期望、移植过程和最终结果。下面,就请大家随我一道回顾这段历程。

为什么要移植

很多伴侣可能好奇,为什么会有人要大费周章把这么些代码移植成别的一种语言?把项目做完,之后再用新语言不好吗?我当然有本身的考虑,主要基于以下几点:

现状让我的日常工作非常痛苦

我有称手的系统移植工具,所以移植过程应该会比力顺利

对我来说,Jai 似乎比 C++ 或者 D 更有吸引力

为什么不消 C++

网上关于 C++ 缺点的讨论已经很多,所以这里不再赘述。简而言之,C++ 几十年发展下来积累了太多错误决策,并且永远都摆脱不掉了。它的标准库堪称灾难,代码几乎不成能在交给他人后顺利运行。并且不知道为什么,C++ 每次新增功能都有坑。所以 C++ 的使用感受确实很差,不时刻刻在熬煎着我,并且这个项目的发展标的目的在我看来也有问题。总之,我想尽快离开 C++ 生态系统。

为什么不消 D

我的游戏开发之旅始于 2019 年,当时我已经感受到 C++ 的问题了,但并不知道哪种语言更好。于是我选择了 D,理由如下:它很像 C++,只是去掉了不好的部分。但很遗憾,这只是我的一厢情愿,D 在很多方面都跟 C++ 一个德行。

必需承认,D 跟 C++ 比确实有一些优势,比如更强大的元编程、无需标头、没有未初始化的值等。但很遗憾,这些优点根本就抵消不掉现实缺陷。

我在 Windows 上的两种 D 编译器(dmd 和 ldc2)之间反复横跳了四年,最后发现至少在 Windows 平台上,D 语言的状态也就是个业余项目、往好听了说也是极不成熟的水平。很难相信这是种已经发展了 20 多年的语言。截至目前,我不建议任何人在 Windows 上用 D 开发严肃项目,甚至还不如继续用 C++。从我亲身经历的问题来看,目前最大的麻烦是 Windows 上的调试信息完全 损坏:

在 90% 的情况下,this 指针会缺失或出错

函数堆栈上的变换经常部分 / 完全丢失或出错

有时会报告变量值错误,但却没有任何其他可见的问题

静态 foreach 扩展处理不妥,令调试工具无所适从

minxins(相当于 D 中的宏)生成的调试信息会令调试工具找不到正确文件(只能按步进行反汇编)

在 Visual Studio 中将指令指针移回上一行,经常导致程序在执行下一条指令时崩溃在 D Language Code Clulb 讨论版上,我看到“DMD 以往曾经出过很多关于调试信息的问题”,但号称正在处理中。可很遗憾,ldc2 也根本没好到哪去。并且除了调试之外,元编程这边也有不少缺点和问题:

不同的编译器阶段会诡异地彼此影响,导致元编程出现不测,引发误导性错误

ldc2 的编译速度非常慢,但我别无选择,因为 dmd 有 bug

D 提供所谓 betterC 模式,其中禁用垃圾收集;但在使用此模式时,标准库不编译并且元编程也会受到严重阻碍

说明文档缺失

还有其他几十个更具体的问题

总而言之:虽然 D 在某些方面确实比 C++ 强点儿,但其他地方的问题反而更多,导致使用体验痛苦万分。其中最大的问题就是糟糕的调试信息和极具破坏力的垃圾收集机制。我承认,我只是想把 D 当成改进版的 C++ 来用,而 D 的开发者并不认同这种理解。所以 D 不适合我,并且我现在根本就不敢信任它的调试器。

为什么选择 Jai

种种遭遇,把我引向了 Jai。这是 Jonathan Blow 从 2014 年开始开发的一种语言,并且直到 2019 年 12 月才向编译器敞开大门。目前封闭测试仍在进行中,我也是约两个月前受邀体验的一员。Jai 的诞生源自 Blow 对 C++ 的失望。并且跟 D 不同,Jai 确实朝着我所认可的、能够对 C++ 做出有意义改进的标的目的前进。在我看来,Jai 的最大优势有二:更快的编译速度,还有以不受限制的编译时执行进行元编程。

值得注意的是,这里讨论的可不是编译速度提高 20%、或者元编程功能选项略有增加之类不痛不痒的小改进。Jai 的编译速度提高了 10 到 100 倍,并且能在编译期间执行任何操作。特别是与编译时编译器 API 相结合的元编程,已经带来了具有深远意义的影响:例如消除对构建系统的需求,也摆脱了对复杂的非启发式自定义检查的依赖。除此之外,Jai 还对 C++ 做出了其他改进,例如更好的默认值、更简单的语法、更实用的标准库、命名函数参数、上下文、using 等。这就让我有了信心,打算通过从 D 移植到 Jai 让本身的游戏获得以下收益:

将编译时间从现在的 60 秒摆布降低到 5 秒以内,最好能达到 1 秒上下

调试器终于能用了

用 Jai 代码替代了 build-script

用元编程引入自定义编译检查,借此捕捉更多错误

用更简单的命令式代码替代复杂的元编程代码

提供一系列语法改进,减少了代码中的噪声

删除了以往使用多种语言时无法避免的重复部分##

为什么不考虑其他语言

的确,我明确不想用的只有 D 和 C++,而略感兴趣的是 Jai……那为什么不试试别的语言呢?

最无法回避的选项应该就是 Rust 了。它风头正劲、社区活跃,但我还是感觉 Rust 在设计权衡方面有点问题。支持 Rust 的开发者们似乎有种“内存安全是第一要务”的集体心态。没错,很多问题都源自内存安全问题,所以我大体能够理解这样的判断。但也有很多对于安全和质量要求没那么极端的软件需求。比如,我相信如果 C 和 C++(基本就是公认的「最不安全」语言)能够去掉零终止字符串、默认初始化值和适当的指针 + 长度类型,那就足以用边界检查取代 90% 的指针算法、解决临时内存办理的需求了。别的,我觉得很多人在追求“安全”代码时,其实是忽略掉了软件漏洞层出不穷的根本原因:文化上对于复杂性的容忍,甚至是鼓励 14。总之,我不肯意忍受 Rust 那漫长的编译时间,也不太认同它的文化定位。跟 Rust 不同,jai 就很关注如何控制复杂度,这一点更符合我本身的文化判断。

还有其他一些人气稍逊的选项,例如 Zig。我只能说它们可能也有巨大的潜力,但我不太相信这些会是正确的选择。不是好或者不好,只是没那么强的吸引力。

如何移植

刚开始,我还在心里给本身鼓劲、祈祷移植过程能顺利完成。之所以比力乐观,是因为我在游戏中设置了两套有助于移植的系统:

游戏会将游戏会话的输入(HID、加载文件、网络等)记录到文件内并稍后重播。在重播时,记录的输入输出确定性能让游戏循环达到完全相同的状态,精确无误。

游戏在执行过程中的各个点位上,都会对游戏状态进行哈希处理,并将相应的哈希值保留在不同文件傍边。这样在重播时,文件内容即可用于检查重播是否跟原始执行完全匹配。

这两项功能间有一些细微不同,但对整体运行影响不大。依托这些功能,我的移植计划如下:

将一小段代码由 D 或 C++ 复制到 jai,而后编译。

调用新的 jai 代码,替代旧有 D 或 C++ 代码。

重播录制的游戏会话。

如果重播发生不合,则说明移植引入了 bug,修复此 bug。

如果重播无不合,则移植成功,继续下一步。

这种方法的关键在于:

是不是所有引入的 bug 都会导致游戏状态发生显著不合?

代码能否以较小的增量进行移植,以便易于知晓 bug 存在、找到 bug?

第一个问题,取决于状态哈希覆盖到多少代码。有些代码需要知晓游戏是否正在重播,这些部分的内部行为会有所不同,因此无法得到有意义的哈希值。例如,写入文件功能会在重播时丢弃一切数据,因此如果移植后的写入文件中出现了 bug,就会被哈希过程注意到。幸运的是,大部分代码并不属于这一类。最初的哈希在代码库中极少被用到,但比来我开始将其扩展到插入动态数组的过程,例如记录动态数组的大小和容量。

如此一来,当有 bug 导致动态数组的插件会改变游戏逻辑时,问题就会被及时注意到。因为我代码中的几乎所有功能都是靠动态数组实现的,所以即使是在任何庞大而复杂的数学算法傍边,每一点微波的行为变化都能引起注意。

第二个问题则属于经典编程问题:你的代码解耦程度到底有多高?这一点非常有趣,因为我得把代码库里的所有偶发复杂性元素找出来。首先就是模板函数:它们无法直接移植,因为函数定义和调用站点必需在同一编译器之内,才能让模板正常起效——除非对模板进行手动实例化。我的代码库里有不少模板化代码,但它们跟容器和序列化没什么紧密关联,所以我觉得这应该不会惹出太大的麻烦。

继续推进

下面来点更直观的统计数据和图片吧。先来看我这代码库的当前状态:

总体来说,这里有 45701 行 D 代码和 12919 行 C++ 代码,总共 58620 行。编译时间如下:

在调试模式下,ldc2 的编译过程大概需要 3 分钟,最高占用 8 GB 内存。这时候如果打开浏览器,那我这台 16 GB 内存的笔记本电脑就会进入满负荷运行。发布模式内存占用量更大,为 11.5 GB。

如果一切顺利进行,那在两张图中,一切现有色块都应该会被新色块取代。能成功吗……

最后,我想用数字来解释移植的收益,特别是我当初的预期错得有多离谱:

我预计整个过程需要 160 个小时(每周 40 小时,共耗时一个月)。但我这个估计差得太多了,很可能根本用不上一个月的时间。

我希望编译时间能从 1 分钟摆布下降到最多 5 秒,能到 1 秒上下最好。至于移植后的最终结果如何,我将持续保持更新。

AI前线

未经允许不得转载:迪欧吧_技术交流_资源分享_热点资讯_免费VPS空间 » 瞧不上 C++ 和 D 语言,国外程序员将 5.8 万行代码迁移到 Jai 语言,到底图什么? | 程序师 - 程序员、编程语言、软件开发、编程技术-迪欧吧