局部重排与 damage model
这一块是 Ansiq runtime 成熟化里最值得警惕的部分之一。
因为很多复杂度都来自这条链:
- signal 更新
- dirty scope
- subtree replacement
- 哪些祖先必须 relayout
- 哪些兄弟只是位置变化
- 哪些 region 需要 redraw
- 哪些 region 其实不该动
为什么这件事难
它不是一个单独的问题,而是一串连续问题。
一旦边界不清楚,就会出现:
- 过度 relayout
- 过度 redraw
- 终端 patch 太大
- 看起来“没报错但布局总是怪怪的”
Ansiq 当前的方向
Ansiq 现在已经有:
- ancestor-chain relayout
- invalidated region tracking
- overlapping dirty path normalization
- 更明确的 layout protocol 和 render math 分层
也就是说,它已经不再是“整树 relayout -> 整块 redraw”的粗模型。
damage model 和 relayout 不是一回事
这是很重要的一条区分:
- relayout 关心的是几何与位置
- damage model 关心的是终端哪些区域真的变了
一个节点的位置变化,不代表整块区域都必须重画。
为什么这会持续成为重点
因为越到后期,Ansiq 越不能靠“先整块刷了再说”来掩盖边界问题。
真正成熟的 runtime 一定要把这一层越收越细、越做越可预测。
当前已经收紧到什么程度
目前这条链里已经明确存在:
- dirty scope 收集
- dirty path 归一化
- ancestor-chain relayout
- invalidated region tracking
- full redraw 与 partial redraw 的边界
这意味着 Ansiq 已经不再依赖“整树重排、整块重画”去掩盖边界问题。
normalize_dirty_paths(...):先把脏路径压缩干净
如果一个节点和它的子节点同时被标脏,例如:
[0]
[0, 0]本质上只需要按父路径处理一次。
normalize_dirty_paths(...) 这一类逻辑做的就是:
- 重复路径去重
- 父路径吞掉已被覆盖的子路径
如果不先做这一步,就会出现:
- 重复 ancestor relayout
- 重复 damage 计算
- 表面上没错,但性能和可解释性都会变差
所以 partial relayout 的成熟化,不只是“少算一些布局”,而是让这条链更可证明。
ancestor-chain relayout:只沿祖先链向上传播
Ansiq 当前并不是“某个节点脏了,就从根重新算一遍”。
更接近的策略是:
- 找到 dirty subtree
- 用新子树替换旧子树
- 沿着它的祖先链向上重新测量和重新定位
- 直到几何不再继续变化
这就是 ancestor-chain relayout。
它比“整树 relayout”小得多,但又比“只算当前节点”更正确。因为一个子树尺寸变了,最先受到影响的通常不是它自己,而是:
- 它的父节点怎么分配空间
- 它的兄弟节点是否要重新定位
- 更上层容器的总高度是否变化
为什么 wrapper 高度同步也是这条链的一部分
另一个隐蔽问题是:
- dirty subtree 已经替换了
- 包装节点还拿着旧的 measured height
这会让父节点继续按旧高度分配 child rect。看起来像“布局偶尔错位”,实际上是 relayout 边界没收紧。
所以这一层不仅要减少工作量,还要保证:
- 新子树的几何真正向上传播
- 只有必须知道这件事的祖先重新计算
damage model 为什么要跟着这条链走
当 ancestor-chain relayout 算出新的 rect 之后,damage model 才能继续回答:
- 旧 rect 和新 rect 哪些区域必须清掉
- 哪些区域只是需要重画内容
- 哪些兄弟虽然没 rerender,但因为位置变化仍然要重画
所以 layout invalidation 和 damage model 不是两套互不相干的优化,而是同一条更新链的前后两段。
这和 patch emission 的关系
最终目标不是漂亮的内部结构,而是更小、更稳定的终端 patch:
- relayout 太大,patch 一定也会变大
- damage model 太粗,终端残影和多余 redraw 更容易出现
所以这部分虽然是 internals 主题,但它会直接影响:
- 交互稳定性
- 终端残影
- 长时间运行时的性能
下一步
继续阅读: