按这一版代码继续执行,不改 DAG 主体设计。 **后端只处理前面确认的 1~9,原来的第 10 项完全略过;同时重写 `Kernel/threading.md` ;前端整体迁移到 React + Vite + Material UI,DAG 使用 React Flow,但不使用 ELK、Dagre 或其他自动布局库,拓扑解析和布局算法自己实现。** 前端职责明确分开:Material UI 负责页面、菜单、表单、Tabs、Dialog、Card、Table、Tooltip、Chip、Button、Input、Select、Accordion 等全部普通 UI;React Flow 只作为 DAG 画布和节点/边交互层。React Flow 本身已经提供节点/边、缩放、平移、选择、自定义节点等基础能力,节点又直接是 React component,适合和 MUI 组合; **拓扑层级、节点坐标、折叠和时序映射全部由我们自己的 parser 决定**。 ([MUI][1]) --- # Renderive Frame Publication + Lock Elimination + Web UI 执行计划 ## 0. 最终目标 这轮改造完成以后,后端形成严格边界: ```text UI / Control / Producer │ ├── 修改 Renderable state ├── 修改 RTD ├── 修改 viewport ├── 修改 visibility ├── 修改 configuration ├── rebuild Renderable graph └── 修改 frame-control │ ▼ ┌──────────────────────────────┐ │ Frame Publication Boundary │ │ │ │ 完成所有 mutable → immutable │ │ snapshot / ownership 转移 │ └──────────────────────────────┘ │ ════════════════════════════════════ │ 以下禁止访问 mutable ▼ Render Plan │ ▼ ONE Taskflow │ ├── Prepare ├── Paint └── Composite │ ▼ Frame Complete ``` 双线以下必须满足: ```text 不获取 Plot mutex 不获取 Renderable configuration mutex 不获取 Renderable graph mutex 不重新读取 Frame Control mutable state 不修改 RTD 不访问 Scene mutable API 不读取 live visibility 不持有 renderer mutex 跑完整帧 ``` 允许的只有: ```text immutable Frame Snapshot immutable Renderable Graph Snapshot Prepare Buffer Paint Buffer Frame-local execution slots Frame ownership ``` --- # 第一阶段:建立统一 `Frame_Render_Snapshot` 这是 1~9 所有修改的基础。 不要针对每一个 mutex 单独打补丁。 新增一个明确的 Frame 级只读上下文,名称按当前项目命名规范确定,语义固定为: ```cpp Frame_Render_Snapshot ``` 至少承载: ```text frame_id render_sequence scene_state_revision viewport frame_control_state capture state/ticket per-renderable: Renderable_Id visibility configuration snapshot prepare revision paint revision immutable render graph ``` 注意: ```text Frame_Render_Snapshot ``` 不是把所有 Renderable 业务数据复制一次。 Renderable 的大块 State / RTD 继续使用已有: ```text published state render state immutable snapshot ``` Frame Snapshot 保存的是: ```text 这一帧到底引用哪个 published version ``` 以及渲染过程需要的小型 immutable metadata。 --- # 1. 删除 Paint 中的 viewport mutex ## 当前问题 当前路径: ```text Paint Task ↓ Renderable::viewport_size() ↓ Plot_Core::viewport_size() ↓ Plot_Core::Impl::mutex ``` 多个 Paint worker 会共同碰这个 mutex。 这是第一处直接从 worker 热路径删除的锁。 ## 修改 Frame 发布时: ```text Plot viewport ↓ Frame_Render_Snapshot::viewport ``` Paint Context: ```cpp struct Paint_Render_Context { ... Size viewport; }; ``` 实际代码不要重复保存时,则引用: ```cpp const Frame_Render_Snapshot* frame; ``` Paint: ```text Painter ↓ context.frame.viewport ``` 禁止再: ```text Painter ↓ Renderable::viewport_size() ↓ Plot_Core ``` ## 旧接口语义 `Plot_Core::viewport_size()` 对外 API 不删除。 调用方普通控制线程仍然可以调用它。 但是: > Render DAG 内部不得调用它。 所以不是破坏旧 API,而是把渲染内部依赖切断。 ## 测试 新增: ```text Frame snapshot 后修改 viewport ``` 验证: ```text 当前 Frame → 使用旧 viewport 下一 Frame → 使用新 viewport ``` 同时保证多个并行 Paint 不进入 `Plot_Core::mutex`。 --- # 2. 拆分 Prepare / Paint / Composite Context 这是这轮最重要的架构约束之一。 当前: ```cpp Scene_Render_Context ``` 同时给 Prepare 和 Paint 使用,而且带: ```cpp Scene_Base* scene; ``` 必须拆。 ## 新结构 ```text Prepare_Render_Context Paint_Render_Context Composite_Render_Context ``` ### Prepare 可以拥有 ```text Frame snapshot 自身 Renderable identity prepare buffer 必要的 dependency published views metrics ``` Prepare 可以解析已声明 dependency。 --- ### Paint 只能拥有 ```text Frame immutable snapshot 自身 Prepare Buffer 自身 Paint Buffer / Color Cache 自身 paint-only immutable config metrics ``` Paint Context **禁止存在**: ```cpp Scene_Base* scene; Renderable_Base* dependency; Plot_Core* plot; Frame_Control_Strategy_Base* strategy; ``` 这样用户就算想在 Paint 里面: ```cpp context.scene->xxx(); ``` 也根本写不出来。 --- ### Composite 只能拥有 ```text Frame snapshot 已完成 Paint Buffer final target layer/composite metadata metrics ``` Composite 不允许重新访问 Renderable 原始状态。 ## 执行 逐个修改: ```text Renderable Waterfall Spectrum Afterglow Axis Overlay 其他内置 Renderable ``` 所有: ```text prepare callback paint callback composite callback ``` 签名。 旧的统一 `Scene_Render_Context` 实现直接删除。 不保留兼容 overload。 --- # 3. visibility 正式进入 Frame Snapshot 当前: ```cpp visible_.load(...) ``` 虽然无 mutex,但是仍然是 live state。 这个要改。 ## 新语义 控制侧: ```text set_visible(false) ``` 修改下一次 publication 使用的状态。 Frame 发布: ```text Renderable A visible = true Renderable B visible = false ``` 这一帧之后固定。 DAG 编译也直接基于这份 snapshot。 最好可以进一步: ```text visible == false ``` 直接裁掉: ```text prepare paint composite ``` 或者按现有缓存/图层语义裁剪所需节点。 ## 禁止 Taskflow worker: ```cpp is_visible() ``` 不要再 live load。 ## 测试 ```text Frame A publish: visible=true Taskflow blocked control thread: set_visible(false) release Taskflow ``` 验证: ```text Frame A 仍绘制 Frame B 不绘制 ``` --- # 4. `discard_real_time_data()` 移出 Prepare Worker 这是现在 Prepare 热路径里最不应该存在的一类操作。 当前: ```text Prepare ↓ discard_real_time_data() ↓ Frame Control query ↓ RTD state ↓ RTD mutation/discard ``` 必须取消。 ## 正确边界 RTD 的: ```text update discard history maintenance retention ``` 属于: ```text producer/control/publication side ``` 不属于 Render DAG。 Frame 建立之前: ```text RTD mutable storage ↓ maintenance / discard ↓ publish render snapshot ════════════════════════ ↓ Prepare 只读 ``` ## Render DAG 内 Prepare 只允许: ```text read RTD render snapshot ``` 禁止: ```text clear discard update lock update storage ``` ## Frequency 如果 discard policy 需要: ```text frequency_hz ``` 直接使用 publication 时捕获的: ```text frame_control_state.frequency ``` 不要从 RTD Prepare 中再返回 Scene 查询。 ## 测试 必须覆盖: ```text RTD update 与 Frame publication 并发 RTD discard 与 publish Frame publish 后 RTD 再变化 ``` 当前 Frame 只能看到 publication 时确定的 snapshot。 --- # 5. `configuration_mutex_` 移出每帧 Render Plan 编译 当前 Render Plan 编译为了: ```text cache_enabled prepare validity paint validity ... ``` 会读取: ```cpp configuration() ``` 然后拿: ```text configuration_mutex_ ``` 这要取消。 ## 新设计 区分: ```text mutable/control configuration ``` 和: ```text published render configuration ``` Scene 配置修改仍然通过当前: ```text set_renderable_configuration() lock_render_idle() ``` 保持原有外部线程安全语义。 Frame publication 时: ```text configuration ↓ Renderable_Frame_State.configuration ``` Render Plan compiler 之后只读: ```text snapshot.configuration ``` ## `configuration_mutex_` 如果控制 API 自身仍然需要 mutex,可以保留。 但它不能再进入: ```text compile_render_plan() Taskflow ``` ## 测试 同样使用 Frame A / Frame B 语义: ```text Frame A publication 改变 cache/config Frame A 执行 Frame B publication ``` Frame A 不允许中途改变。 --- # 6. Render Graph 改为 immutable published graph 当前: ```cpp renderable->render_graph() ``` 每帧经过: ```text render_graph_mutex_ ``` 这个也清掉。 但不能直接删 mutex,因为当前允许: ```text render || rebuild_render_graph() ``` 并发。 因此要改 ownership,不是硬删锁。 ## 新结构 Renderable 控制侧: ```text mutable graph builder state │ ▼ rebuild │ ▼ shared_ptr │ ▼ atomic/publication ``` Frame publication: ```text Frame └── shared_ptr ``` 当前 Frame 一旦拿到: ```text Graph v17 ``` 哪怕控制线程马上 rebuild: ```text Graph v18 ``` 当前 Frame 仍继续读: ```text Graph v17 ``` 不加锁。 下一 Frame 获得: ```text Graph v18 ``` ## 生命周期 必须依靠 immutable graph ownership: ```text shared_ptr ``` 或者现有项目已有等价 ownership。 不要重新做 generation pointer hack。 ## `rebuild_render_graph()` 锁只允许保护: ```text build/publish 新 Graph ``` 绝不保护: ```text 当前 Frame 读取旧 Graph ``` ## 测试 保留并强化当前: ```text Scene render || rebuild_render_graph() ``` 测试。 新增验证: ```text Frame A = graph version 10 rebuild → 11 Frame A 仍然完整执行 version 10 Frame B 使用 version 11 ``` --- # 7. Frame Control `swap + read` 合并成一次 publication 现在类似: ```text swap() ↓ unlock ↓ frame_control_state() ↓ lock ``` 第二次锁没有意义。 ## 改造 Frame Control publication API 必须能够直接返回: ```text published State snapshot ``` 语义类似: ```cpp State publish(); ``` 或者符合现有命名: ```text swap_and_acquire_render_state() ``` 具体函数名按现有接口风格确定。 但语义必须是: ```text lock ├── publish/swap ├── 得到当前 immutable State └── unlock ``` 一次完成。 Frame 以后只持: ```text State value ``` ## 不做 不要为兼容保留: ```text swap() + swap_and_get() ``` 两套内部实现。 如果原接口属于公开旧语义,需要保留 public API 时: ```text public swap() ``` 仍可存在。 但是 Renderive 内部新执行路径只使用单 publication operation。 --- # 8. `Render_Plan_History` 从 unchanged-frame 热路径移出 Plan History 是: ```text 历史/分析冷路径 ``` 不能每帧: ```text mutex compare unlock ``` ## 新状态 Scene/Frame compiler 持有: ```text current_render_plan ``` 同时维护能够判断 effective topology 是否变化的: ```text plan signature ``` 这个 signature 是明确的结构字段组合。 不要使用字符串序列化。 例如覆盖: ```text active node IDs node kind dependency edges composite edges ``` 按照 canonical 顺序比较。 ## 没变化 ```text reuse current shared_ptr ``` 完全不进入 History mutex。 ## 有变化 才: ```text new version ↓ new Render_Plan ↓ publish current ↓ append Render_Plan_History ``` History lock 只发生在: ```text plan version changed ``` 这条冷路径。 ## 注意 继续保持当前 `a.md` 的语义: > cache pruning 导致 active topology 改变,可以生成新的 Render Plan version。 不要因为这次锁优化重新改掉。 --- # 9. Render Lease 从“整帧持 mutex”改成短 ownership claim 这个要非常谨慎,必须保留旧函数语义: > 多个 renderer caller 并发调用时,同一个 Frame 最多只能被一个 renderer 消费。 不能因为去锁导致重复消费。 ## 当前 ```text acquire_renderer ↓ render_mutex LOCK ↓ Render Lease 整个生命周期 ↓ Taskflow ↓ wait ↓ Render Lease destructor ↓ render_mutex UNLOCK ``` 要改成: ```text acquire_renderer ↓ 短同步 ownership claim ↓ 获得独占 Frame ════════════════════════ 下面不再持 Frame-Control mutex ↓ Taskflow ↓ complete ``` ## ownership 状态 Frame 必须具有明确状态机,例如语义: ```text pending ↓ claimed ↓ rendering ↓ completed ``` ownership claim 可以: ```text 短 mutex ``` 或者符合当前数据结构时使用: ```text CAS ``` 不要为了“lock-free”强行上复杂 atomic 状态机。 目标不是: ```text acquire_renderer 本身绝对无锁 ``` 目标是: ```text ownership 确定以后不再持 mutex 执行整帧 ``` ## Flow / Manual / Low Latency 分别处理,但统一遵守: ```text Frame pointer/queue ownership transition 允许短同步 Frame 已被 renderer 独占后 不持 mutex ``` 不能破坏各 strategy 原来的语义。 ## 测试 必须继续覆盖: ```text 4/8 renderer callers 同一个 Frame 恰好消费一次 无重复 无撕裂 无 ownership 丢失 ``` 同时增加: ```text 一个 renderer 正在执行长 Taskflow 另外 renderer 调用 acquire_renderer ``` 验证: ```text 不会因为第一个 Render Lease 持整帧 mutex 导致无关 Frame Control API 被长时间锁死 ``` --- # 原第 10 项 **完全略过。** 不做: ```text TSan 执行计划 性能阈值验收计划 ``` 这轮不写进去。 --- # 11. 最终 Render Worker 的锁规则 完成 1~9 后,建立一条可以机械检查的规则。 进入: ```text Render Plan execution ``` 以后,下列函数不得出现: ```cpp std::mutex::lock std::recursive_mutex::lock std::shared_mutex::lock std::lock_guard std::unique_lock ``` 指的是 Kernel 自己的执行数据路径。 下面这种 Frame-local worker slot: ```text execution_slots[index] ``` 继续直接独占写。 不增加锁。 最终应该是: ```text Taskflow worker ├── immutable Frame Snapshot readonly ├── immutable Render Graph readonly ├── Prepare Buffer owner-defined ├── Paint Buffer exclusive by DAG ├── final composite dependency DAG controlled └── execution slot[index] exclusive index ``` --- # 12. 同步重写 `Kernel/threading.md` 这次不要在现在的 `threading.md` 上简单补几行。 当前里面很多描述会因为此次改造失效,例如: ```text Double/Triple State render snapshot 都在 mutex 下读取 render_graph_mutex_ 串行当前 render graph 访问 Low Latency render_mutex_ 整个 Render Lease 生命周期持有 ``` 这些都要按新模型重写。 --- # `threading.md` 必须采用下面的结构 ## 1. 核心线程模型 第一段直接规定: ```text Renderive 的同步边界分为: 1. Mutable Control Domain 2. Publication Domain 3. Immutable Frame Execution Domain 4. Cold Analysis Domain ``` --- ## 2. Mutable Control Domain 明确这里可以使用锁。 包括: ```text Renderable 属性修改 Scene attach/detach dependency/layer 修改 RTD update viewport 修改 configuration 修改 graph rebuild Frame producer queue 操作 ``` 这里的原则: ```text 锁保护 mutable ownership 锁不泄漏到 Render DAG ``` --- ## 3. Publication Domain 专门说明: ```text publication 是 mutable → immutable 的唯一边界 ``` 允许: ```text mutex short critical section pointer swap shared_ptr publication atomic ownership change ``` 禁止: ```text 持锁进入 Taskflow ``` 描述: ```text Frame publication 完成以后, 当前 Frame 所需的数据版本必须全部固定。 ``` --- ## 4. Immutable Frame Execution Domain 这一节要写成最严格的规则。 明确: ```text Prepare/Paint/Composite worker 不获得 Kernel mutex。 ``` Worker 只允许: ```text 读取 Frame Snapshot 读取 published Render State 读取 immutable Renderable Graph 访问 dependency snapshot 写自己的 Prepare Buffer 写自己的 Paint Buffer 写自己的 execution slot ``` 禁止: ```text Plot_Core live getter Scene control API Frame Control mutable API RTD mutation configuration getter requiring lock render_graph rebuild/getter requiring lock live visibility ``` --- # 13. threading.md 增加“锁分类表” 必须直接写表。 类似: | 锁/同步 | 所属域 | 是否允许跨 Frame Execution | 用途 | |---------------------------|---------------------|---------------------------:|---------------------------------| | Scene topology mutex | Control | 否 | attach/detach/topology mutation | | State publish mutex | Publication | 否 | mutable→render state | | RTD mutation mutex | Control | 否 | update + observer 顺序 | | RTD publish mutex | Publication | 否 | render snapshot | | Graph rebuild mutex | Control/Publication | 否 | 创建新 immutable graph | | Frame queue mutex | Ownership | 否 | claim/publish frame | | Render Plan History mutex | Cold | 不进入 worker | 保存历史版本 | | Capture repository mutex | Cold | 不进入 worker | 保存成功 Snapshot | | Taskflow worker data | Execution | 无锁 | immutable/exclusive data | 这样以后审代码的时候直接对表查。 --- # 14. threading.md 增加锁顺序 虽然 Frame execution 不拿锁,控制侧仍然需要锁顺序。 必须明确规定: ```text Scene control ↓ Renderable control ↓ Frame control ↓ RTD / observer ``` 但实际顺序需要按代码最后改完后的真实调用链重新核对后写。 **不能提前凭猜测写死。** 最终 threading.md 必须列: ```text 允许的 nested locking 禁止的 nested locking callback 前必须释放哪些锁 ``` 尤其强调: ```text 用户 observer callback 用户 Renderable callback Taskflow callback ``` 调用前不得持有可能被 callback 重入的控制锁。 --- # 15. threading.md 增加 callback 规则 写明: ```text Observer callback 是同步 callback ``` 但: ```text 不得在持有 Frame ownership mutex 时调用 不得在持有 Scene topology mutation mutex 时调用会重入 Scene 的 callback 不得在 publication mutex 下执行未知用户代码 ``` 需要 state observation 时: ```text lock 生成 Observation value unlock callback(observation) ``` 保持当前已经采用的正确模式。 --- # 16. threading.md 增加 Frame Ownership 章节 Flow / Manual / Low Latency 分别说明: ```text producer ownership pending ownership renderer claim render ownership completed ownership recycle ownership ``` 核心规则: > Frame ownership 转移可以同步,Frame ownership 一旦确定,Frame 内容执行不依赖互斥锁。 这句话作为 Frame Strategy 的总规则。 --- # 17. threading.md 增加 Snapshot Lifetime 章节 写清: ```text Frame 持有什么 shared ownership 什么时候释放 graph version 生命周期 RTD snapshot 生命周期 renderable snapshot 生命周期 ``` 特别说明: ```text Renderable rebuild Graph ``` 不会使正在执行 Frame 的 Graph 失效。 --- # 18. threading.md 增加 Prepare/Paint 并发规则 保留现在正确的: ```text 不同 Renderable 无依赖即可并发 Renderable 内无 dependency edge 即可并发 Prepare A 可以与 Paint B 并发 ``` 并新增: ```text 并发安全不是依赖 mutex 达成 而是依赖: immutable input exclusive output DAG dependency ``` 这个很重要。 --- # 19. threading.md 增加“不应该看到的锁” 直接写一个禁止清单。 在: ```text prepare callback paint callback composite callback Taskflow wrapper ``` 里面如果 Kernel 自己出现: ```text configuration_mutex_ render_graph_mutex_ Plot_Core::mutex Frame Control state mutex RTD mutable mutex Render Lease mutex ``` 视为架构错误。 不是“性能优化建议”。 是违反线程模型。 --- # 20. 前端整体废除当前手写 DOM 架构 当前: ```text webapp_gallery/ ├── index.html ├── app.js └── styles.css ``` 其中 `app.js` 自己维护: ```text GalleryCard class DOM clone querySelector 菜单状态 Tabs 表单 DAG SVG Timeline SVG 选择状态 ``` 这一版直接替换。 旧实现删除。 不保留: ```text legacy app.js legacy page React page ``` 双实现。 --- # 21. 前端新技术栈 固定: ```text React TypeScript Vite Material UI @mui/icons-material @xyflow/react ``` **不使用:** ```text ELK elkjs Dagre D3 layout Graphviz Cytoscape AMIS 手写 DOM UI 手写完整 DAG SVG ``` Material UI 是 React component library,可以把普通 UI 全部收敛为一致的组件模型。 ([MUI][1]) React Flow 只承担图画布能力: ```text node rendering edge rendering zoom pan selection fit view viewport ``` 这些都是其现成能力。 ([React Flow][2]) --- # 22. DAG 布局自己解析 这里严格按你刚才的要求: **不要 ELK。** 新增自己的纯 TypeScript parser/layout: ```text renderPlanToDagModel() ↓ layoutRenderDag() ↓ React Flow nodes/edges ``` --- # 23. DAG parser 输入 直接使用当前后端: ```text render_plan ├── version ├── nodes └── edges ``` 不要让 React component 自己理解后端 JSON。 首先解析成前端自己的强类型模型: ```ts RenderPlan RenderNode RenderEdge RenderNodeKind RenderDagModel RenderDagLevel ``` 所有后端 JSON 检查集中在: ```text protocol/ ``` React component 不做: ```ts data?.foo?.bar ?? ... ``` 满页面 defensive parsing。 --- # 24. 自己实现 DAG level parser 布局算法按 DAG 本身语义做。 第一步: ```text node indegree ``` 然后 Kahn topological traversal。 计算: ```text level[node] = max(level[parent] + 1) ``` 无父节点: ```text level = 0 ``` 得到: ```text Level 0 ├── A.prepare ├── B.prepare └── C.prepare Level 1 ├── A.paint └── C.paint Level 2 ├── ... ``` --- # 25. 同 level 节点排序 不能随机。 排序规则固定,保证同一 plan 刷新后节点不乱跳。 依次: ```text owner/renderable order kind logical execution order node_id ``` 如果前一 version 有相同 Node ID: ```text 优先保持上一个位置 ``` 这样: ```text Plan v17 → Plan v18 ``` 只新增一个 node 时,不应该全图重排。 --- # 26. DAG 布局增加 barycenter pass 基础 level 完成之后,自己实现简单交叉减少。 两遍: ```text left → right right → left ``` 对每层节点根据邻接节点平均位置排序。 不引入第三方 layout engine。 复杂度保持可控。 如果数据量增长,最多增加几轮 deterministic pass。 不做无限迭代优化。 --- # 27. DAG Node 使用 MUI 组件 React Flow custom node 内部直接用 MUI: ```text Paper Stack Typography Chip Tooltip Box ``` 例如: ```text ┌────────────────────────────┐ │ Waterfall │ │ chunk.paint.4 │ │ │ │ PAINT 1.82ms 23.1% │ └────────────────────────────┘ ``` Node 数据: ```text owner name kind duration critical cache state worker ``` React Flow custom node 本身就是普通 React component,因此这种组合是自然的。 ([React Flow][3]) --- # 28. DAG Edge 不自己写 SVG Path 算法 这个和“自己写 parser”区分开。 自己写的是: ```text DAG topology parser level layout position selection mapping ``` 不是重新实现图形库。 Edge 使用 React Flow 自带: ```text straight step smoothstep ``` 当前更适合执行 DAG 的默认使用: ```text smoothstep ``` dependency / composite 用不同: ```text edge data / style ``` 表达。 React Flow 已经提供这些 edge 类型以及自定义 edge 能力,不需要我们重新维护点击命中、SVG 路径等基础设施。 ([React Flow][4]) --- # 29. 前端目录重构 改成: ```text webapp_gallery/ ├── index.html ├── package.json ├── tsconfig.json ├── vite.config.ts └── src/ ├── main.tsx ├── App.tsx ├── theme.ts ├── protocol/ │ ├── gallery.ts │ ├── renderPlan.ts │ └── capture.ts ├── websocket/ │ └── GallerySocket.ts ├── state/ │ └── galleryState.ts ├── components/ │ ├── GalleryCard.tsx │ ├── GalleryToolbar.tsx │ ├── ControlPanel.tsx │ ├── ObserverPanel.tsx │ ├── PerformancePanel.tsx │ └── capture/ │ ├── CapturePanel.tsx │ ├── CaptureControls.tsx │ ├── FrameBrowser.tsx │ ├── RenderDag.tsx │ ├── RenderDagNode.tsx │ ├── WorkerTimeline.tsx │ ├── NodeDetail.tsx │ └── PlanComparison.tsx └── dag/ ├── model.ts ├── parseRenderPlan.ts └── layoutRenderDag.ts ``` 不要搞一个: ```text utils.ts ``` 塞几千行。 --- # 30. Gallery 主页面 Material UI 化 现在的: ```text topbar hero mode tabs category filters cards context menu toast ``` 分别改成 MUI: ```text AppBar Toolbar Tabs ToggleButtonGroup / Chip Grid Card Drawer / Dialog Snackbar ``` 所有按钮和 input 都不再自己手写 HTML styling。 --- # 31. 原右键菜单改成 MUI Drawer 当前性能控制区内容很多。 不要继续使用自己计算定位的 context menu。 右键 Renderable 后打开: ```text Drawer ``` PC 端右侧。 里面: ```text Tabs ├── 属性 ├── 专属 API ├── Observer ├── Performance ├── Performance Capture └── Render DAG ``` 保持现有功能语义。 --- # 32. 表单全部 MUI controlled component 后端字段类型解析成: ```text boolean → Switch enum → Select number → TextField type=number action → Button readonly → Typography/TextField readonly ``` 解决之前手写表单容易: ```text 后端刷新覆盖正在输入的值 Enter 后值恢复 dirty state 不明确 ``` 的问题。 每个字段维护: ```text serverValue draftValue dirty submitting error ``` **后端 telemetry refresh 不允许覆盖 dirty draft。** 只有: ```text submit 成功 manual reset ``` 才同步。 --- # 33. Performance Capture 页面重新组件化 布局: ```text ┌────────────────────────────────────────────────────┐ │ Capture Controls │ ├──────────────┬─────────────────────────────────────┤ │ Frame List │ DAG │ │ │ │ │ ├─────────────────────────────────────┤ │ │ Worker Timeline │ ├──────────────┴─────────────────────────────────────┤ │ Selected Node Detail │ ├────────────────────────────────────────────────────┤ │ Statistics / Plan Comparison │ └────────────────────────────────────────────────────┘ ``` 使用 MUI: ```text Paper Stack Grid List ListItemButton Tabs Table Chip Tooltip Divider ``` --- # 34. Timeline 不再手写 SVG Timeline 不需要另外一个图框架。 自己解析: ```text worker_id start_offset_ns duration_ns ``` 然后使用: ```text MUI Box ``` 做 CSS absolute positioning。 每一个 execution block: ```text Box ``` 位置: ```text left = start / frame_duration width = duration / frame_duration ``` Worker 一行一个: ```text Stack / Box ``` 这样: ```text selection hover tooltip critical path ``` 全部是标准 React DOM,而不是手工 `createElementNS()`。 --- # 35. DAG 和 Timeline 共享 selection store 当前: ```text selectedCaptureNodeId ``` 保留这个概念,但放到 React state。 ```text selectedNodeId selectedFrameId selectedPlanVersion ``` 点击 DAG: ```text selectedNodeId = X ``` Timeline 对应 block 高亮。 点击 Timeline: ```text selectedNodeId = X ``` DAG 对应 Node 高亮。 Detail 自动更新。 不要两套独立选择状态。 --- # 36. DAG parser 不关心 Capture 建立: ```ts parseRenderPlan(plan) ``` 只处理 topology。 Capture overlay 单独: ```ts applyFrameExecution(dag, frame) ``` 这样同一套 DAG 可以用于: ```text Current Render Plan Capture Frame Plan Comparison ``` 避免现在 SVG renderer 里: ```text plan + frame + statistics ``` 全部搅在一起。 --- # 37. Plan version 变化的前端处理 利用稳定: ```text node_id ``` 做增量视觉稳定。 当: ```text v17 → v18 ``` parser 输出: ```text same nodes added nodes removed nodes changed edges ``` UI 可以: ```text 新增节点标记 删除节点列表 边变化 ``` 并尽可能保持原 Node 位置。 这非常适合你后面看: ```text Waterfall partition cache pruning ``` 到底怎么改变 DAG。 --- # 38. 前端不要修改后端协议语义 这一轮: ```text 前端技术实现替换 ``` 不是重新设计 Gallery protocol。 当前已有: ```text render_plan performance_capture node_statistics plan statistics frame snapshots ``` 继续消费。 只有在 React 强类型化时发现: ```text 数据本身缺关键 identity ``` 才修改协议。 不要为了前端组件化随便改 C++ JSON 字段。 --- # 39. Vite 集成方式 `webapp_gallery` 自己作为 frontend source。 Vite build 输出固定到现有 Web Server 使用的静态目录。 路径计算全部基于: ```text vite.config.ts 自身目录 ``` 不做随机输出目录。 不改变现有构建库的文件路径语义。 CMake 只负责: ```text 调用 frontend build 复制/嵌入确定的 dist ``` 不把前端源文件逻辑塞进 CMake。 --- # 40. 前端迁移顺序 严格按: ```text 1. Vite + React + TS 能构建 2. WebSocket protocol 强类型化 3. Gallery 首页 4. Pixel Canvas 5. Controls 6. Actions 7. Observer 8. Performance 9. Performance Capture 10. Render DAG 11. Worker Timeline 12. Plan Comparison 13. 删除 app.js 14. 删除旧 DOM templates 15. 删除不再使用的 styles.css ``` 不要边迁移边留旧页面 fallback。 最后直接一套实现。 --- # 41. 后端实际执行顺序 这部分严格按依赖执行: ```text A. Frame_Render_Snapshot 基础结构 ↓ B. viewport snapshot ↓ C. Prepare/Paint/Composite Context 拆分 ↓ D. visibility snapshot ↓ E. RTD discard 移出 DAG ↓ F. configuration snapshot ↓ G. immutable Render Graph publication ↓ H. Frame Control publish/read 合并 ↓ I. Render Plan current/history 分离 ↓ J. Render Lease 短 ownership claim ↓ K. threading.md 重写 ``` 原因是后面的锁删除都依赖前面的 snapshot ownership。 不要反过来先删 mutex。 --- # 42. 每一步测试要求 每一个步骤都采用: ```text 先补对应测试 ↓ 修改实现 ↓ 跑对应测试 ↓ 跑 Kernel/render_2D/web_server 全量相关测试 ↓ 进入下一项 ``` 不是全部改完最后才测。 尤其不能出现: ```text 先把 mutex 全删掉 然后看看有没有 crash ``` --- # 43. 最终锁审计标准 改完以后重新全局搜索: ```text mutex lock_guard unique_lock shared_lock recursive_mutex ``` 逐个给它分类: ```text Control Publication Ownership Cold ``` **不存在第五类。** 凡是属于: ```text Frame Execution ``` 的 Kernel mutex,继续修改,直到为 0。 --- # 44. 最终架构 最终形成: ```text MUTABLE DOMAIN │ ┌──────────────────────┼───────────────────────┐ │ │ │ Renderable State RTD / Config Graph Builder │ │ │ └──────────────────────┼───────────────────────┘ ▼ FRAME PUBLICATION │ ┌────────────┴────────────┐ │ Frame Render Snapshot │ │ Immutable Graphs │ │ Published RTD Views │ │ Render Plan │ └────────────┬────────────┘ │ ═══════════════════════════╪════════════════════════════ NO KERNEL LOCKS │ ▼ ONE TASKFLOW ┌────────────┼────────────┐ ▼ ▼ ▼ Prepare Paint Composite │ │ │ └────────────┼────────────┘ ▼ Frame Snapshot │ COLD ANALYSIS DOMAIN │ Repository / Statistics / API │ ▼ React + Material UI │ React Flow Render DAG │ Own DAG Layout Parser ``` ## 最终验收只有一句话 > **控制侧允许锁,publication 允许短锁,Frame ownership 转移允许短同步;一旦 Frame 的 immutable execution snapshot 发布完成,直到 Prepare/Paint/Composite 完成,Kernel 不再通过 mutex 获取任何渲染输入,也不持有 Frame-Control mutex 执行整帧。** 前端则执行: > **所有常规 UI 使用 Material UI;DAG 使用 React Flow 的画布/节点/边能力;DAG 拓扑解析、分层、排序、交叉减少、版本位置稳定全部由 Renderive 自己的 TypeScript parser 完成;不引入 ELK/Dagre。** ([React Flow][2]) 这版计划不会动你已经完成的全局 DAG 核心,主要是在它外面补上最后一道真正严格的 **Frame immutable publication boundary** 。完成后,“交换以后渲染执行无锁”就不再是一种实现倾向,而会成为整个 Kernel 明文规定的线程契约。 [1]: https://mui.com/material-ui/?utm_source=chatgpt.com "React components that implement Material Design" [2]: https://reactflow.dev/api-reference/react-flow?utm_source=chatgpt.com "The ReactFlow component" [3]: https://reactflow.dev/learn/customization/custom-nodes?utm_source=chatgpt.com "Custom Nodes - React Flow" [4]: https://reactflow.dev/examples/edges/custom-edges?utm_source=chatgpt.com "Custom Edges - React Flow"