Skip to Content
内部原理布局与渲染

布局与渲染

Ansiq 的渲染不是“一次 render 就结束”的简单模型,而是一条更长的链:

render retained tree -> rerender dirty subtrees -> partial relayout -> invalidated regions -> redraw framebuffer -> diff -> terminal patch emission
Ansiq 渲染管线
1reactive flush / input / messages
2dirty scope collection
3subtree rerender 或 full rerender
4partial relayout
5invalidated regions
6redraw framebuffer
7framebuffer diff
8terminal patch emission

继续沿用 NotesApp

现在假设我们的 NotesApp 已经有:

  • 左侧 List
  • 右侧输入区
  • 一段 footer

也就是前一页那个已经开始拥有多个交互区域的版本。

当你只是在输入框里多输入一个字符时,Ansiq 理想中的行为不是:

  • 整个 app 重建
  • 整个布局重算
  • 整个 terminal 重画

而是更像这样:

input signal changed -> dirty component scope -> rerender only the composer subtree -> relayout only affected ancestors if geometry changed -> redraw only invalidated regions -> emit the minimal terminal patch
一次小输入变化的理想更新路径
1input signal changed
2dirty scope
3subtree rerender
4ancestor relayout if needed
5region redraw
6minimal patch

partial relayout

当 dirty subtree 被替换后,真正可能需要 relayout 的通常只是:

  • 这个子树本身
  • 它的祖先链
  • 某些会受位移影响的兄弟节点

NotesApp 里:

  • 如果只是 input 文本变了,但几何尺寸没变,影响范围应该很小
  • 如果提交后列表新增一项,列表区域和相关祖先可能需要 relayout
  • 但这仍然不意味着整屏都该重算

这也是为什么前面几页一直在强调“局部 signal”和“app 级状态”要分开。
状态边界清楚,dirty scope 和 relayout 边界才有机会清楚。

damage model

布局变化和 redraw 不是同一件事。

Ansiq 还会继续回答:

  • 哪些 region 真正 invalidated
  • 哪些地方只需要 redraw
  • 哪些地方根本不用动

这就是 damage model 的意义。

framebuffer diff

即使 redraw 发生了,最终输出到 terminal 之前仍然会做 diff。

所以 Ansiq 当前并不是:

  • 整树重建
  • 整屏重画
  • 整帧覆盖

而是:

  • 尽量局部 rerender
  • 尽量局部 relayout
  • 尽量局部 redraw
  • 最后再 patch emission

如果你把这页和前面的 streaming 那一页放在一起看,就会更容易理解 Ansiq 的主张:

  • 异步任务负责把消息带回来
  • app 负责把消息变成状态
  • runtime 负责把状态变化尽量局部地落到终端屏幕上

下一步

继续阅读 Viewport 与历史
那一页会把这个小应用继续推向 transcript / shell 场景,解释为什么 terminal session 和 scrollback 也是 runtime 的一部分。

Last updated on