跳转到内容
查看源码

热替换与校验

Entelechy 的核心承诺:无效的东西永远不会发布,被拒绝的修改让正在运行的版本继续活着。 CLI 的 --watch、Player 和协议共享同一套权限规则。

保存 .ent ──► 离帧读取并编译 ──► 准备(校验,不构建)──► 下一次 sync 原子发布
│ │
└── 失败:rejected ◄──┘ 运行中的旧版本继续计算
  1. 准备(prepare):解析、类型检查、能力检查、降低为 IR 并校验。准备不构建任何东西——设备在第一次需要时构建(或在发布时,如果节点必然在那里运行)。
  2. 离帧:改动的文档在另一个线程读取和编译,替换在工作线程准备;这期间 evaluate 继续运行旧版本。sync 先报告 preparing,之后的某次 sync 报告 published 或 rejected。
  3. 发布(publish):原子的,并按**代数(generation)**检查。每个 KernelID 在注册表里有一个代数 g1, g2, …。
  4. 撤回(retire):从文档中消失的 Kernel 被撤回。

一个文档的改动只重新发布文本真正变化的 Kernel。

--watch 教程中记录的真实日志:先保存一个有错的 Kernel,再修好它:

rejected gw/kernels/grayscale.ent:8:59: `nope` is not defined
scene loaded
evaluated in 51.856µs: executed [], reused 2 -> gw.png
scene loaded
evaluated in 1.30081ms: executed [gray+source, blocks], reused 0 -> gw.png

被拒绝后什么都没有重新执行(executed []),输出仍来自旧版本;修好后受影响的节点(已融合的 gray+source 和 blocks)重新运行。

通过协议发布时可以带上 expect:只有注册表仍持有该代数时才替换。两次以同一个 expect 发布,第二次被拒绝(真实输出):

{"id":1,"ok":{"generation":3,"id":"demo.tint@1"}}
{"error":{"message":"cannot replace demo.tint@1 g2: the registry holds g3"},"id":2}

这让多个 Agent(或 Agent 与人)同时编辑时不会互相覆盖。文档层面的对应物是 compose 的 expect(文档版本摘要)。

情况 结果
语法/类型错误 rejected,带行列;旧版本继续运行
需要未授予的能力 准备时拒绝,并说明缺少哪些;之前运行的继续
场景自己的 Kernel 有一个被拒绝 整个场景被拒(scene_rejected),它的 Kernel 一个都不发布
一次求值失败 状态不提交;报告失败的 node(迁移失败还有 port、migration)
compose 检查不通过 一个文档都不写;错误指出出错的 path、version

替换 Kernel 不会丢失图的状态:<~ 端口保持已提交的值。如果新版本改变了状态端口的布局,使用 migrate 指定迁移 Kernel,否则状态从 initial 重新开始并在求值报告中列为 reset。详见图与状态。

  • 会话的位置是 (revision, frame):revision 在世界以“运行”之外的方式改变时前进,frame 计数该 revision 内提交的求值。
  • 每次改变都带来源(origin)记录;源码和文档按摘要只保存一次;被拒绝的尝试单独记录,什么也不改变。用协议 history 读取,kernel 带 generation 可读回旧源码。
  • snapshot 完整取走已提交状态及其“法则”(laws,即已发布 Kernel 的源码);restore 安装快照,用一帧证明世界能运行,否则全部回滚。

现实原子地改变;历史记录原因。(docs/DECISIONS.md)

门 粒度 操作
结构 一次一条语句 compose:编辑场景的文档,多个文档同时,要么全写要么都不写
状态 一次一个已提交端口 write、reset、restore
参数 文档声明的 param param

此外没有别的入口。 从外部设置节点的值会被拒绝——那是文档的结构,应通过 compose 修改(见 Agent 协议)。