《游戏引擎实现原理》附录 A helio 内核 API 与模块速查
《游戏引擎实现原理》卷一附录 A:把全书出现的 helio 内核类型与关键 API 按 core / ecs / jobs / reflect / platform 五个模块整理成速查表,每个条目给出签名、一句话说明与所在章节,并附模块依赖方向、典型组合用法与按章反查索引,方便边读边查、随时回跳。
category
《游戏引擎实现原理》卷一附录 A:把全书出现的 helio 内核类型与关键 API 按 core / ecs / jobs / reflect / platform 五个模块整理成速查表,每个条目给出签名、一句话说明与所在章节,并附模块依赖方向、典型组合用法与按章反查索引,方便边读边查、随时回跳。
卷一《引擎内核》的收尾:逐章回顾 18 章各自解决了什么问题、它们如何拼成一台能跑的引擎,给出从零到能跑的施工顺序与各阶段验收标准,并说明进入卷二《渲染引擎》的路线——GPU 抽象、渲染图、相机与资源,以及内核与渲染之间那道接缝的三个具体例子。
用装好的 helio 内核跑出《微光岛》第一个能走动的方块:一份 120 帧预录输入序列驱动方块在 16×16 岛屿平面上按固定步长移动,连跑两遍,最终位置与轨迹校验和逐位相同;正文给出真实的最终坐标与断言结果,并解释那个 9.7e-17 的浮点残渣从何而来。
把前面 17 章的零件——arena 帧内存、slot map 句柄、ECS 世界、固定步长主循环、子系统注册表——装配成一个可运行的 helio 内核 Kernel 类,真跑 4 帧,打印每帧子系统的执行顺序与真实耗时,并说清装配顺序为什么必须是拓扑序。
存档格式会变:今天的 hp 明天可能改名 health。本节给 helio 内核的 schema 迁移框架:存档里带 $v 版本号,加载时沿迁移链逐级升到当前版本,缺环直接抛错、绝不静默降级。实跑一份 v1 存档升到 v3,字段从 {hp, name, gold} 变成 {health, name, mana, level}。
朴素 JSON.stringify 遇到父子互指的图会抛 TypeError,报错原文是「Converting circular structure to JSON」。本节给出 helio 内核里带 id 表的对象图序列化器:把对象引用替换成整数 id,循环变成两条指向同一 id 的边,两趟重建后引用同一性保持不变;句柄部分复用第 6 章的世代句柄。
第 16 章的类型描述符在这一节接上序列化器:同一张字段表既能写出可读 JSON,也能写出紧凑二进制。给出 helio 内核里可编译的 TypeRegistry、json-serde 与 binary-serde 三个完整文件与 vitest 用例,并实测同一个 Player 对象 JSON 93 字节、二进制 37 字节。
不用装饰器、不依赖 emitDecoratorMetadata:用一个构建期脚本扫描源码里带标记的类型定义,生成一份显式注册表 .ts 文件,再由运行时 import 进注册表。本节真跑一次扫描,把扫过的文件数、抽出的类型数与属性数、生成的代码行数都写进正文。
从零实现 helio 内核的 TypeRegistry:用 defineType 按字段类型累加偏移,产出名字、类型、偏移、默认值四件套的属性描述符;注册顺序被保留,遍历稳定可复现。本节给出完整可编译实现与 vitest 用例,并真跑一次 registry.get('Vec3').properties,把真实输出贴进正文。
编辑器要列出属性、存档要读写字段、脚本要绑定对象——这三件事都要求引擎在运行时知道「类型里到底有什么」。本节讲清这份类型信息为什么必须有,交代 Node 26 只做类型剥离、装饰器元数据走不通这个硬约束,并把它讲成一个主动的设计选择:显式注册表。
任务被派发到别的线程之后,帧边界就成了同步点:用 Atomics 屏障让所有线程在每帧末尾对齐,用双缓冲让逻辑线程与渲染线程各写各的、只在帧边界交换一次引用。本节用 worker_threads + Atomics 跑一个最小同步 demo,并标出命令行不可复现的部分。
每帧重建一张任务图:把本帧真实出现的任务(可见区块数每帧都在变)拼成 DAG,跑一次拓扑分层,得到本帧的执行序与并行批次。本节给出可编译的 FrameScheduler,并真实跑出 5 帧的执行顺序、每帧并行度,以及「重建」本身随任务数增长的开销。
把帧内任务的先后关系表达成一张有向无环图,再用 Kahn 算法做分层拓扑排序,输出一个合法执行序与每层可并行的任务集合。本节给出 helio 内核可编译的 TaskGraph 完整实现与 vitest 用例,并用一个真实的环断言「循环依赖被检测到并报错」。
14.2 的共享队列在核多时会撞上锁瓶颈。本节实现 Chase-Lev 双端队列:每个 worker 一条 deque,拥有者从 bottom 端 LIFO 取,窃取者从 top 端 FIFO 偷,只剩最后一个元素时用 Atomics.compareExchange 定胜负。并用 8:1 的不均衡负载,实测静态分片与 work stealing 的 makespan 与处理数分布。
从零写一个基于 worker_threads 的线程池:固定 4 个 worker、一条用 SharedArrayBuffer 装的共享环形任务队列,用 Atomics.compareExchange 做锁、Atomics.wait 挂起空闲 worker。给出 JobSystem 实现与 vitest 用例,实测 1000 个任务串行与并行的差距,以及任务太碎反而慢近 11 倍的反例。
前 13 章的所有更新都挤在一个核上跑。本节先讲清楚为什么 JS 也需要一套 Job System:Node 的 worker_threads 与浏览器 Worker 差在哪、SharedArrayBuffer 与 Atomics 在 Node 里怎么用、在浏览器里为什么必须加 COOP/COEP 跨源隔离头,并用一段 CPU 密集任务实测主线程串行与 4 worker 并行的差距。
每个系统声明读哪些、写哪些数据槽,两个系统存在写-读或写-写重叠即冲突。用最长路径分层把系统排进互不冲突的组:组内可并行、组间同步。本节给出 helio 内核可编译的调度器,跑出《微光岛》十个系统的真实分组结果,并对照粗粒度与细粒度数据槽对并行度的影响。
改动一个节点后只把它的子树标脏,updateDirty 只重算被标脏的节点,updateAll 则无条件全量重算。本节给出 helio 内核可编译的 TransformHierarchy(扁平 TypedArray + 拓扑序),并实测 4 叉树上改叶子时「全量重算 vs 脏标记」的耗时差与被重算的节点数。
实体用位掩码表示拥有哪些组件,查询用 all / none 两个掩码描述条件;结果按查询键缓存,任一组件增删都把全局 revision 加一使缓存整体失效。本节给出 helio 内核可编译的 QueryEngine,并实测有缓存与无缓存跑同一批查询的耗时差、命中与失效次数。
实现 helio 的曲线模块:二次/三次贝塞尔、Catmull-Rom 样条与常用缓动函数。用采样点表说清贝塞尔不穿过中间控制点、Catmull-Rom 穿过却会过冲(实测会冲到 -0.0625),并给出四种缓动函数在同一组 t 上的取值对照。