这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
《游戏引擎实现原理》2.1 帧、时间与帧预算
帧预算决定了引擎能否稳住 60Hz。本节把一帧拆成逻辑、提交、GPU 与呈现四个阶段,用 text 时间线标出 16.67ms 的预算边界,再从零实现一个可注入假时钟的 FrameTimer,在 Node 里真跑出 300 帧的耗时分布、百分位与 FPS。
posts
这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
帧预算决定了引擎能否稳住 60Hz。本节把一帧拆成逻辑、提交、GPU 与呈现四个阶段,用 text 时间线标出 16.67ms 的预算边界,再从零实现一个可注入假时钟的 FrameTimer,在 Node 里真跑出 300 帧的耗时分布、百分位与 FPS。
一个引擎最少要有哪些零件?helio 的答案是六个:主循环、内存、句柄、场景/实体、渲染接口、资源表。本节给出这六个零件的最小实现,并用一段 30 行的启动代码在 Node 里真跑出「第 1 帧到第 3 帧」的日志,再用 vitest 断言帧数。
本卷只做「从零实现 + 每节跑出真实产物」,渲染原理、引擎选型、子系统视角这些别人讲透的话题一律外链。本节用一张三列分工表把边界钉死,把这张表编码成可运行的路由函数,并给出 18 章的阅读路线、L1/L2/L3 代码分层怎么用,全部附可复现断言。
从零实现引擎的第一件事是划边界。helio 分成平台、内核、渲染、子系统、游戏五层,依赖只能向下。本节用一张 ASCII 分层图对照 Unity、Unreal、Godot、Bevy,并用 TypeScript 类型把「内核层不得依赖渲染层」钉死在编译期。
对库作者而言,类型层的破坏性变更比运行时更难察觉——代码照跑,用户却编译不过。本节先给出一份类型破坏性变更清单,再用 api-extractor 的 API 报告与类型测试把公开契约锁进 CI,最后梳理 changesets 驱动的发布流程、dist-tag 与弃用周期,让每个版本号都名副其实。
同一个包如何同时被 ESM 与 CJS 消费者使用,又让两套类型各自对得上?本节先讲清 type 字段与文件扩展名如何共同决定模块格式,再给出 tsup 双入口构建与 exports 双条件的完整配置,最后剖析双包最危险的副作用——双包风险导致 instanceof 失效与状态分裂,并给出收敛策略。
讲清包如何向消费者交付类型:用 declaration 与 emitDeclarationOnly 让 tsc 产出 .d.ts 与 declarationMap,用 api-extractor 把散落声明压成单文件;再拆解 package.json 的 types 字段与 exports 的 types 条件,解释它为何必须排在同条件首位。读完你能为包设计出不报错的类型入口。
构建变慢和包变大常常是同一批依赖的两面。本节先给出构建耗时的定位方法,用 --debug 与插件计时找出慢在哪个环节;再做包体积的可视化分析与体积预算;最后落到四条治理手段——依赖替换、按需引入、压缩器与 target 取舍、长期缓存下的 chunk 命名,并给出把体积门禁写进 CI 的可执行脚本。
首屏体积的敌人不是代码总量,而是「用不到的代码被提前加载」。本节先讲动态 import() 在类型层面如何标注模块边界,再拆解 tree-shaking 真正生效的三个前提——ESM 静态语法、sideEffects 声明、压缩器的死代码消除;最后给出 manualChunks 分包策略与 React.lazy、路由懒加载的落地写法,并列出五类让优化静默失效的场景。
Vite 已成为 TS 前端项目的默认构建工具,但它对 TypeScript 的态度常被误解。本节先讲清开发阶段的「按需编译」与构建阶段的「Rollup 打包」为何是两套引擎;再划清 tsconfig 与 Vite 的职责边界;最后落到 vite.config.ts 的类型写法、import.meta.env 与 import.meta.glob 的类型化,以及六个高频坑。
拆开 @PersistenceContext 注入的那个 EntityManager:它为什么是共享代理、如何绑定到当前事务,持久化上下文作为一级缓存怎样工作,实体的四种状态如何转换,以及 persist / merge / detach / refresh 各自的真实语义。
围绕借阅服务的封面上传,讲清 MultipartFile 与 spring.servlet.multipart.* 配置项、Tomcat 容器级的 max-part-count 等真实限制、延迟解析与异常处理位置、流式读写避免 getBytes() 撑爆堆、路径穿越与后缀白名单防护、中文文件名下载编码,以及为什么文件不该进数据库 BLOB。
一个事务调用另一个带事务的方法时,两者是什么关系?本节用「借阅失败但审计日志必须留存」的场景,逐个讲清七种传播行为,重点对比 REQUIRED 与 REQUIRES_NEW,再给出四种隔离级别与脏读、不可重复读、幻读的对照表,并说明实际工作中该怎么选。
对比 asyncio.TaskGroup 与 anyio 4.15.1 的结构化并发,讲清异常组如何打包与传播、asyncio.timeout 的 reschedule 语义,以及 PYTHONASYNCIODEBUG 慢回调警告等调试手段,附实测输出。
从 CPython 源码出发讲清 Task 与 Future 的关系:Future 的三态机、cancel() 把 CancelledError 注入到哪个挂起点、cancelling() 计数与 uncancel() 的语义、shield 的保护边界,并附取消时序实测。
用 selectors 手写一个最小事件循环,再对照 CPython 的 BaseEventLoop._run_once,讲清就绪队列与定时器队列的双队列设计、call_soon 与 call_later 的调度优先级,以及本轮新注册回调为何延后,并附实测执行顺序。
给命令行工具加一层窗口:讲清 Tkinter 的 Tk/Frame/布局管理器与事件循环、PySide6 的信号槽与 MVC 思路,以及 GUI 与业务逻辑解耦的关键原则,并用假视图把控制器逻辑真跑通测试(本机无 GUI 环境,窗口代码为伪代码)。
把 14.1 的工具真正交到用户手里:讲清 console_scripts 入口点、用标准库 zipapp 真打包真运行单文件可执行、踩过相对导入与包根的经典陷阱、把依赖 vendor 进归档,并说明 PyInstaller 的取舍(本机未装,仅配置与流程)。
把脚本能力包成真正的命令行工具:用 Click 8.5.0 的装饰器、命令组与共享上下文声明参数与子命令,再用 Typer 0.27.3 的类型标注重写同一个工具,真跑帮助、统计、词频与退出码,并用 CliRunner 把 CLI 层测透,完成工程化的分层与测试。
前两节让 context 能取消,但取消信号从哪来?本节把信号处理接上:用 signal.Notify 与 signal.NotifyContext 捕获 Ctrl-C 和 SIGTERM,讲清「中断」与「排空」两种停止语义,加二次信号强杀与超时兜底,最后给出 TaskAPI 完整的 main。