场景与 .esn
.esn 文件就是图:它引入库,声明输入和输出,并放置节点。节点可以按 id 引用已发布的 Kernel,也可以内联携带自己的 Kernel,所以一个 .esn 可以完全自给自足。
一个完整的例子
Section titled “一个完整的例子”use "kernels/"; // 目录下所有 .entuse "../shared/tonemap.ent"; // 单个库文件
input photo: Image; // 由调用者提供param amount: Float = 1.0; // 图参数,调用者可覆盖require gentle: amount <= 4; // 参数必须满足的条件
node gray: image.grayscale@1 { // 引用已发布的 Kernel source <- photo; // 连线:本次求值的值 amount = amount; // 赋值:字面量,或按名字引用图参数}
node trail: kernel { // 匿名内联 Kernel,就放在这里 requires { field.read; field.write; } input current: Image <- gray.result; // 声明处直接连线 input previous: Image <~ trail.result from gray.result; // 上一次求值的值 param decay: Float = 0.9; // 默认值就是这个节点的值 output result: Image; compute { return sample(current) + sample(previous) * decay; }}
node glow: kernel fx.glow@1 { ... } // 具名内联 Kernel:放在这里,也可被其他节点按 id 使用
node display: graph "../shared/display.esn" { // 把另一张图作为节点放置 source <- glow.result; // 连接它的 input exposure = 1.2; // 设置它的 param}
output result <- display.result; // 命名输出;子图的输出用 node.output 读取output kept <- state.result to "saves/state.field"; // 同时写入文件节点名可以是任意单词,包括关键字(node scan: …);但 Kernel 自己的名字(端口、参数、值)不能是关键字。
- 连线只写在消费者一侧。
port <- source读本次求值的值,构成依赖;port <~ source from initial读上一次成功求值提交的值,不构成依赖,在有提交之前读initial。 - 来源是图输入名或
node.port;输入和节点共用一个命名空间。 - 每个声明的输入端口都必须连线,连接未声明的端口是错误——两者都在运行前报告。
- 值用
=:数字、true/false、取负、vec2/vec3/vec4。值是写出来的,不是算出来的:gain = 0.5 * 2.0;、gain = n + 1;会被拒绝。 domain设置节点输出范围,可以是关于结构的表达式(size(port)、window()等)。- Kernel id 在一张图加载的所有内容中唯一;匿名内联 Kernel 命名为
<文档>.<节点>@1。 use路径相对于文档,结尾的/表示目录中的.ent文件。- 节点可以绑定后端的原生操作:
node raster: native.rasterize@1 { … }(native.rasterize@1/@3、native.cover@1),它们由运行时发布,不能被文档重新发布,也从不融合。 - 子图:
node name: graph "path.esn" { … }放置时展开,其节点以name.前缀加入父图。 - 重复:
times = N;与carry port <- output;。 - 填充节点:
node zeros: Rows<Float> { domain = 65; value = 0.5; }——一个 Field 而不是 Kernel。 async = true;离帧运行。awake = value(source, row);让节点在该行为 0 时休眠。require name: a <= b;声明图参数必须满足的条件,加载时与每次求值前检查。
每条规则的完整说明见仓库 docs/DOCUMENTS.md 的 Graph · Rules 一节,各写法的速查见类参考 · 场景语法。
输出与 Host 设备
Section titled “输出与 Host 设备”图可以声明多个输出。窗口显示第一个没有指定设备的图片输出;输出可以用 to 交给 Host 设备:
| 设备 | 接受 |
|---|---|
to sound |
一个或两个 float 的行(单声道/立体声),每帧一块采样 |
to pointer |
最大 128×128 的图片,作为系统指针 |
to pointer_tip |
指针图片中指向点的像素位置 |
to clipboard |
字符的 Rows<Int>,放到剪贴板(Player 中需要 --allow clipboard.write) |
to "file.ext"、to bytes "f.bin"、to files "dir/" |
写文件,需要 --allow file.write(删除/重命名还需 file.delete) |
设备名写错会在加载前被拒绝,并提示最接近的名字。
字体、目录与 Field 文件节点
Section titled “字体、目录与 Field 文件节点”node g: font "x.ttf" { codes <- wanted.result; }:读取字体,输出g.glyphs、g.outlines、g.metrics。node d: files "dir/" { paths <- wanted.result; }:像编辑器一样列出目录(d.listing),只读取被打开的文件(d.opened),遵守.gitignore。node near: Rows<Spot> fields "chunks/" { paths <- want.result; }:按需载入.field文件——比内存大的世界只保留帧附近需要的部分。
会话解析图,发布内联 Kernel,跟随 use(包括包),重建 Graph 模型。它只发布场景的 use 直接指向的文档中的 Kernel;对这些文档再 use 的文档,只取其声明。改动的文档只重新发布文本发生变化的 Kernel;被拒绝的替换保留旧版本;从文档中消失的 Kernel 被撤回(retire)。

