跳转到内容
查看源码

场景与 .esn

.esn 文件就是图:它引入库,声明输入和输出,并放置节点。节点可以按 id 引用已发布的 Kernel,也可以内联携带自己的 Kernel,所以一个 .esn 可以完全自给自足。

use "kernels/"; // 目录下所有 .ent
use "../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 自己的名字(端口、参数、值)不能是关键字。

  1. 连线只写在消费者一侧。 port <- source 读本次求值的值,构成依赖;port <~ source from initial 读上一次成功求值提交的值,不构成依赖,在有提交之前读 initial。
  2. 来源是图输入名或 node.port;输入和节点共用一个命名空间。
  3. 每个声明的输入端口都必须连线,连接未声明的端口是错误——两者都在运行前报告。
  4. 值用 =:数字、true/false、取负、vec2/vec3/vec4。值是写出来的,不是算出来的:gain = 0.5 * 2.0;、gain = n + 1; 会被拒绝。
  5. domain 设置节点输出范围,可以是关于结构的表达式(size(port)、window() 等)。
  6. Kernel id 在一张图加载的所有内容中唯一;匿名内联 Kernel 命名为 <文档>.<节点>@1。
  7. use 路径相对于文档,结尾的 / 表示目录中的 .ent 文件。
  8. 节点可以绑定后端的原生操作:node raster: native.rasterize@1 { … }(native.rasterize@1/@3、native.cover@1),它们由运行时发布,不能被文档重新发布,也从不融合。
  9. 子图:node name: graph "path.esn" { … } 放置时展开,其节点以 name. 前缀加入父图。
  10. 重复:times = N; 与 carry port <- output;。
  11. 填充节点:node zeros: Rows<Float> { domain = 65; value = 0.5; }——一个 Field 而不是 Kernel。
  12. async = true; 离帧运行。
  13. awake = value(source, row); 让节点在该行为 0 时休眠。
  14. require name: a <= b; 声明图参数必须满足的条件,加载时与每次求值前检查。

每条规则的完整说明见仓库 docs/DOCUMENTS.md 的 Graph · Rules 一节,各写法的速查见类参考 · 场景语法。

图可以声明多个输出。窗口显示第一个没有指定设备的图片输出;输出可以用 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)

设备名写错会在加载前被拒绝,并提示最接近的名字。

  • 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)。