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