布局与渲染
Ansiq 的渲染不是“一次 render 就结束”的简单模型,而是一条更长的链:
render retained tree
-> rerender dirty subtrees
-> partial relayout
-> invalidated regions
-> redraw framebuffer
-> diff
-> terminal patch emission1reactive 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 patch1input 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