UI物化:数据树是真相,UWidget 只是可丢弃投影

自动关联目录:UI物化:数据树是真相,UWidget 只是可丢弃投影

核心判断:创作者 UI 的权威状态在文档树,UWidget/Slate 只是当前 World 和 Viewport 的运行时投影。投影可以晚建、重建和丢弃,但不能反过来成为数据真相。

一、业务冲突:节点事件可能早于父子关系与完整属性

编辑预览直接展示源 UI,Play 时又要从运行时克隆后的树构建玩家 UI。节点创建消息可能先于 Parent 或 Name;文本框会因单双行切换而重建控件;图片异步完成时,原节点甚至已销毁或换代。

若收到 NodeAdded 就立刻显示,加载页等编辑辅助节点可能闪进正式 HUD;若把 UWidget 当状态源,重建后输入、样式和脚本可见属性都会丢失。

二、实际实现:先建拓扑,再激活;属性缓存支持重放

工程监听节点新增、属性、父子关系、销毁和重载。Play 构建时先完成根拓扑,设置活动树,再重放待处理样式并标记布局;找不到 Parent 的节点暂存,AncestryChanged 到达后再挂接和失效命中缓存。

运行时 Screen 根延迟一帧确认身份,因为 Create 事件可能早于 Name。控件类型变化导致 UWidget 重建时,缓存属性会完整重放。图片请求同时携带节点 RequestToken 与 UI Generation,回调只有在节点、请求和整棵 UI 代际都匹配时才能应用。

flowchart LR
    D[版本化 UI 数据树] --> P[Document Projection]
    P --> H[运行时层级]
    H --> W[UWidget/Slate]
    C[属性缓存] --> W
    R[异步资源 + Token] --> W
    W --> E[输入/编辑事件]
    E --> D

三、值得保留:状态重放与 Teardown 屏障

“先拓扑、后激活”避免半棵树进入屏幕;“属性缓存、表示可重建”正确适配了 UE 控件生命周期。异步图片同时校验局部 RequestToken 与全局 Generation,也避免同一节点连续改图或整场 UI 重建时旧结果覆盖新状态。

退出 Play 前先标记 TornDown、增加 Generation、停掉 Ticker、解绑 Delegate 并清除 Viewport Widget,再允许文档回滚发出新的 Create 事件,这个顺序解决了重复试玩后 Slate 残留和按钮累积。

四、结构性缺陷:单体 Manager 同时承担五种一致性

现有管理器同时处理文档事件、Widget 物化、布局、命中、输入捕获、文本输入、滚动裁剪、图片合图、资源缓存和诊断。它能集中保证顺序,但任何重载、布局或资源策略改动都可能触发全局副作用,测试也难以隔离。

只有一个全局 Dirty 标志会把局部属性变化扩大成整树工作;反向依赖不完整时,又只能靠同步全量 Flush 保证命中正确。UObject/动态委托/Slate 指针混在同一所有者中,还会让 GC、Delegate 与 Viewport 移除的清理顺序变得脆弱。

五、目标设计:版本化投影管线,而非万能 UI Manager

拆成 DocumentProjection、WidgetMaterializer、LayoutEngine、HitGraph、ResourceBinder 与 PresentationHost。每层接收带 Revision 的变更集并输出新 Revision;节点属性真相只在 Projection,Widget 保存最小缓存和 NodeId,不承担持久状态。

结构变化先更新投影与依赖图,再批量物化;资源完成只提交到匹配 Generation/RequestToken 的属性槽;控件重建从 Projection 重放。PresentationHost 独占 AddToViewport/RemoveFromParent 和生命周期屏障,其他层不直接触碰 Viewport。

void ApplyBatch(ChangeSet changes)
{
    ProjectionDelta d = documentProjection.Apply(changes);
    MaterializeDelta m = materializer.Reconcile(d);
    layout.Invalidate(m.layoutRoots);
    hitGraph.Invalidate(m.hitRoots);
    resourceBinder.Refresh(m.resourceSlots);
    host.CommitVisibleRoots(m.rootChanges);
}

六、决策与验证:重建之后行为不变才算状态完整

变化应重建范围必须保留的事实
普通样式属性当前 WidgetNodeId、输入状态、其他缓存属性
单行/多行切换文本控件子树文本、选择区、焦点策略
父子关系变化受影响拓扑稳定节点身份与层级顺序
Screen 根激活根与 Host编辑/运行模式隔离
图片请求更新单个资源槽新 RequestToken 胜出
Play 退出整个投影 Generation旧回调与委托全部失效

UI 框架的质量不应以“支持多少控件”衡量,而应看任意事件乱序、表示重建和 World 退出后,数据树、屏幕结果与输入目标能否重新收敛到同一 Revision。