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.
oakstorage Rust crate — project persistence
Status: implemented (file backends). Manual: docs/zh/plans/riir/M10-oakstorage.md.
Scope
Project persistence — the single module that knows where projects come
from and where they are saved to. Backends are pluggable via a manual
vtable; shipping in this pass: ove-xml (the XML project format) and
otio (the .otio / .fcpxml interchange, via the native oakotio
crate). The database backend (PostgreSQL + SQLite, SeaORM) is a
declared stub for a later proxy — not registered, todo!() bodies.
Consumers never branch on backend.
Architectural decisions
- URI dispatch, not file paths. Every entry point takes a URI:
file:///…proj.ove/file:///…proj.otio/oakdb://…. Bare paths are normalized tofile://. The core resolves scheme + backendcan_handlearbitration (M10 §2.3). - Manual vtable backends (
backend.rsStorageBackendtrait = the M10 C vtable's Rust shape). The public C ABI vtable (oakstorage_backend_register) accepts foreign (C-side) backends — the database-swap interface proof — and in-crate backends implement the Rust trait directly. - The graph (de)serialization itself stays in oaknode — every
backend calls the oaknode serializer (
oaknode::serializer::load/save) throughbridge::node(direct Rust calls, single-lib unification) to fetch/rebuild the in-memory graph; backends own framing: container bytes, schema, versioning (TOO_OLD/TOO_NEW/ UNKNOWN_VERSION), sessions.OAKSTORAGE_SAVE_COMPRESSis accepted but not implemented (the oaknode serializer emits plain XML only). - Database backend shares one logical schema across PostgreSQL and SQLite via SeaORM: a private current-thread tokio runtime drives the async API behind the synchronous C ABI; the entity set is minimal (projects table: id, name, payload blob, version, timestamps). The graph payload is the same serialized form the ove-xml backend uses — one serialization truth, two containers.
- No callbacks/events (M10: synchronous commands only; the caller — oaktask/facade — owns progress reporting).
- Errors follow the project -MMCCCC scheme, module 10
(
-100001…); the M10 positive info codes (TOO_OLD/TOO_NEW/…) are kept verbatim. - Interchange is lossy. The otio backend's export/import mapping
preserves sequences/tracks/clips/gaps/transitions; effect chains,
keyframes, project bins/settings and exact rational timebases are
not carried (see the module docs in
backends/otio.rs).
Layout
src/
lib.rs crate doc + module map
error.rs error/info codes (M10 §2.1, -MMCCCC module 10)
handle.rs shared CHandle re-export (boxes live in oaknode's handle.rs)
uri.rs URI parsing/classification
session.rs StorageProject session (open/take/uri)
registry.rs backend registry (register/unregister/arbitrate)
backend.rs StorageBackend trait + LoadResult
backends/
ove_xml.rs built-in .ove XML backend (via bridge::node)
otio.rs built-in .otio/.fcpxml backend (via oakotio)
database.rs declared stub (later proxy)
bridge/
node.rs oaknode calls (project + serializer + sequence builder)
ffi.rs export layer (M10 §2.2/§2.3 verbatim)
tests/ contract tests incl. the pluggability proof
Build / test
A workspace member (not a default member); build and test explicitly:
cargo test -p oakstorage