DreamFXLang
生成与往返

构建流水线

一次 build 到底发生了什么:发现、顺序、schema 探测、写入、Niagara 编译、存盘、盖戳,以及为什么"没改就跳过"。

一次构建把源码变成资产,中间经过六个阶段 —— 诊断码的首位数字就是它抛出的阶段:

阶段干什么码段
1驱动与文件 I/ODFX1xxx
2词法与语法DFX2xxx
3声明与文档结构DFX3xxx
4值、类型与表达式DFX4xxx
5生成与资产写入DFX5xxx
6Niagara 编译DFX6xxx

另外两族不在这条主线上:DFX7xxx溯源、漂移与 lintDFX8xxx反编译器

顺序:模块 → emitter → 系统

build -All(以及编辑器里的 Rebuild DFX)按这个顺序建,理由很实在:一个 .dfm 和调用它的 .dfs 同时排队时,模块必须先存在,否则系统解析不到它。

编辑器侧的 watcher 用同一套顺序 —— 它把改动的文件盖进一个待建队列,防抖 ticker 排空这个队列。

一个文件的旅程

解析

词法 + 语法 → 一份文档。位置信息(文件、行、列)在这里就挂上了,一路带到最后一条诊断上, 所以失败的构建能弹出一个直接跳到出错列的 Open in VSCode 链接。

解析模块名与 schema

模块短名在 ModulePaths 上解析(L4)。名字解析出来之后,输入签名是探测出来的,不是猜的: 把模块加进一个临时系统,读它在那个栈里真正暴露的输入。

这是必要的:资产层面的 schema 看不见内联 edit condition,也看不见静态开关揭示出来的输入。 dfx schema <Module> -Stack <Stack> 跑的就是这同一趟探测。

写入

通过 Niagara 外部编辑 API 建系统、emitter、栈、模块与输入。写入顺序是有语义的: 静态开关必须先写,它揭示的输入才存在(见 .dfs)。

Niagara 编译

写完之后系统要编译。编译不干净就是 DFX6xxx —— 包括引擎自己的栈问题 (缺依赖、未绑定参数),DreamFX 把它们转述成带位置的诊断。

存盘 + 盖戳

SavePackage 写包,同时在资产上盖溯源戳:源码 hash、生成器版本、模块版本 GUID。 存盘失败是 DFX5030,是一条构建错误 —— 不是一次 fatal,编辑器不会因为「文件被另一个程序占用」而崩掉。

没改就跳过

第二次构建同一个没动过的文件,报的是 0 built, 1 up to date:资产上戳着的 hash 与源码的 hash 一致,就没有事可做。

想要
强制重建,无视 hash-Force
建但不写包(只看能不能过)-NoSave
只做静态检查,不碰资产lint
只对照资产检查,不写任何东西verify

编辑器里构建 vs 命令行构建

同一条管线,两个入口。差别只有一个,而且是硬的:

编辑器开着时不要跑写包的命令。 两个进程存同一批包,谁后存谁赢,两边都不吭声。 build / corpus / mirror-diff / decompile-all 都属于写包。

还有一条同样是硬的:重建一个系统之前先让它的实例消失 —— 见日常循环

性能:为什么全树构建不是线性的

一次全树构建里,绝大部分时间不在解析也不在写入,而在 Niagara 编译。单个资产上量到过 84% 的时间花在那里,每个资产平均触发约 4 次系统编译。

几个可调项,默认值都已经是快的那一侧,它们存在主要是为了 A/B 和逃生:

开关作用
-Window=<n>流水线深度:允许多少个系统同时处在「已请求编译、尚未 finalize」之间。1 退回完全串行
-NoWriteScope每次写入都重建编辑上下文(旧行为,慢得多)
-RebuildOnStructural每次结构性写入后丢弃编辑上下文(旧行为)
-RebuildOnSwitch每次写静态开关后丢弃编辑上下文(旧行为)
-RebuildPerAdd每加一个模块都付一次引擎的栈刷新,而不是每个栈批量刷一次

这些开关不是给日常用的。它们的价值是:一台机器上、一份二进制里,把"快的做法"和"慢的做法" 各跑一遍,于是「这次优化到底有没有用」是量出来的而不是读代码读出来的。

相关

本页目录