Planes 与 Compositor
@simon_he/vue-tui 现在的终端渲染不是“所有内容共享一块 buffer 再一起重绘”,而是 plane-scoped retained row compositor:
- 调度按 plane 收集 invalidation
- render manager 按 plane 维护 dirty rows
- terminal 在 commit 时把各 plane 的 row buffer 合成到最终可见 buffer
这套模型的目标很直接:让 footer / loading / overlay 这类高频更新,不再把 transcript 正文一起拖进同一轮重绘。
Plane 模型
框架公开了 4 个 plane:
defaulttranscriptchromeoverlay
含义约定:
default:不显式分层时的默认平面transcript:正文、消息列表、长文本主体chrome:header、footer、loading、input、状态栏overlay:dialog、popover、approval、调试层
导出入口:
TERMINAL_RENDER_PLANESTerminalRenderPlaneTerminalRenderPlanes
为什么要分 plane
在长正文 streaming 的 CLI 场景里,最常见的卡顿不是“没有内容”,而是“内容已经到了,但 footer/loading 的动画和正文重绘互相阻塞”。
plane compositing 解决的是这个耦合问题:
chrome更新时,不必扫描transcript的节点overlay打开关闭时,不必让正文参与本轮 rendertranscript大量追加时,chrome仍然可以保持自己的刷新节奏
它优化的是“无关区域根本不要参与渲染”,而不只是把一次全局渲染做得更快。
公开 API
createTerminal().commit({ planes })
Terminal.commit() 现在支持显式提交某些 plane:
terminal.commit({ planes: ["chrome"] });多数应用代码不需要直接手动传 planes,因为 TerminalProvider / createTerminalApp() 会自动根据本轮 invalidation 收集 active planes。
对应的 commit 事件也会带上:
dirtyRowsplanes
scheduler.invalidate({ plane })
调度器支持 plane-aware invalidation:
scheduler.invalidate({ plane: "overlay" });如果不传,行为等价于普通 invalidation;但对高频区域,显式指定 plane 可以让框架只刷新对应平面。
runtime.mount(component, props, { plane })
命令式挂载的运行时 portal 也支持指定 plane:
runtime.mount(DialogLike, { open: true }, { plane: "overlay" });这让 runtime-mounted 节点也能参与同一套 plane 调度和 compositor 合成。
TRenderPlane
TRenderPlane 是 plane model 最常用的 Vue 入口。它会为整棵子树切换到指定 plane,并把:
terminalscheduler.invalidate()runtime.mount()
都自动绑定到当前 plane。
<TerminalProvider :cols="80" :rows="24">
<TRenderPlane plane="transcript">
<ChatMessages />
</TRenderPlane>
<TRenderPlane plane="chrome">
<FooterStatus />
</TRenderPlane>
<TRenderPlane plane="overlay">
<TDialog v-model="open" :w="48" :h="12" />
</TRenderPlane>
</TerminalProvider>使用建议:
- 普通组件库 demo 可以继续停留在
default - 只有当你明确想把正文、状态栏、弹层解耦时,再引入
TRenderPlane overlay适合所有“应该盖住下面内容”的节点TRenderPlane.plane在 mount 后按 immutable 处理;需要移动子树时,用<TRenderPlane :key="activePlane" :plane="activePlane">重新挂载- 不要依赖动态修改
planeprop 迁移已 mount subtree;tab switching、dialog migration 或 animation plane 迁移都应 key remount ctx.invalidate({ plane: undefined })会跳出当前TRenderPlane,在 root scheduler 中按 all-plane invalidate 处理- frame task / mailbox id 是 scheduler-global,不会自动带 plane namespace;跨 plane producer 需要把 plane/instance 写进 id
渲染顺序
最终合成顺序是:
defaulttranscriptchromeoverlay
高层 plane 可以覆盖低层 plane,也可以用空格显式擦除下层内容。也就是说:
overlay不只是“画在上面”,它还能真正遮住下面的文本chrome可以只更新 footer 行,而不破坏正文其余区域
性能含义
这套设计带来的主要收益不是“平均 render 时间看起来更漂亮”,而是:
invalidates更少render-manager扫描的节点更少chrome的 commit cadence 更稳定- stdout 单次大写出更少出现
更具体的验收口径见:
什么时候值得用
下列场景非常适合 plane compositing:
- 长文本 streaming,同时 footer 还有 loading / thinking 动画
- 正文区和底部输入框同时频繁变化
- overlay 打开关闭时,希望正文完全不参与本轮重绘
- 宿主需要把 runtime portal 和普通组件树放进同一套终端渲染体系
如果你的界面很小、更新也很少,那么继续用默认 plane 就够了,不必为了“架构完整”强行拆分。