DreamFXLang
语言参考

事件与 simulation stage

OnEvent 处理器与具名 Stage 块——它们怎么写、各自的硬性上限,以及为什么一个 DataInterface 名字就是全部契约。

在六个固定栈之外,一个 emitter 还可以携带一个事件处理器和任意数量的 simulation stage。两者都是普通的栈:模块调用、折叠赋值、disabled,一样都不少。

OnEvent —— 响应另一个 emitter 的事件

Emitter Receiver
{
    ParticleSpawn = { … }

    OnEvent(Source = Sparks, Event = "LocationEvent", Mode = SpawnedParticles, SpawnNumber = 2) = {
        ReceiveLocationEvent();        // ordinary stack statements; runs per received event
    }
}

头部配置这个处理器;块本身就是一个和别处一样的栈。

参数含义
Source本系统里生成该事件的那个 emitter。必填
Event它生成的事件(一个 GenerateLocationEvent() 模块发出的就是 "LocationEvent")。必填
ModeSpawnedParticles 每个事件生成 SpawnNumber 个新粒子并在它们身上运行这个块;EveryParticle 在已有粒子上运行它
SpawnNumber / RandomSpawnNumber / MinSpawnNumber生成多少个
MaxEventsPerFrame每帧处理事件的上限
UpdateAttributeInitialValues是否更新初值

只写与默认值不同的那几项。缺少 Source 或 Event,或者出现未知参数,都是 DFX2025。

导出也可以使用反引号包裹的源令牌:`<none>` 表示处理器读取自己的 emitter,`<unresolved GUID>` 表示一个过期的源标识符。两者都按原样重建; 无法解析的源会报告 DFX5036,因为这个处理器永远不会触发。完整的 事件源契约见系统参考。

警告:

每个 emitter 只能有一个 OnEvent 块(DFX5031)。DreamFX 通过和另外四个栈同一套轨道去寻址事件栈,而那套轨道只能到达恰好一个 处理器。要响应两条事件流的 emitter,拆成两个 emitter。

事件的生成方通常需要在源 emitter 的 Settings 里设置 RequiresPersistentIDs = true, 因为它会读取 Particles.ID;缺了它,构建会报告引擎自身的栈问题 (DFX6003)。

Stage —— simulation stage

Emitter Fluid
{
    Settings = { SimTarget = GPU; }

    ParticleUpdate = { … }

    Stage Settle = {                     // a bare stage: enabled, particle iteration, runs once
        ScaleColor();
    }

    Stage Project(DataInterface = "Emitter.PressureGrid", NumIterations = 10) = {
        …                                // ordinary stack statements; runs per grid cell, ten times
    }
}

stage 是运行在 ParticleUpdate 之后的粒子栈,作用于 GPU emitter (SimTarget = GPU;带 Stage 块的 CPU emitter 属于 DFX5033)。声明顺序就是执行顺序。

头部参数含义
DataInterfacestage 迭代的那个网格(一个线程一个 cell,而不是一个粒子一个线程);它已经隐含了迭代源
Iteration显式声明的迭代源——Particles、DataInterface、DirectSet——用于需要它的形态,比如没有任何绑定的数据接口迭代
NumIterations重复该 stage(默认 1)
Enabledfalse 把它停在那里
ExecuteBehavior什么时候运行(例如 Always、OnSimulationReset)

这些是常见的头部字段。通用 stage 还额外支持 direct-dispatch 尺寸设定、 线程数覆盖以及粒子迭代状态过滤;完整的参数表见 系统参考。

只写与刚添加的 stage 不同的那些。参数不认识或形状不对是 DFX2026;两个同名的 Stage 块是 DFX5032 —— stage 按名字标识,否则构建会 悄悄留下最后运行的那个栈。

DataInterface 是个名字,而这个名字就是全部契约

引擎在编译期按名字解析迭代网格,并且从不读取绑定里存着的类型。这不是猜测: 创作出来的资产带着的类型句柄在后来的会话中会解析成垃圾,而它们照样 正常模拟。

网格本身通常是由某个模块的内部写入物化出来的——绑定要成立并不需要它是一个 声明过的参数,而名字拼错会表现为这个 stage 对着一个并不存在的网格编译。

NumIterations 与 Enabled 接受一个值或一个参数

值所在的位置决定是哪一种:数字、true 或 false 是字面量,任何读起来 像名字的东西是绑定。

Stage Project(NumIterations = 6, NumIterations = Emitter.OVERRIDE.SolveIterations) = { … }
Stage DebugSlice(Enabled = Grid3D_GAS_CONTROLS_SPAWN.RenderDebugSlice) = { … }

两者可以同时出现,如上所示,因为引擎两个都存:数字是绑定覆盖不到时的兜底。

警告:

这件事值得照写,而不是简化掉。Enabled 被绑定的 stage 在特效说了算的时候才运行, 而只保留字面量标志的重建会永远运行它——Ninja 的 debug slice 就是这样在每一个镜像里 画到了流体上面。

缺口

DFX8016: 自定义 C++ stage 类的 stage——任何不是引擎通用 stage 的东西——没有 DreamFXLang 形式,会被作为缺口写进导出文件的头部。

DFX8015: 一个 emitter 上超过一个事件处理器也无法携带;导出会点名重建后的 emitter 将不再接收哪些事件。

两者都属于「声明过就绝不丢弃」的契约——见 往返与等价。

两族都是量过的

事件与 stage 都做过全库往返:本项目全部 27 个真实 stage 都走过了 corpus、mirror-diff 以及 asset-diff 的 stage 事实族,而 DFX8016 为零。usage id 为零的 栈在读取和写入两侧都需要特殊处理——那部分属于实现——但对使用者来说结论很直接:这两族 不是「大概没问题」,而是量过的。

本页目录