图与状态
Graph 运行时(entelechy-graph)按 KernelID 组合 Kernel,进行带缓存、脏标记的求值。Kernel 拥有计算,Graph 拥有关系。
一次求值(一帧)只运行“有东西在读”的节点。节点在以下任一项改变时重新运行:它的 Kernel、它的值、它的范围、它读取的时钟或输入、它读取的来源。
一个节点如果给出了与上次逐字相同的结果,对它的读者来说就是“未改变”——所以一个便宜的、已稳定的节点可以让后面昂贵的节点免于运行,处于不动点的状态不会推进。(比较只在 Host 内存中总是进行;在设备上只有当节点仅为它读取的状态而运行时才比较,因为在设备上比较需要等待。)
融合与执行计划
Section titled “融合与执行计划”当消费者只在自己的元素处 sample 生产者时,两者会融合成一个 Kernel,中间结果不写出。entelechy graph 打印展平后的图、已发布 Kernel 及其代数、状态推进顺序和计划:
// plan: 2 group(s)// gray + source: fused.gray@1 vec2(128, 128)// blocks: image.pixelate@1// gray runs apart: `blocks` reads it neither at its own element (`sample`) nor at one offset of a row每个“分开运行”的生产者都会附上原因(它在休眠、它的工作有预算、按地址读取……)。--no-fusion 可以关闭融合以便对照;pin(协议)可以保持某个节点物化以便 read。
docs/DOCUMENTS.md 明确写明从不融合的:归约、scan、scatter、原生操作、两个节点之间的窄 Field(narrow Field)。
node life: life.step@1 { cells <~ life.result from seed.result; // 读上一帧提交的值;第一帧读 seed}port <~ source from initial读取上一次成功求值提交的值,不构成依赖;在第一次提交前读initial。- 被
<~读取的节点持有状态。用<-读它的节点看到本次求值产生的新状态,用<~读它的看到上一次留下的状态。所以多个节点必须一致的时刻(比如“战斗开始”)应该由一个节点决定,其他节点用<-从它的新状态读取。entelechy graph会在计划中说明每个节点读取了谁的新状态、谁的已提交状态。 - 只有整次求值成功时状态才提交;失败的一帧什么都不改变。
重新发布的 Kernel 改变了状态端口的布局时:
cells <~ life.result from seed.result migrate life.widen@1;migrate 指定一个 map Kernel:输入是旧布局,输出新布局,在所有节点之前运行一次,求值报告为 migrated。没有 migrate 时状态从 initial 重新开始,求值报告 reset。迁移 Kernel 未发布、不是这样的 map、布局不对或失败,都会使求值失败,状态保持原样。
结构上的复用
Section titled “结构上的复用”| 写法 | 作用 |
|---|---|
node name: graph "path.esn" { … } |
把另一张图作为节点放置:展开后其节点以 name. 前缀加入;它的 input 由父图连线,param 由父图赋值 |
times = N; + carry port <- output; |
节点或子图放置 N 次(name/1…name/N),从第二次起 carry 把上一次的输出喂给端口;name.port 指最后一次。N 可以是字面量或图参数——改参数会让运行中的场景改变结构 |
node zeros: Rows<Float> { domain = 65; } |
填充节点:一个 Field,而不是 Kernel |
examples/fluid 用 times 放置 Jacobi 压力求解;examples/mipmap 用 carry 生成各级 mip。
async = true;:节点在自己的线程上离帧运行。完成前读者拿到它上一次的输出,它通过<~喂的状态保持不变;发现它完成的那次求值读取新输出、重跑读者并提交。一次只运行一个;开始时复制输入。回放的settle和协议evaluate的settle: true会等待所有运行完成。awake = value(source, row);:该行为 0 时节点休眠,不运行,只被它读取的节点也不运行。一个“标题画面 + 角色创建 + 世界”合在一个场景里的游戏,可以让不显示的两部分休眠。
param是图参数,调用者可以覆盖(--param name=value、回放param、协议param)。节点自己的值属于文档,只能通过compose修改。require name: a <= b;声明参数必须满足的条件(比较两个计数),加载时和每次求值前检查,不满足时求值在任何东西运行前停止,并说明两边各算出了什么。例如 physics 包:require cells: columns * rows <= 1 << bits;。
图计算结构,从不计算值。(
docs/DECISIONS.md)
节点的值是写出来的,domain 和 times 可以由结构推出,但任何数值计算都属于 Kernel。这让图保持可读、可编辑,Agent 修改结构时也不会意外引入计算。

