DreamFXLang
插件参考Tools

增量构建

增量构建

增量构建

DreamFX » 增量构建

过去保存 .dfs 会清空每个堆栈的系统并重建全部内容,因此一次保存的开销与整个系统的大小成正比,而不是与本次编辑成比例。增量构建会将新的构建计划与上次构建记录的计划进行比较,只写入差异部分。

编辑器默认对源监视器由保存触发的重建开启 — 项目设置 ▸ 插件 ▸ DreamFX ▸ 工作区 ▸ 保存时增量构建
命令行默认关闭;-Incremental 可选择启用
始终完整重建 DFX、从源重建 以及批量积压的 立即重建它们 链接
存储位置资产的包元数据中,键为 DreamFX.PlanDigest,紧邻来源标记(DreamFX.SourceHash 等)

在编辑器中,对一个 1230 行的系统(NS_Magic_Explosion)从保存到重建完成的耗时测量如下:

保存MoonEngine 构建原版预编译 UE 5.8
完整重建(此前)17 秒56 秒
更改一个值(五处)1.0–1.3 秒1.7 秒
同上,编辑器启动后的首批保存~3 秒~4 秒
启用一个模块(重建两个堆栈)2.5–2.7 秒完整重建

值保存的大部分耗时现在来自 Niagara 自身对系统的重新编译,而快速迭代的值写入仍会触发这一过程。启动后的首批保存还会付出模块探测的代价,之后运行中的编辑器会保留这些结果。

在原版预编译引擎上,增量路径涵盖值编辑以及发射器的添加或移除;任何其他结构变更——需要重建的堆栈或发射器——都会执行完整重建。重建单个堆栈必须先丢弃它所移除模块的快速迭代常量,否则重新添加的模块会保留其先前节点遗留的常量——即隐藏输入的旧值,而完整重建则会是模块默认值。引擎自身针对该情况的清理在那里并未导出;该引擎过去启动的每次编辑编译作为副作用完成了它,而构建现在会阻止这些编译。MoonEngine 构建会调用该清理并重建单个堆栈。

在原版引擎上,完整重建本身就比上表所示的更快:构建窗口会保持一个编译请求挂起,使引擎在构建自身的编译之前不启动任何编译;静态开关写入只刷新其所在模块;一批模块添加只刷新其堆栈一次;被清空的堆栈会一步从图中移除;模块编辑会尽可能保留编辑上下文以便原地刷新;每次模块探测共享同一个上下文。对 NS_Magic_Explosion 的无头完整重建从 110 秒降至约 32 秒(含引擎启动);其系统视图模型从数百个降至 163 个。每个步骤都有一个开关可恢复旧行为,用于单二进制对比以及作为应急出口:-RebuildOnSwitch、-RebuildPerAdd 和 -ClearPerModule(在 dfx.ps1 build 上名称相同)。

工作原理

每次成功的系统构建都会存储一个 计划摘要:扁平化构建计划的哈希树——.dfe 合并、Parent / inherits 继承和模块版本均已解析——再加上构建为每个模块赋予的节点名称,以及资产构建安全性事实的哈希。该摘要位于包元数据中、来源标记旁边,因此会随资产一起保存。

下一次增量构建会计算新计划,与存储的摘要进行比较并写入差异:

变更内容构建执行的操作
一个参数值——字面量、枚举、链接或 hlsl 表达式写入该值
参数动态输入链的结构——动态输入被添加、替换或移除重置该参数并作为组重写
源不再设置的字面量或枚举参数,或诸如曲线之类的数据接口值清除并重建该单个堆栈
堆栈的模块、其顺序或版本、as 名称、disabled 标志、Set Parameters 条目或静态开关清除并重建该单个堆栈
发射器设置、渲染器、事件处理器、模拟阶段、参数默认值、用户参数、系统设置重新应用该部分,且仅在其发生变更时
添加了发射器、其父级(inherits)或 SimTarget 发生变更,或其堆栈与另一个正在重建的堆栈共享模块整重建该发射器
移除了发射器移除它

移除字面量参数会重建其堆栈,而不是重置该参数,因为完整重建正是如此:它会重新添加该模块,而未清理的快速迭代常量会保留该参数最后所具有的值。而重置则会将模块默认值放在那里,构建便不再等同于完整重建——构建安全门也会因值丢失而拒绝它。数据接口值同理,因为写入内存中已有接口的值并不总能到达资产。

共享模块的情况与引擎节点名称有关:当两个堆栈使用同一模块且其中一个被重建时,引擎会重新编号另一个堆栈输入所存储的节点名称,因此改为重建整个发射器,以避免出现不匹配。

每种编辑的开销

编辑写入范围开销取决于
更改字面量或表达式该参数编辑本身——在上述系统上约一秒,其中大部分是重新编译
在动态输入链内部编辑以使其结构改变该参数,作为组该链
移除某个设置字面量参数的行;更改曲线或其他数据接口一个堆栈该堆栈
添加、移除、重排序或更换版本模块;切换 disabled;更改 Set Parameters 条目或静态开关一个堆栈该堆栈
添加发射器;更改 inherits 或 SimTarget一个发射器该发射器
重排序发射器;回退列表 上的任何内容整个系统完整重建

编译

增量构建会向 Niagara 请求一次普通的、非强制编译。Niagara 只重新编译写入所到达图表的脚本;仅落在快速迭代参数上的值写入通常不会到达任何脚本,这就是一次单值编辑可以以 nothing to recompile 结束的原因。完整重建则仍然强制编译。

构建随后会像完整构建一样验证编译状态——参见 无头 Niagara 编译——并在任何内容过期时强制重新编译。

何时回退到完整重建

输出日志会说明原因,形式为 full rebuild (<reason>)。原因包括:

原因含义
资源是新的没有可比较的内容
资源未携带已存储的计划它由较旧版本的 DreamFX 构建,或是被采纳的
已存储的计划具有另一种格式、生成器版本或写入路径摘要来自不同的 DreamFX 代次,或来自另一个后端——MoonEngine 快速编辑与标准引擎的写入方式不同
资源自上次构建以来发生了变化构建安全事实不再与上次构建所记录的内容匹配:资源在 DreamFX 之外被编辑过(在 Niagara 编辑器中手动编辑、从源代码控制还原)
发射器被重新排序发射器顺序是计划标识的一部分
系统堆栈或发射器的结构发生变化,本引擎会对此完全重建仅限标准预构建引擎,见上文
数据接口按位置引用发射器或渲染器,而发射器或渲染器集合发生了变化自引用令牌将指向错误的目标
增量写入本身失败完整重建在同一次构建中运行,并报告其自身的诊断信息

回退是一次正确的构建,只是较慢。下一次成功构建会存储新的摘要,因此回退之后的构建会再次变为增量构建。

[!NOTE] “资源已更改”回退是安全网,而不是麻烦。已存储的计划描述的是上次构建留下的资源状态。一旦资源在 DreamFX 之外被编辑过,计划就不再描述当前存在的内容,因此基于它计算出的差异不可信,只有完整重建——即参照——才是可信的。

等价性保证

增量构建生成的资源与对同一文本进行完整重建所生成的资源相同。 完整重建仍是参照,增量路径会针对它进行测试:

  • 一个自动化测试会将同一编辑历史构建两次——一次增量地逐次编辑构建,一次完整构建——并比较两个资源的事实(DescribeSystemFacts,构建安全门和 asset-diff 所读取的事实)。

该测试为 DreamFX.Incremental.Equivalence;DreamFX.Incremental.Coverage 运行相同的历史,并还会断言哪些编辑是以增量方式应用而非回退。三个夹具系统(若干发射器,其中一个模块由两个堆栈共享;一个事件源和接收器;一个带模拟阶段的 GPU 发射器)通过约九十次变更进行编辑,每次变更都是 Tests/DreamFXIncrementalEquivalenceTest.cpp 顶部变更表中的一对查找-替换,因此新增用例只需多一条条目。除了事实之外,每一步还会比较原始节点名称以及适配器读取的每个模块输入,并且在最后一步之后对反编译文本比较一次。

UnrealEditor-Cmd <uproject> -ExecCmds="Automation RunTests DreamFX.Incremental; Quit" -unattended -nullrhi -nosplash -stdout -FullStdOutLogOutput -TestExit="Automation Test Queue Empty"

日志在每一步有一行 DFXINCR,包含增量报告和每次构建的墙钟时间,这同时也是前后测量。差异会在造成差异的那一步报告,并附上双方的事实,随后两个资源都会被从头重建,以便下一步独立评判。-DreamFXIncrementalDump=<dir> 为两个资源写出每一步的事实、名称和输入,而带有 -DreamFXIncrementalFixture=<name> 的 -DreamFXIncrementalSteps=<word>|<word> 可将一次运行缩小到少量变更。

DescribeSystemFacts 只统计从系统出发通过非瞬态引用可达的数据接口。重建后在内存中遗留的接口——被移除的节点、Invalidated_* 缓存的默认值、瞬态占位符——不会被报告。这对比较很重要:以前,同一资源在垃圾回收或保存前后可能对自身作出不同的描述,因此事实比较可能取决于资源最后一次保存或回收的时间。构建安全门和 asset-diff 读取相同的事实,因此两者在保存和重新加载后都保持稳定。

开启和关闭

位置控制项默认值
编辑器,保存触发的重建Project Settings ▸ Plugins ▸ DreamFX ▸ Workspace ▸ Incremental Build on Save(bIncrementalBuildOnSave)开
编辑器,Rebuild DFX / Rebuild from Source / Rebuild them now不可配置始终完整
命令行dfx.ps1 build <file> -Incremental,或 UnrealEditor-Cmd <uproject> -run=DreamFX -File=<file> -Incremental关

命令行默认关闭是有意为之:CI 和验证门想要的是完整重建给出的答案,而不是取决于资源之前内容的答案。在无编辑器的本地编辑循环中使用 -Incremental,切勿在门中使用。参见无头 Niagara 编译。

编辑器的显式命令是应急出口:如果某个资源在保存后看起来不对,Rebuild from Source 会从文本完整重建它。

阅读日志

命令小工具和监视器为每个文件记录一行:

incremental: 0 emitter(s) and 1 stack(s) rebuilt, 3 input(s) written, 1 reset, nothing to recompile
full rebuild (the asset changed since its last build)

在写入之前,生成器会按堆栈记录它即将修改的内容:

incremental build -- Sparks ParticleSpawnScript values(+0 ~1 -0)

在 values(+a ~b -c) 中,三个计数分别是在该堆栈中添加、更改和移除的参数值。

模块探测只进行一次

规划过程每个进程复用一个模块库。一次构建所需的模块探测——解析模块名称、读取其输入签名——在每次编辑器会话或命令小工具运行中只进行一次,而不是每次构建都进行。因此在编辑器中,启动后的第一次构建可能比之后的构建慢。

故障排除

症状原因和修复
“我的保存总是进行完整重建”在 Output Log 中的 full rebuild (…) 行读取原因,并在回退表中找到它。常见原因是资源在两次保存之间于 DreamFX 之外发生变化,以及系统由较旧版本的 DreamFX 构建——升级后的第一次构建是完整的,下一次则不是
每次保存都是完整构建,且原因显示资源已更改有东西在你的保存之间重写了资源:在 Niagara 编辑器中手动编辑、源代码控制还原,或另一个进程保存同一个包。下一次完整构建之后的构建将是增量的,除非它再次发生
打开编辑器后的第一次保存较慢模块探测正在此会话中首次进行;参见上文
传入了 -Incremental 但日志显示 full rebuild (<reason>)读取原因。对于新资源、没有已存储计划的资源或生成器版本已更改的资源,本次运行所做的完整重建已存储了计划:下一次 -Incremental 运行将是增量的
资源看起来与源代码所述不同运行 Rebuild from Source(或 dfx.ps1 build <file> -Force)以获得完整重建的答案,然后对其执行 verify。如果完整重建和增量重建不一致,那就是增量路径中的缺陷——保留源代码和两个资源
批量批次中的一次保存没有构建那是批量批次门,而不是增量构建;Rebuild them now 链接始终是完整重建
门或 CI 作业不应依赖于先前的资源不要在那里传入 -Incremental。除非要求,否则它是关闭的

另见

本页目录