node: virtual graph endpoints and the Kahn-order BFS sweep (M0b core)

Per docs/zh/plans/render-pipeline-threads.md §3.8:

- oak-node/nodes/graphendpoints.rs: the GraphInput/GraphOutput
  virtual node pair — factory-registered but hidden from every create
  menu, duplicate refused, real value() semantics (the input forwards
  its feed_in row, the output publishes its tex_in as the frame).
  The input endpoint also declares a connectable feed_in port
  (documented deviation: footage/generator sources have no connectable
  inputs, so the walk needs a feeder anchor).
- graph.rs: ensure_endpoints/endpoints/is_endpoint — idempotent,
  identified by type id, default input->output edge only while the
  output's tex_in is free; remove_node refuses endpoints.
- project.rs + serializer.rs: every project graph carries the pair;
  a legacy file without endpoints migrates on load (roundtrip and
  legacy-migration tests, re-save is idempotent).
- traverser.rs: eval_graph_bfs — the endpoint-to-endpoint Kahn
  sweep. Live set = (input's forward cone U its feeder cone) INTERSECT
  (output's backward cone); multi-input nodes dequeue at zero
  in-degree over the live subgraph; deterministic ascending-id ready
  order (Graph::edges is a BTreeSet, so insertion order is
  unrecoverable — documented); time-shifted upstreams pull through
  the shared DFS memo (walk_dfs, factored out of evaluate);
  un-orderable remainder reports a named cycle; missing endpoints /
  unreachable output are errors. Eight BFS tests cover the plan's
  acceptance bullets.
- oak-render: bfs_endpoint_sweep_renders_footage_through_position —
  real clip through a real Position node via the sweep, shifted
  pixels asserted against a reference decode.
- Endpoint names localized in all eight i18n packs; storage/structure
  tests updated for the two extra nodes.
This commit is contained in:
2026-09-11 15:13:53 +08:00
parent 3a48dd4991
commit 29204d1f63
24 changed files with 1849 additions and 81 deletions
+4
View File
@@ -545,6 +545,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "Graph-Eingang"
"node.graphinput.input.feed_in": "Zufuhr"
"node.graphoutput.name": "Graph-Ausgang"
"node.graphoutput.input.tex_in": "Textur"
"status.ready": "Bereit"
"status.cache": "Cache: Aktiviert"
"status.proxy.on": "Proxy: Ein"
+4
View File
@@ -543,6 +543,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "Graph Input"
"node.graphinput.input.feed_in": "Feed"
"node.graphoutput.name": "Graph Output"
"node.graphoutput.input.tex_in": "Texture"
"status.ready": "Ready"
"status.cache": "Cache: Enabled"
"status.proxy.on": "Proxy: On"
+4
View File
@@ -545,6 +545,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "Entrada del grafo"
"node.graphinput.input.feed_in": "Entrada"
"node.graphoutput.name": "Salida del grafo"
"node.graphoutput.input.tex_in": "Textura"
"status.ready": "Listo"
"status.cache": "Caché: activada"
"status.proxy.on": "Proxy: activado"
+4
View File
@@ -545,6 +545,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "Entrée du graphe"
"node.graphinput.input.feed_in": "Entrée"
"node.graphoutput.name": "Sortie du graphe"
"node.graphoutput.input.tex_in": "Texture"
"status.ready": "Prêt"
"status.cache": "Cache : activé"
"status.proxy.on": "Proxy : activé"
+4
View File
@@ -545,6 +545,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "グラフ入力"
"node.graphinput.input.feed_in": "入力"
"node.graphoutput.name": "グラフ出力"
"node.graphoutput.input.tex_in": "テクスチャ"
"status.ready": "準備完了"
"status.cache": "キャッシュ:有効"
"status.proxy.on": "プロキシ:オン"
+4
View File
@@ -545,6 +545,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "Entrada do grafo"
"node.graphinput.input.feed_in": "Entrada"
"node.graphoutput.name": "Saída do grafo"
"node.graphoutput.input.tex_in": "Textura"
"status.ready": "Pronto"
"status.cache": "Cache: ativado"
"status.proxy.on": "Proxy: ativado"
+4
View File
@@ -545,6 +545,10 @@
"node.multicam.input.sources_in": "Sources"
"node.multicam.input.sequence_in": "Sequence"
"node.multicam.input.sequence_type_in": "Sequence Type"
"node.graphinput.name": "Вход графа"
"node.graphinput.input.feed_in": "Вход"
"node.graphoutput.name": "Выход графа"
"node.graphoutput.input.tex_in": "Текстура"
"status.ready": "Готово"
"status.cache": "Кэш: включён"
"status.proxy.on": "Прокси: вкл"
+4
View File
@@ -543,6 +543,10 @@
"node.multicam.input.sources_in": "源"
"node.multicam.input.sequence_in": "序列"
"node.multicam.input.sequence_type_in": "序列类型"
"node.graphinput.name": "图输入"
"node.graphinput.input.feed_in": "馈入"
"node.graphoutput.name": "图输出"
"node.graphoutput.input.tex_in": "纹理"
"status.ready": "就绪"
"status.cache": "缓存:已启用"
"status.proxy.on": "代理:开"