diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index 3c23ca8c7..2b6368443 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -3636,6 +3636,7 @@ dependencies = [ "gpui_platform", "gpui_widgets", "image", + "smallvec", ] [[package]] diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml index 243c89efa..dc781aa1a 100644 --- a/app/rust/Cargo.toml +++ b/app/rust/Cargo.toml @@ -37,6 +37,10 @@ gpui = { path = "../../gpui/crates/gpui" } gpui_platform = { path = "../../gpui/crates/gpui_platform", features = ["font-kit"] } # Oak's widget library: menus, viewer, form controls, project explorer. gpui_widgets = { path = "../../gpui/crates/gpui_widgets" } +# The mock engine's synthetic viewer frames (`image::Frame` in a +# `RenderImage`), matching the versions gpui itself uses. +image = "0.25" +smallvec = "1" [dev-dependencies] # `#[gpui::test]` harness for engine-seam smoke tests (test-support feature). diff --git a/app/rust/docs/screenshot-window.png b/app/rust/docs/screenshot-window.png index c656a9afc..5e20ab3cf 100644 Binary files a/app/rust/docs/screenshot-window.png and b/app/rust/docs/screenshot-window.png differ diff --git a/app/rust/examples/screenshot.rs b/app/rust/examples/screenshot.rs index 2566a2660..f61fc110e 100644 --- a/app/rust/examples/screenshot.rs +++ b/app/rust/examples/screenshot.rs @@ -57,12 +57,16 @@ fn main() -> Result<()> { })?; let handle: AnyWindowHandle = window.into(); - // Let the platform settle, then draw one full frame into the rendered - // scene so `render_to_image` has something to capture. + // Draw a few frames so the layout settles: the node editor fits its graph + // once the canvas size is known and the viewers upload their first CPU + // frame, both of which happen on the frame after the initial render. + for _ in 0..4 { + cx.run_until_parked(); + cx.update_window(handle, |_root, window, app| { + let _ = window.draw(app); + })?; + } cx.run_until_parked(); - cx.update_window(handle, |_root, window, app| { - let _ = window.draw(app); - })?; let image = cx.capture_screenshot(handle)?; std::fs::create_dir_all(std::path::Path::new(OUT).parent().unwrap())?; diff --git a/app/rust/src/app.rs b/app/rust/src/app.rs index 41f228a13..8ed355c88 100644 --- a/app/rust/src/app.rs +++ b/app/rust/src/app.rs @@ -152,7 +152,7 @@ impl PanelRegistry for AppPanelRegistry { cx, )), "node-editor" => Some(PanelHandle::new( - cx.new(|cx| NodeEditorPanel::new(window, cx)), + cx.new(|cx| NodeEditorPanel::new(self.engine.clone(), window, cx)), cx, )), "inspector" => Some(PanelHandle::new( @@ -234,7 +234,7 @@ impl OakApp { cx, ) }); - let node_editor = cx.new(|cx| NodeEditorPanel::new(window, cx)); + let node_editor = cx.new(|cx| NodeEditorPanel::new(engine.clone(), window, cx)); let inspector = cx.new(|cx| InspectorPanel::new(engine.clone(), window, cx)); let history = cx.new(|cx| HistoryPanel::new(window, cx)); let timeline_panel = diff --git a/app/rust/src/i18n.rs b/app/rust/src/i18n.rs index d621126ac..1a328994f 100644 --- a/app/rust/src/i18n.rs +++ b/app/rust/src/i18n.rs @@ -95,17 +95,19 @@ pub fn set_language(language: Language) { Language::ZhCN => 1, }, Ordering::Relaxed); persist_language(language); + sync_widgets(); } /// Loads the persisted language from the oakcommon config C ABI. Called once /// at startup. Never fails: without liboakcommon the default (en-US) stays. pub fn init() { let Some(store) = ConfigAbi::load() else { + sync_widgets(); return; }; match store.get("Language") { Some(code) if !code.is_empty() => set_language(Language::from_code(&code)), - _ => {} + _ => sync_widgets(), } } @@ -127,6 +129,28 @@ pub fn tr(key: &'static str) -> &'static str { } } +/// The keys the gpui widget crates localize through +/// `gpui_widgets::i18n` (viewer transport labels, effect-stack empty state). +pub const WIDGET_KEYS: &[&str] = &[ + "viewer.safe_frames", + "viewer.zoom", + "viewer.no_frame_source", + "effect_stack.empty", + "effect_stack.add", +]; + +/// Installs the active language's widget strings into the +/// `gpui_widgets::i18n` string-table hook, so the widget-baked labels follow +/// the app language on the next render. Called on startup and on every +/// language switch; the widgets keep their built-in defaults otherwise. +pub fn sync_widgets() { + let mut table = gpui_widgets::i18n::StringTable::new(); + for key in WIDGET_KEYS { + table.insert((*key).to_string(), tr(key).to_string()); + } + gpui_widgets::i18n::set_table(table); +} + /// Looks `key` up in the en-US table. fn en(key: &'static str) -> &'static str { EN @@ -226,14 +250,16 @@ const EN: &[(&str, &str)] = &[ ("timeline.track_height", "Track Height"), ("timeline.snap", "Snap"), // --- node editor --- - ("node.zoom_in", "Zoom In"), - ("node.zoom_out", "Zoom Out"), ("node.fit", "Fit"), - ("node.fit_window", "Fit Window"), - ("node.placeholder", "Node Editor · Placeholder — gpui::node_graph not wired up yet"), // --- viewer header chips --- ("viewer.source", "Source Viewer · Source"), ("viewer.program", "Program Viewer · Program"), + // --- widget-baked strings (synced to gpui_widgets::i18n) --- + ("viewer.safe_frames", "Safe Frames"), + ("viewer.zoom", "Zoom"), + ("viewer.no_frame_source", "No frame source"), + ("effect_stack.empty", "No selection"), + ("effect_stack.add", "+ Add Effect"), // --- inspector --- ("inspector.params", "Parameters (placeholder)"), ]; @@ -319,14 +345,16 @@ const ZH: &[(&str, &str)] = &[ ("timeline.track_height", "轨道高"), ("timeline.snap", "吸附"), // --- node editor --- - ("node.zoom_in", "放大"), - ("node.zoom_out", "缩小"), ("node.fit", "适配"), - ("node.fit_window", "适配窗口"), - ("node.placeholder", "节点编辑器 · 占位 — gpui::node_graph 尚未接入"), // --- viewer header chips --- ("viewer.source", "素材查看器 · 源"), ("viewer.program", "序列查看器 · 节目"), + // --- widget-baked strings (synced to gpui_widgets::i18n) --- + ("viewer.safe_frames", "安全框"), + ("viewer.zoom", "缩放"), + ("viewer.no_frame_source", "无帧源"), + ("effect_stack.empty", "未选择"), + ("effect_stack.add", "+ 添加效果"), // --- inspector --- ("inspector.params", "参数(占位)"), ]; @@ -534,6 +562,40 @@ mod tests { assert_eq!(tr("menu.file.save"), "Save"); } + /// `sync_widgets` installs the active language's strings into the widget + /// string-table hook, so the widget-baked labels follow the app language. + #[test] + fn sync_widgets_installs_the_active_language() { + let _guard = lang_lock().lock().unwrap(); + set_language(Language::EnUs); + gpui_widgets::i18n::clear_table(); // simulate a fresh process + sync_widgets(); + assert_eq!( + gpui_widgets::i18n::tr("viewer.safe_frames", "安全框").to_string(), + "Safe Frames" + ); + assert_eq!( + gpui_widgets::i18n::tr("viewer.zoom", "缩放").to_string(), + "Zoom" + ); + // Every widget key is covered by the installed table. + for key in WIDGET_KEYS { + let installed = gpui_widgets::i18n::tr(key, "fallback").to_string(); + assert_ne!(installed, "fallback", "widget key {key} not synced"); + } + + set_language(Language::ZhCN); + assert_eq!( + gpui_widgets::i18n::tr("viewer.safe_frames", "Safe Frames").to_string(), + "安全框" + ); + assert_eq!( + gpui_widgets::i18n::tr("effect_stack.add", "+ Add Effect").to_string(), + "+ 添加效果" + ); + set_language(Language::EnUs); + } + /// The config code round-trips. #[test] fn language_code_round_trips() { diff --git a/app/rust/src/oakui/mock.rs b/app/rust/src/oakui/mock.rs index 6407fa937..c6533de20 100644 --- a/app/rust/src/oakui/mock.rs +++ b/app/rust/src/oakui/mock.rs @@ -36,17 +36,25 @@ //! The real engine will later implement the same gateway over the //! `liboakengine` C ABI; only the wiring in [`crate::app`] changes. +use std::collections::{BTreeSet, HashMap}; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Instant; use gpui::effect_stack::{ EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent, }; +use gpui::node_graph::{ + EdgeData, EdgeId, NodeData, NodeGraphDataSource, NodeGraphEvent, NodeId, PortData, + PortDataType, PortId, PortKind, +}; use gpui::timeline::{ ClipData, ClipId, Frame, FrameRange, FrameRate, TimelineDataSource, TrackData, TrackKind, }; -use gpui::{prelude::*, px, App, Context, Entity, Hsla, Pixels, SharedString}; +use gpui::{ + hsla, point, prelude::*, px, App, Context, Entity, Hsla, Pixels, Point, RenderImage, + SharedString, +}; use gpui_widgets::audio_meter::AudioMeterDataSource; use gpui_widgets::project_explorer::{ProjectDataSource, ProjectEntry}; use gpui_widgets::viewer::PlaybackClock; @@ -57,6 +65,12 @@ use super::transport::TransportState; /// The demo sequence length: 00:04:18:18 at 25 fps. const SEQUENCE_LENGTH: i64 = 6468; +/// The synthetic viewer test frame is rendered at a small proxy size (the +/// real engine will deliver full-resolution frames; the mock only needs to +/// prove the CPU-frame path end to end). +const SYNTH_FRAME_WIDTH: u32 = 384; +const SYNTH_FRAME_HEIGHT: u32 = 216; + // --------------------------------------------------------------------------- // Clocks // --------------------------------------------------------------------------- @@ -256,6 +270,125 @@ impl EffectData for MockEffect { } } +// --------------------------------------------------------------------------- +// Node graph model +// --------------------------------------------------------------------------- + +/// A port on a mock node. `connected` is a model-side cache refreshed by +/// [`MockEngine::refresh_port_connectivity`] after every graph mutation. +#[derive(Debug, Clone)] +pub struct MockPort { + id: PortId, + kind: PortKind, + label: SharedString, + data_type: PortDataType, + connected: bool, +} + +impl PortData for MockPort { + fn id(&self) -> PortId { + self.id + } + + fn kind(&self) -> PortKind { + self.kind + } + + fn label(&self) -> SharedString { + self.label.clone() + } + + fn data_type(&self) -> PortDataType { + self.data_type.clone() + } + + fn is_connected(&self) -> bool { + self.connected + } +} + +/// A node in the mock graph (媒体 → 变换 → … → 输出). +#[derive(Debug, Clone)] +pub struct MockNode { + id: NodeId, + title: SharedString, + position: Point, + inputs: Vec, + outputs: Vec, + header_color: Option, + enabled: bool, + collapsed: bool, +} + +impl NodeData for MockNode { + type Port = MockPort; + + fn id(&self) -> NodeId { + self.id + } + + fn title(&self) -> SharedString { + self.title.clone() + } + + fn position(&self) -> Point { + self.position + } + + fn inputs(&self) -> Vec { + self.inputs.clone() + } + + fn outputs(&self) -> Vec { + self.outputs.clone() + } + + fn header_color(&self) -> Option { + self.header_color + } + + fn is_collapsed(&self) -> bool { + self.collapsed + } + + fn is_enabled(&self) -> bool { + self.enabled + } +} + +/// An edge in the mock graph: a connection from an output port to an input +/// port. +#[derive(Debug, Clone)] +pub struct MockEdge { + id: EdgeId, + from_node: NodeId, + from_port: PortId, + to_node: NodeId, + to_port: PortId, +} + +impl EdgeData for MockEdge { + fn id(&self) -> EdgeId { + self.id + } + + fn from_node(&self) -> NodeId { + self.from_node + } + + fn from_port(&self) -> PortId { + self.from_port + } + + fn to_node(&self) -> NodeId { + self.to_node + } + + fn to_port(&self) -> PortId { + self.to_port + } +} + // --------------------------------------------------------------------------- // The engine // --------------------------------------------------------------------------- @@ -281,6 +414,19 @@ pub struct MockEngine { selected_item: Option, /// Phase counter driving the demo audio levels. meter_phase: u32, + /// The demo node graph shown in the node editor. + nodes: Vec, + /// The demo node-graph edges. + edges: Vec, + /// Id allocator for edges added at runtime. + next_edge_id: u64, + /// The node selection, kept in sync with the node editor (and, later, the + /// effect stack) so both views share one selection. + node_selection: BTreeSet, + /// Cache of the synthetic CPU frames handed to the viewers, keyed by + /// monitor. Entries are the playhead frame that produced the image, so a + /// paused viewer never regenerates its picture. + cpu_frame_cache: Mutex)>>, } impl MockEngine { @@ -307,8 +453,122 @@ impl MockEngine { l: 0.55, a: 1.0, }; + let node_color = |h: f32| Hsla { + h, + s: 0.55, + l: 0.5, + a: 1.0, + }; - Self { + // The demo node graph: two clips, one through the transform, one + // through the blur, merged in the mixer and LUT'd to the viewer. + // Port ids are globally unique. + let video_type = PortDataType::new("video", hsla(0.55, 0.75, 0.6, 1.0)); + let audio_type = PortDataType::new("audio", hsla(0.1, 0.7, 0.55, 1.0)); + let port = |id: u64, + kind: PortKind, + label: &str, + data_type: &PortDataType| MockPort { + id: PortId(id), + kind, + label: label.into(), + data_type: data_type.clone(), + connected: false, + }; + let node = |id: u64, + title: &str, + position: (f32, f32), + color: f32, + inputs: Vec, + outputs: Vec| MockNode { + id: NodeId(id), + title: title.into(), + position: point(px(position.0), px(position.1)), + inputs, + outputs, + header_color: Some(node_color(color)), + enabled: true, + collapsed: false, + }; + let nodes = vec![ + node( + 0, + "第一稿.mp4", + (40.0, 40.0), + 0.55, + vec![], + vec![port(1, PortKind::Output, "video", &video_type)], + ), + node( + 1, + "B-roll.mp4", + (40.0, 230.0), + 0.60, + vec![], + vec![ + port(3, PortKind::Output, "video", &video_type), + port(4, PortKind::Output, "audio", &audio_type), + ], + ), + node( + 2, + "变换", + (320.0, 40.0), + 0.35, + vec![ + port(20, PortKind::Input, "in", &video_type), + port(22, PortKind::Input, "mask", &video_type), + ], + vec![port(21, PortKind::Output, "out", &video_type)], + ), + node( + 3, + "模糊", + (320.0, 230.0), + 0.78, + vec![port(30, PortKind::Input, "in", &video_type)], + vec![port(31, PortKind::Output, "out", &video_type)], + ), + node( + 4, + "混合", + (600.0, 130.0), + 0.08, + vec![ + port(40, PortKind::Input, "A", &video_type), + port(41, PortKind::Input, "B", &video_type), + ], + vec![port(42, PortKind::Output, "out", &video_type)], + ), + node( + 5, + "输出", + (880.0, 130.0), + 0.0, + vec![port(50, PortKind::Input, "in", &video_type)], + vec![], + ), + ]; + let edge = |id: u64, + from_node: u64, + from_port: u64, + to_node: u64, + to_port: u64| MockEdge { + id: EdgeId(id), + from_node: NodeId(from_node), + from_port: PortId(from_port), + to_node: NodeId(to_node), + to_port: PortId(to_port), + }; + let edges = vec![ + edge(1, 0, 1, 2, 20), + edge(2, 1, 3, 3, 30), + edge(3, 2, 21, 4, 40), + edge(4, 3, 31, 4, 41), + edge(5, 4, 42, 5, 50), + ]; + + let mut this = Self { project: Project { name: "第一稿".into(), path: PathBuf::from("/home/mikesolar/Videos/aaa.ove"), @@ -407,7 +667,16 @@ impl MockEngine { program_playing: false, selected_item: None, meter_phase: 0, - } + nodes, + edges, + next_edge_id: 6, + node_selection: BTreeSet::new(), + cpu_frame_cache: Mutex::new(HashMap::new()), + }; + // The demo graph is born connected: derive every port's `connected` + // flag from the edge list. + this.refresh_port_connectivity(); + this } /// The current sequence length (also used by the gateway). @@ -476,6 +745,134 @@ impl MockEngine { cx.notify(); } + /// Applies a node-editor request to the mock graph (the "edits are + /// requests" loop: the view emits, the engine applies and notifies). + pub fn apply_node_graph_event(&mut self, event: &NodeGraphEvent, cx: &mut Context) { + match event { + NodeGraphEvent::NodeMovePreview { .. } | NodeGraphEvent::ViewChanged { .. } => {} + NodeGraphEvent::NodeMoveRequested { nodes, delta } => { + for id in nodes { + if let Some(node) = self.nodes.iter_mut().find(|n| n.id() == *id) { + node.position = node.position + *delta; + } + } + } + NodeGraphEvent::ConnectionRequested { from, to } => { + if self.can_connect(*from, *to) { + let from_node = self.node_with_port(*from).map(|n| n.id()); + let to_node = self.node_with_port(*to).map(|n| n.id()); + if let (Some(from_node), Some(to_node)) = (from_node, to_node) { + self.edges.push(MockEdge { + id: EdgeId(self.next_edge_id), + from_node, + from_port: *from, + to_node, + to_port: *to, + }); + self.next_edge_id += 1; + self.refresh_port_connectivity(); + } + } + } + NodeGraphEvent::DisconnectionRequested { edge } => { + if let Some(index) = self.edges.iter().position(|e| e.id() == *edge) { + self.edges.remove(index); + self.refresh_port_connectivity(); + } + } + NodeGraphEvent::DeleteRequested { nodes, edges } => { + self.edges.retain(|edge| { + !edges.contains(&edge.id()) + && !nodes.contains(&edge.from_node()) + && !nodes.contains(&edge.to_node()) + }); + self.nodes.retain(|node| !nodes.contains(&node.id())); + self.node_selection.clear(); + self.refresh_port_connectivity(); + } + NodeGraphEvent::SelectionChanged { nodes } => { + self.node_selection = nodes.clone(); + } + NodeGraphEvent::BackgroundClicked { position } => { + // The real app opens an "add node" menu here; the mock logs it. + println!("[node editor] add-node menu at graph {position:?} (mock: ignored)"); + } + } + cx.notify(); + } + + /// The node currently under the cursor selection (demo state; the effect + /// stack will sync to this once it drives the same selection). + pub fn selected_node_ids(&self) -> &BTreeSet { + &self.node_selection + } + + /// Looks up a node by id (test helper). + #[cfg(test)] + fn node(&self, id: NodeId) -> Option<&MockNode> { + self.nodes.iter().find(|n| n.id() == id) + } + + /// Looks up a port by its globally unique id. + fn port(&self, id: PortId) -> Option<&MockPort> { + self.nodes + .iter() + .flat_map(|n| n.inputs.iter().chain(n.outputs.iter())) + .find(|p| p.id() == id) + } + + /// Returns the node that owns `port`. + fn node_with_port(&self, port: PortId) -> Option<&MockNode> { + self.nodes.iter().find(|n| { + n.inputs.iter().any(|p| p.id() == port) + || n.outputs.iter().any(|p| p.id() == port) + }) + } + + /// Whether an edge with the same endpoints already exists. + fn edge_exists(&self, from: PortId, to: PortId) -> bool { + self.edges + .iter() + .any(|e| e.from_port() == from && e.to_port() == to) + } + + /// Whether there is a directed path from node `from` to node `to` + /// following the existing edges (DFS). `from == to` counts as a path. + fn reaches(&self, from: NodeId, to: NodeId) -> bool { + if from == to { + return true; + } + let mut visited = std::collections::HashSet::new(); + let mut stack = vec![from]; + while let Some(current) = stack.pop() { + if !visited.insert(current) { + continue; + } + for edge in &self.edges { + if edge.from_node() == current { + let next = edge.to_node(); + if next == to { + return true; + } + stack.push(next); + } + } + } + false + } + + /// Recomputed every port's `connected` flag from the current edge list. + fn refresh_port_connectivity(&mut self) { + for node in &mut self.nodes { + for port in node.inputs.iter_mut().chain(node.outputs.iter_mut()) { + port.connected = self + .edges + .iter() + .any(|e| e.from_port() == port.id || e.to_port() == port.id); + } + } + } + /// Sets the row height of every timeline track (demo toolbar). pub fn set_track_height(&mut self, height: Pixels, cx: &mut Context) { for track in &mut self.tracks { @@ -649,6 +1046,44 @@ impl EffectStackDataSource for MockEngine { } } +impl NodeGraphDataSource for MockEngine { + type Node = MockNode; + type Edge = MockEdge; + + fn nodes(&self) -> Vec { + self.nodes.clone() + } + + fn edges(&self) -> Vec { + self.edges.clone() + } + + fn can_connect(&self, from: PortId, to: PortId) -> bool { + let (Some(from_node), Some(to_node)) = (self.node_with_port(from), self.node_with_port(to)) + else { + return false; + }; + if from_node.id() == to_node.id() { + return false; + } + let (Some(from_port), Some(to_port)) = (self.port(from), self.port(to)) else { + return false; + }; + if from_port.kind() != PortKind::Output || to_port.kind() != PortKind::Input { + return false; + } + if from_port.data_type() != to_port.data_type() { + return false; + } + if self.edge_exists(from, to) { + return false; + } + // Connecting from_node → to_node would create a cycle iff there is + // already a path from to_node back to from_node. + !self.reaches(to_node.id(), from_node.id()) + } +} + impl ProjectDataSource for MockEngine { fn roots(&self) -> Vec { vec![ @@ -698,6 +1133,87 @@ impl MockEngine { pub fn clock_frame(&self, monitor: Monitor, cx: &App) -> Frame { self.clock(monitor).read(cx).transport.frame() } + + /// The synthetic CPU test frame for `monitor`, cached per playhead frame so + /// a paused viewer never regenerates its picture. This is the frame the + /// source/program viewers display through [`ViewerWidget::set_cpu_frame`], + /// proving the CPU-frame path end to end before the real engine lands. + pub fn cpu_frame(&self, monitor: Monitor, cx: &App) -> Arc { + let frame = self.clock_frame(monitor, cx); + let mut cache = self.cpu_frame_cache.lock().unwrap(); + if let Some((cached_frame, image)) = cache.get(&monitor) { + if *cached_frame == frame.0 { + return image.clone(); + } + } + let image = Arc::new(self.synthetic_frame(frame)); + cache.insert(monitor, (frame.0, image.clone())); + image + } + + /// Generates a synthetic test frame: SMPTE-style color bars with a white + /// sweep whose x position follows `frame`, so playback is visibly moving. + /// + /// Samples are computed as F32 RGBA (mirroring the real engine's pixel + /// pipeline) and downconverted to BGRA8 for the viewer's CPU-frame path. + /// The picture is rendered at a small proxy size ([`SYNTH_FRAME_WIDTH`] × + /// [`SYNTH_FRAME_HEIGHT`]); the real engine delivers full resolution. + fn synthetic_frame(&self, frame: Frame) -> RenderImage { + let width = SYNTH_FRAME_WIDTH; + let height = SYNTH_FRAME_HEIGHT; + + // F32 RGBA samples, then quantized to BGRA8 for the sprite atlas. + let mut samples = vec![0.0f32; (width * height * 4) as usize]; + // SMPTE bars: 75% white, yellow, cyan, green, magenta, red, blue. + let bars: [(f32, f32, f32); 7] = [ + (1.0, 1.0, 1.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 1.0), + (0.0, 1.0, 0.0), + (1.0, 0.0, 1.0), + (1.0, 0.0, 0.0), + (0.0, 0.0, 1.0), + ]; + // Bottom strip: blue, magenta, 75% white, black. + let strip: [(f32, f32, f32); 4] = [ + (0.0, 0.0, 1.0), + (1.0, 0.0, 1.0), + (0.75, 0.75, 0.75), + (0.0, 0.0, 0.0), + ]; + // The sweep moves 6 px per frame and wraps around the width, so + // transport playback shows up as motion across the picture. + let sweep = (frame.0 as f32 * 6.0) % width as f32; + let bars_top = height as f32 * 0.66; + + for y in 0..height { + for x in 0..width { + let in_sweep = (x as f32 - sweep).abs() < 6.0; + let color = if in_sweep { + (1.0, 1.0, 1.0) + } else if (y as f32) < bars_top { + bars[((x as f32 / width as f32) * 7.0) as usize] + } else { + strip[((x as f32 / width as f32) * 4.0) as usize] + }; + let i = ((y * width + x) * 4) as usize; + samples[i] = color.0; + samples[i + 1] = color.1; + samples[i + 2] = color.2; + samples[i + 3] = 1.0; + } + } + + let mut bytes = Vec::with_capacity((width * height * 4) as usize); + for i in (0..samples.len()).step_by(4) { + bytes.push((samples[i + 2] * 255.0) as u8); // B + bytes.push((samples[i + 1] * 255.0) as u8); // G + bytes.push((samples[i] * 255.0) as u8); // R + bytes.push((samples[i + 3] * 255.0) as u8); // A + } + let buffer = image::RgbaImage::from_raw(width, height, bytes).expect("synthetic frame"); + RenderImage::new(smallvec::SmallVec::from_elem(image::Frame::new(buffer), 1)) + } } #[cfg(test)] @@ -799,4 +1315,183 @@ mod tests { assert_eq!(before, after); }); } + + #[gpui::test] + async fn demo_graph_has_six_nodes_and_five_edges(cx: &mut TestAppContext) { + cx.update(|app| { + let engine = demo_engine(app); + let engine = engine.read(app); + let nodes = engine.nodes(); + let edges = engine.edges(); + + assert_eq!(nodes.len(), 6, "media, transform, blur, mixer, viewer"); + assert_eq!(edges.len(), 5); + + // The chain ends at the viewer node, fed by the mixer. + let viewer = nodes + .iter() + .find(|n| n.title() == "输出") + .expect("viewer node"); + assert_eq!(viewer.inputs().len(), 1); + assert!(viewer.outputs().is_empty()); + + // Every edge endpoint references an existing port, and every port + // connectivity flag matches the edge list. + for edge in &edges { + assert!( + nodes.iter().any(|n| n.id() == edge.from_node()), + "edge {} from-node exists", + edge.id().0 + ); + assert!( + nodes.iter().any(|n| n.id() == edge.to_node()), + "edge {} to-node exists", + edge.id().0 + ); + } + for node in nodes { + for port in node.inputs().into_iter().chain(node.outputs()) { + let expected = engine + .edges() + .iter() + .any(|e| e.from_port() == port.id() || e.to_port() == port.id()); + assert_eq!(port.is_connected(), expected, "port {}", port.id().0); + } + } + }); + } + + #[gpui::test] + async fn can_connect_enforces_direction_type_duplicates_and_cycles( + cx: &mut TestAppContext, + ) { + cx.update(|app| { + let engine = demo_engine(app); + let engine = engine.read(app); + + // A valid, still-free connection: Clip1.video → Transform.in (the + // transform already takes the clip-0 path, so this would be the + // second input). + assert!(engine.can_connect(PortId(3), PortId(20))); + // The existing connection is not offered again. + assert!(!engine.can_connect(PortId(21), PortId(40)), "duplicate edge"); + // Input → output is rejected (wrong direction). + assert!(!engine.can_connect(PortId(20), PortId(1)), "wrong direction"); + // A port cannot connect to itself. + assert!(!engine.can_connect(PortId(42), PortId(40)), "self connection"); + // Type mismatch: the clip's audio output is not a video signal. + assert!( + !engine.can_connect(PortId(4), PortId(20)), + "audio cannot feed a video input" + ); + // Unknown ports are rejected. + assert!(!engine.can_connect(PortId(999), PortId(20))); + assert!(!engine.can_connect(PortId(1), PortId(999))); + + // Cycle rule: the graph flows left-to-right (Clip → Transform → + // Mixer → Viewer), so no connection can close a loop — but the + // reachability helper behind the rule is exercised directly. + assert!(engine.reaches(NodeId(0), NodeId(5)), "main chain path"); + assert!(!engine.reaches(NodeId(5), NodeId(0)), "no backward path"); + assert!(engine.reaches(NodeId(4), NodeId(4))); + }); + } + + #[gpui::test] + async fn node_edits_apply_to_the_model(cx: &mut TestAppContext) { + cx.update(|app| { + let engine = demo_engine(app); + // Move the viewer node. + engine.update(app, |engine, cx| { + engine.apply_node_graph_event( + &NodeGraphEvent::NodeMoveRequested { + nodes: vec![NodeId(5)], + delta: point(px(100.0), px(-20.0)), + }, + cx, + ); + }); + let moved = engine.read(app).node(NodeId(5)).expect("viewer node"); + assert_eq!(moved.position(), point(px(980.0), px(110.0))); + + // Connect the B-roll clip's video to the transform's unused mask + // input, then disconnect the transform → mixer edge. + engine.update(app, |engine, cx| { + engine.apply_node_graph_event( + &NodeGraphEvent::ConnectionRequested { + from: PortId(3), + to: PortId(22), + }, + cx, + ); + }); + let engine_read = engine.read(app); + assert_eq!(engine_read.edges().len(), 6); + assert!(engine_read.port(PortId(22)).is_some_and(|p| p.is_connected())); + let edges = engine_read.edges(); + let extra = edges + .iter() + .find(|e| e.to_port() == PortId(22)) + .expect("the new connection"); + + engine.update(app, |engine, cx| { + engine.apply_node_graph_event( + &NodeGraphEvent::DisconnectionRequested { edge: extra.id() }, + cx, + ); + }); + assert_eq!(engine.read(app).edges().len(), 5); + assert!( + !engine.read(app).port(PortId(22)).is_some_and(|p| p.is_connected()), + "mask input freed again" + ); + + // Deleting the blur node takes its incident edges with it. + engine.update(app, |engine, cx| { + engine.apply_node_graph_event( + &NodeGraphEvent::DeleteRequested { + nodes: vec![NodeId(3)], + edges: vec![], + }, + cx, + ); + }); + let engine_read = engine.read(app); + assert_eq!(engine_read.nodes().len(), 5); + assert!( + !engine_read.edges().iter().any(|e| e.to_node() == NodeId(3) + || e.from_node() == NodeId(3)), + "edges incident to the deleted node are removed" + ); + }); + } + + #[gpui::test] + async fn cpu_frame_is_cached_per_playhead_and_advances(cx: &mut TestAppContext) { + cx.update(|app| { + let engine = demo_engine(app); + + // Same playhead → same cached image (Arc identity), so a paused + // viewer never regenerates its picture. + let a = engine.read(app).cpu_frame(Monitor::Program, app); + let b = engine.read(app).cpu_frame(Monitor::Program, app); + assert!(Arc::ptr_eq(&a, &b), "paused frame must be cached"); + + // Advancing the playhead produces a different image: the sweep + // moved, so playback is visible. + engine.update(app, |engine, cx| { + engine.step(Monitor::Program, 25, cx); + }); + let c = engine.read(app).cpu_frame(Monitor::Program, app); + assert!(!Arc::ptr_eq(&a, &c), "a new playhead frame regenerates"); + + // The frame has the documented proxy size and opaque BGRA8 bytes. + let size = c.size(0); + assert_eq!(size.width, SYNTH_FRAME_WIDTH.into()); + assert_eq!(size.height, SYNTH_FRAME_HEIGHT.into()); + let bytes = c.as_bytes(0).expect("single frame"); + assert_eq!(bytes.len(), (SYNTH_FRAME_WIDTH * SYNTH_FRAME_HEIGHT * 4) as usize); + assert!(bytes.chunks_exact(4).all(|px| px[3] == 255), "opaque alpha"); + }); + } } diff --git a/app/rust/src/panels/mod.rs b/app/rust/src/panels/mod.rs index 6c50b3fd5..b09438b9a 100644 --- a/app/rust/src/panels/mod.rs +++ b/app/rust/src/panels/mod.rs @@ -43,7 +43,7 @@ pub mod ids { pub const SOURCE_VIEWER: PanelId = PanelId::new(2); /// The program viewer (序列查看器). pub const PROGRAM_VIEWER: PanelId = PanelId::new(3); - /// The node editor placeholder (节点编辑器). + /// The node editor (节点编辑器). pub const NODE_EDITOR: PanelId = PanelId::new(4); /// The inspector / effect stack (检查器·效果栈). pub const INSPECTOR: PanelId = PanelId::new(5); diff --git a/app/rust/src/panels/node_editor.rs b/app/rust/src/panels/node_editor.rs index 06178c76e..9c36d291f 100644 --- a/app/rust/src/panels/node_editor.rs +++ b/app/rust/src/panels/node_editor.rs @@ -14,35 +14,139 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! The node editor panel (节点编辑器): a placeholder tab sharing the program -//! viewer's dock group. +//! The node editor panel (节点编辑器): the real `gpui::node_graph` canvas over +//! the engine's mock graph, with the design's zoom controls (+ / − / 适配). //! -//! The design puts the node editor in the center, switchable with the program -//! viewer. The real `gpui::node_graph` widget exists in the gpui submodule -//! but is not wired up yet — this panel is a placeholder surface with the -//! zoom controls the design specifies (+ / − / fit). +//! The graph is a full [`NodeGraphView`] fed by the [`MockEngine`]'s +//! [`NodeGraphDataSource`] implementation. Every gesture the view emits +//! (move, connect, disconnect, delete, selection) is forwarded to the engine +//! as a request; the engine applies it to its model and notifies, so the +//! view re-reads on the next frame. The toolbar buttons drive the viewport +//! directly: zoom in/out at the canvas center, or fit the whole graph. use gpui::colors::DefaultColors; use gpui::dock::{DockPanel, PanelEvent}; +use gpui::node_graph::{ + MAX_ZOOM, MIN_ZOOM, NodeData, NodeElement, NodeGraphDataSource, NodeGraphEvent, NodeGraphView, + NodeVisualState, +}; use gpui::{ - div, prelude::*, AnyElement, App, ClickEvent, Context, EventEmitter, Render, SharedString, - Window, + div, point, prelude::*, px, AnyElement, App, Bounds, ClickEvent, Context, Entity, + EventEmitter, Pixels, Render, SharedString, Window, }; +use crate::oakui::MockEngine; use crate::panels::ids::NODE_EDITOR; -/// The node editor placeholder panel. -pub struct NodeEditorPanel; +/// The node editor panel. +pub struct NodeEditorPanel { + /// The node-graph canvas over the engine's mock graph. + graph: Entity>, + engine: Entity, + /// Whether the initial fit-to-window has been applied (the canvas size is + /// only known after the first layout). + fitted: bool, +} impl NodeEditorPanel { - /// Creates the placeholder. - pub fn new(_window: &mut Window, _cx: &mut Context) -> Self { - Self + /// Builds the graph canvas over `engine` and routes its edit requests back + /// to the engine. + pub fn new( + engine: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let graph = cx.new(|cx| NodeGraphView::new(engine.clone(), window, cx)); + // The "edits are requests" loop: every graph gesture goes to the + // engine, which applies it to its model and notifies. + cx.subscribe(&graph, |this, _graph, event: &NodeGraphEvent, cx| { + this.engine + .update(cx, |engine, cx| engine.apply_node_graph_event(event, cx)); + }) + .detach(); + + Self { + graph, + engine, + fitted: false, + } + } + + /// The union of every node's bounds in graph space, if the graph is + /// non-empty. + fn graph_bounds(&self, cx: &App) -> Option> { + let nodes = self.engine.read(cx).nodes(); + if nodes.is_empty() { + return None; + } + let mut min_x = f32::MAX; + let mut min_y = f32::MAX; + let mut max_x = f32::MIN; + let mut max_y = f32::MIN; + for node in &nodes { + let element = NodeElement::from_node(node, NodeVisualState::default()); + let position = node.position(); + let width = gpui::node_graph::DEFAULT_NODE_WIDTH; + let height = element.height(); + let (x, y) = (f32::from(position.x), f32::from(position.y)); + min_x = min_x.min(x); + min_y = min_y.min(y); + max_x = max_x.max(x + f32::from(width)); + max_y = max_y.max(y + f32::from(height)); + } + Some(Bounds::from_corners( + point(px(min_x), px(min_y)), + point(px(max_x), px(max_y)), + )) + } + + /// The canvas size to fit against: the graph view's own painted size once + /// known, otherwise the window (before the first layout). + fn fit_viewport(&self, window: &Window, cx: &App) -> gpui::Size { + let viewport = self.graph.read(cx).viewport_size(); + if viewport.width > px(0.0) && viewport.height > px(0.0) { + viewport + } else { + window.viewport_size() + } + } + + /// Fits the whole graph into the canvas (the 适配 button). + fn fit_graph(&mut self, window: &mut Window, cx: &mut Context) { + let Some(rect) = self.graph_bounds(cx) else { + return; + }; + let viewport = self.fit_viewport(window, cx); + self.graph.update(cx, |graph, cx| { + graph.state_mut().fit_to_rect(rect, viewport); + cx.notify(); + }); + } + + /// Zooms the canvas by `factor` at its center (`+` / `−` buttons). + fn zoom(&mut self, factor: f32, window: &mut Window, cx: &mut Context) { + let viewport = self.fit_viewport(window, cx); + let anchor = point(viewport.width * 0.5, viewport.height * 0.5); + self.graph.update(cx, |graph, cx| { + graph.state_mut().zoom_at(anchor, factor); + cx.notify(); + }); } } impl Render for NodeEditorPanel { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + // Fit the graph once the canvas size is known (first layout). Before + // that the viewport is zero-sized, so ask for another frame instead. + if !self.fitted { + if self.graph.read(cx).viewport_size() != Default::default() { + self.fitted = true; + self.fit_graph(window, cx); + } else { + cx.notify(); + } + } + let colors = cx.default_colors().clone(); div() .size_full() @@ -57,43 +161,46 @@ impl Render for NodeEditorPanel { .py_1() .border_b_1() .border_color(colors.border) - .child(zoom_button( - cx, - "node-zoom-in", - "+", - crate::i18n::tr("node.zoom_in"), - )) - .child(zoom_button( - cx, - "node-zoom-out", - "−", - crate::i18n::tr("node.zoom_out"), - )) + .child(zoom_button(cx, "node-zoom-in", "+", |this, window, cx| { + this.zoom(1.25, window, cx); + })) + .child(zoom_button(cx, "node-zoom-out", "−", |this, window, cx| { + this.zoom(1.0 / 1.25, window, cx); + })) .child(zoom_button( cx, "node-zoom-fit", crate::i18n::tr("node.fit"), - crate::i18n::tr("node.fit_window"), - )), + |this, window, cx| this.fit_graph(window, cx), + )) + .child(div().flex_1()) + .child( + div() + .text_color(colors.disabled) + .child(format!( + "{}% · {}–{}", + (self.graph.read(cx).state().zoom() * 100.0).round(), + MIN_ZOOM, + MAX_ZOOM, + )), + ), ) .child( div() + .debug_selector(|| "node-editor-canvas".into()) .flex_1() - .flex() - .items_center() - .justify_center() - .text_color(colors.disabled) - .child(crate::i18n::tr("node.placeholder")), + .min_h_0() + .child(self.graph.clone()), ) } } -/// A small toolbar button (the design's `+`/`−`/`适配` controls). +/// A small toolbar button driving the graph viewport. fn zoom_button( cx: &mut Context, id: &'static str, - label: &'static str, - title: &'static str, + label: impl IntoElement, + action: impl Fn(&mut NodeEditorPanel, &mut Window, &mut Context) + 'static, ) -> impl gpui::IntoElement { let colors = cx.default_colors().clone(); let container = colors.container; @@ -107,11 +214,9 @@ fn zoom_button( .text_color(colors.text) .cursor_pointer() .hover(move |style| style.bg(container)) - .on_click( - cx.listener(move |_this, _event: &ClickEvent, _window, _cx| { - println!("[node editor] {title} (placeholder)"); - }), - ) + .on_click(cx.listener(move |this, _event: &ClickEvent, window, cx| { + action(this, window, cx); + })) .child(label) } @@ -132,3 +237,55 @@ impl DockPanel for NodeEditorPanel { .into_any_element() } } + +#[cfg(test)] +mod tests { + use super::*; + use gpui::{TestAppContext, VisualTestContext, size}; + + /// Builds the panel in a window and returns a `VisualTestContext` for + /// bounds assertions. + fn panel_window( + cx: &mut TestAppContext, + ) -> (&'static mut VisualTestContext, Entity) { + cx.update(|cx| cx.init_colors()); + let window = cx.open_window(size(px(640.0), px(480.0)), |window, cx| { + let engine = cx.new(|cx| crate::oakui::MockEngine::demo(cx)); + NodeEditorPanel::new(engine, window, cx) + }); + cx.run_until_parked(); + // The graph canvas reports its size only after the first paint, so the + // initial fit applies on the following frame: draw a few more. + for _ in 0..3 { + cx.update_window(window.into(), |_root, window, app| { + let _ = window.draw(app); + }) + .expect("window still open"); + cx.run_until_parked(); + } + let panel = window.root(cx).expect("node editor panel root"); + let cx = VisualTestContext::from_window(window.into(), cx).into_mut(); + (cx, panel) + } + + /// The panel lays out a graph canvas below the zoom toolbar, and the + /// initial fit centers the graph so every demo node is on screen. + #[gpui::test] + async fn canvas_fills_the_panel_below_the_toolbar(cx: &mut TestAppContext) { + let (cx, panel) = panel_window(cx); + + let canvas = cx + .debug_bounds("node-editor-canvas") + .expect("graph canvas rendered"); + assert!(canvas.size.width > px(0.0)); + assert!(canvas.size.height > px(0.0)); + + // The initial fit moved the viewport off the default origin, so the + // graph is framed rather than clipped at the corner. + let state = cx.read(|app| { + panel.read(app).graph.read(app).state().clone() + }); + assert_ne!(state.offset(), gpui::node_graph::GraphViewState::new().offset()); + assert!(state.zoom() > 0.0); + } +} diff --git a/app/rust/src/panels/program_viewer.rs b/app/rust/src/panels/program_viewer.rs index d0acfe425..92a41839a 100644 --- a/app/rust/src/panels/program_viewer.rs +++ b/app/rust/src/panels/program_viewer.rs @@ -40,6 +40,9 @@ pub struct ProgramViewerPanel { viewer: Entity>, meter: Entity>, engine: Entity, + /// The last CPU frame handed to the viewer (compared by `Arc` identity so + /// a paused playhead does not re-upload the picture every frame). + last_cpu_frame: Option>, } impl ProgramViewerPanel { @@ -69,12 +72,28 @@ impl ProgramViewerPanel { viewer, meter, engine, + last_cpu_frame: None, + } + } + + /// Pushes the engine's synthetic test frame into the viewer, but only when + /// it actually changed (the engine caches one image per playhead frame). + fn sync_frame(&mut self, cx: &mut Context) { + let frame = self.engine.read(cx).cpu_frame(Monitor::Program, cx); + if self.last_cpu_frame.as_ref().is_none_or(|last| !std::sync::Arc::ptr_eq(last, &frame)) + { + self.last_cpu_frame = Some(frame.clone()); + let frame = frame.clone(); + self.viewer + .update(cx, |viewer, cx| viewer.set_cpu_frame(Some(frame), cx)); } } } impl Render for ProgramViewerPanel { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.sync_frame(cx); + let colors = cx.default_colors().clone(); let format = self .engine diff --git a/app/rust/src/panels/source_viewer.rs b/app/rust/src/panels/source_viewer.rs index 380710a44..a27ccfd3b 100644 --- a/app/rust/src/panels/source_viewer.rs +++ b/app/rust/src/panels/source_viewer.rs @@ -20,7 +20,8 @@ use gpui::colors::DefaultColors; use gpui::dock::{DockPanel, PanelEvent}; use gpui::{ - div, prelude::*, AnyElement, App, Context, Entity, EventEmitter, Render, SharedString, Window, + div, prelude::*, AnyElement, App, Context, Entity, EventEmitter, Render, SharedString, + Window, }; use gpui_widgets::viewer::{ViewerEvent, ViewerWidget}; @@ -33,6 +34,9 @@ use crate::panels::ids::SOURCE_VIEWER; pub struct SourceViewerPanel { viewer: Entity>, engine: Entity, + /// The last CPU frame handed to the viewer (compared by `Arc` identity so + /// a paused playhead does not re-upload the picture every frame). + last_cpu_frame: Option>, } impl SourceViewerPanel { @@ -56,12 +60,31 @@ impl SourceViewerPanel { }) .detach(); - Self { viewer, engine } + Self { + viewer, + engine, + last_cpu_frame: None, + } + } + + /// Pushes the engine's synthetic test frame into the viewer, but only when + /// it actually changed (the engine caches one image per playhead frame). + fn sync_frame(&mut self, cx: &mut Context) { + let frame = self.engine.read(cx).cpu_frame(Monitor::Source, cx); + if self.last_cpu_frame.as_ref().is_none_or(|last| !std::sync::Arc::ptr_eq(last, &frame)) + { + self.last_cpu_frame = Some(frame.clone()); + let frame = frame.clone(); + self.viewer + .update(cx, |viewer, cx| viewer.set_cpu_frame(Some(frame), cx)); + } } } impl Render for SourceViewerPanel { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.sync_frame(cx); + let colors = cx.default_colors().clone(); let format = self .engine diff --git a/cli/rust/Cargo.lock b/cli/rust/Cargo.lock index 1a42e595d..4af6b8780 100644 --- a/cli/rust/Cargo.lock +++ b/cli/rust/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "1.0.0" @@ -52,6 +76,155 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +dependencies = [ + "find-msvc-tools", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.6.6" @@ -83,7 +256,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -92,35 +265,742 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "ffmpeg-next" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a" +dependencies = [ + "bitflags 2.13.1", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b" +dependencies = [ + "bindgen", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" +dependencies = [ + "bitflags 2.13.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.1", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.15.5", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "strum", + "thiserror 2.0.20", + "unicode-ident", +] + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "oak-cli" version = "0.1.0" dependencies = [ "clap", - "oakfacade", + "oakengine", ] [[package]] -name = "oakfacade" +name = "oakaudio" version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "oakcodec" +version = "0.1.0" +dependencies = [ + "ffmpeg-next", + "oakcore-rs", +] + +[[package]] +name = "oakcommon" +version = "0.1.0" +dependencies = [ + "image", + "log", + "oakcore-rs", + "ocio-rs", + "quick-xml", +] + +[[package]] +name = "oakcore-rs" +version = "0.1.0" + +[[package]] +name = "oakengine" +version = "0.1.0" +dependencies = [ + "libc", + "oakaudio", + "oakcodec", + "oakcommon", + "oakcore-rs", + "oaknode", + "oakplugin", + "oakrender", + "oaktask", + "oaktimeline", + "oakundo", + "serde", + "serde_json", +] + +[[package]] +name = "oaknode" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "oakotio" +version = "0.1.0" +dependencies = [ + "oakcore-rs", + "quick-xml", + "serde", + "serde_json", +] + +[[package]] +name = "oakplugin" +version = "0.1.0" +dependencies = [ + "cc", +] + +[[package]] +name = "oakrender" +version = "0.1.0" +dependencies = [ + "oakcore-rs", + "ocio-rs", + "wgpu", +] + +[[package]] +name = "oaktask" +version = "0.1.0" +dependencies = [ + "oakcore-rs", + "oakotio", +] + +[[package]] +name = "oaktimeline" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "oakundo" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "ocio-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3492534019b59e29dba06014f907dd12824537ed4d293d4108c4bfc669de7fd" +dependencies = [ + "ocio-sys", + "thiserror 1.0.69", +] + +[[package]] +name = "ocio-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63251d72d848de5eda39d59cd6490260cf031738ebd518ea37d76b5aae614ec" +dependencies = [ + "cc", + "cmake", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -128,6 +1008,68 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro2" version = "1.0.107" @@ -137,6 +1079,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.47" @@ -146,12 +1115,223 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "3.0.3" @@ -163,24 +1343,396 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +dependencies = [ + "arrayvec", + "bitflags 2.13.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.5", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.5", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.20", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.1", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.15.5", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "ordered-float", + "parking_lot", + "portable-atomic", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.20", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-types" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.20", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -189,3 +1741,114 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/cli/rust/Cargo.toml b/cli/rust/Cargo.toml index 826e6bbec..c8e8d5804 100644 --- a/cli/rust/Cargo.toml +++ b/cli/rust/Cargo.toml @@ -37,7 +37,7 @@ clap = { version = "4", features = ["derive"] } # deferral (src/deferred.rs). The extern declarations in src/ffi.rs mirror # the engine headers verbatim and resolve against this rlib the moment a # family is wrapped -- no manifest change needed. -oakfacade = { path = "../../src/facade/rust" } +oakengine = { path = "../../src/engine/rust" } [profile.release] panic = "unwind" diff --git a/cli/rust/src/deferred.rs b/cli/rust/src/deferred.rs index 568c31a82..4f1b565bd 100644 --- a/cli/rust/src/deferred.rs +++ b/cli/rust/src/deferred.rs @@ -17,7 +17,7 @@ //! Facade-family availability, mirroring `src/facade/rust/src/deferred.rs`. //! //! Every `oak-cli` subcommand depends on one or more families of the -//! `oakengine_*` C ABI. Those families live in the `oakfacade` crate, and +//! `oakengine_*` C ABI. Those families live in the `oakengine` crate, and //! some of them are **deferred**: the facade does not wrap them yet, so the //! subcommands must report a clear "not yet available" error instead of //! calling into the facade (the calls would not link, and faking behavior @@ -47,7 +47,7 @@ pub struct DeferredFamily { pub const INIT: DeferredFamily = DeferredFamily { name: "init", headers: "init.h", - reason: "the facade shell (oakengine_init/shutdown) is not wrapped in oakfacade yet (its scope table covers only undo/common/audio/plugin)", + reason: "the facade shell (oakengine_init/shutdown) is not wrapped in oakengine yet (its scope table covers only undo/common/audio/plugin)", }; /// `project.h` + `footage.h` — the oaknode module family. @@ -80,13 +80,13 @@ pub const TIMELINE: DeferredFamily = DeferredFamily { pub const RENDER: DeferredFamily = DeferredFamily { name: "render", headers: "renderer.h", - reason: "deferred for session scope: the engine renderer.h family is not wrapped in oakfacade yet (no structural blocker)", + reason: "deferred for session scope: the engine renderer.h family is not wrapped in oakengine yet (no structural blocker)", }; /// `exporter.h` — export/encode family. /// /// Facade deferred.rs: exporter is a "genuinely facade-only area" (the -/// liboakengine assembly layer) with no files in the oakfacade crate. +/// liboakengine assembly layer) with no files in the oakengine crate. pub const EXPORT: DeferredFamily = DeferredFamily { name: "exporter", headers: "exporter.h", @@ -107,7 +107,7 @@ pub fn require(families: &[&DeferredFamily]) -> Result<(), String> { detail.push_str(&format!("\n - {} ({}): {}", f.name, f.headers, f.reason)); } Err(format!( - "not yet available in the Rust facade (oakfacade): these family(ies) are still deferred \ + "not yet available in the Rust facade (oakengine): these family(ies) are still deferred \ (see src/facade/rust/src/deferred.rs):{detail}" )) } @@ -126,7 +126,7 @@ mod tests { let err = require(&[&INIT]).unwrap_err(); assert!(err.contains("not yet available")); assert!(err.contains("init")); - assert!(err.contains("oakfacade")); + assert!(err.contains("oakengine")); } #[test] diff --git a/cli/rust/src/ffi.rs b/cli/rust/src/ffi.rs index 424331867..98353b2b7 100644 --- a/cli/rust/src/ffi.rs +++ b/cli/rust/src/ffi.rs @@ -28,15 +28,15 @@ //! - `engine/include/oakengine/exporter.h` (export options + render) //! //! All of these families are **deferred** in the Rust facade crate -//! (`oakfacade`, `src/facade/rust/src/deferred.rs`), so none of the symbols +//! (`oakengine`, `src/facade/rust/src/deferred.rs`), so none of the symbols //! below is referenced from this crate yet — the subcommands gate on //! [`crate::deferred`] and report "not yet available" instead of calling //! them. The declarations exist so that: //! //! 1. the exact contract the CLI expects is pinned in one place (types, //! signatures, string conventions, error codes), and -//! 2. when a family is wrapped by oakfacade, the call-through code in -//! `src/cmd/` resolves against the already-linked `oakfacade` rlib +//! 2. when a family is wrapped by oakengine, the call-through code in +//! `src/cmd/` resolves against the already-linked `oakengine` rlib //! without any manifest or signature churn. //! //! Nothing here is ever called today, so no symbol needs to exist in the diff --git a/cli/rust/src/main.rs b/cli/rust/src/main.rs index f7fdcf729..400f9d94f 100644 --- a/cli/rust/src/main.rs +++ b/cli/rust/src/main.rs @@ -30,7 +30,7 @@ //! 64 usage error. //! //! The facade families every subcommand depends on (init/project/timeline/ -//! render/footage/exporter) are still **deferred** in the `oakfacade` crate +//! render/footage/exporter) are still **deferred** in the `oakengine` crate //! (see `src/facade/rust/src/deferred.rs`), so each subcommand validates its //! arguments faithfully, then reports the deferral with its reason and exits //! with the C++-compatible code — never crashing, never faking output. diff --git a/cli/rust/tests/cli.rs b/cli/rust/tests/cli.rs index a29163911..677b725fc 100644 --- a/cli/rust/tests/cli.rs +++ b/cli/rust/tests/cli.rs @@ -71,7 +71,9 @@ fn info_on_a_fixture_reports_not_yet_available() { let (code, _stdout, stderr) = run(&["info", "tests/project_with_footage.ove"]); assert_eq!(code, 1); assert!(stderr.contains("error: info: not yet available"), "stderr: {stderr}"); - assert!(stderr.contains("oakfacade")); + // The crate was renamed oakfacade -> oakengine; the deferral reason + // names the current crate. + assert!(stderr.contains("oakengine")); } #[test] diff --git a/gpui b/gpui index 16ae7c42d..49e471ae6 160000 --- a/gpui +++ b/gpui @@ -1 +1 @@ -Subproject commit 16ae7c42dfebee0901aa27620bfc2e04dfbc1eb0 +Subproject commit 49e471ae64a1dc9e15237b14cc69c3dbcef48ca1 diff --git a/src/facade/rust/Cargo.lock b/src/engine/rust/Cargo.lock similarity index 99% rename from src/facade/rust/Cargo.lock rename to src/engine/rust/Cargo.lock index 1157f05c0..42ba8cd8e 100644 --- a/src/facade/rust/Cargo.lock +++ b/src/engine/rust/Cargo.lock @@ -784,13 +784,14 @@ name = "oakcore-rs" version = "0.1.0" [[package]] -name = "oakfacade" +name = "oakengine" version = "0.1.0" dependencies = [ "libc", "oakaudio", "oakcodec", "oakcommon", + "oakcore-rs", "oaknode", "oakplugin", "oakrender", diff --git a/src/engine/rust/Cargo.toml b/src/engine/rust/Cargo.toml new file mode 100644 index 000000000..52287ad60 --- /dev/null +++ b/src/engine/rust/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "oakengine" +version = "0.1.0" +edition = "2021" +description = "Oak Video Editor facade: re-exports the frozen oakengine_* C ABI over the module C ABIs (Rust)" +license = "GPL-3.0-or-later" + +[lib] +crate-type = ["cdylib", "staticlib", "rlib"] + +[profile.release] +# FFI discipline: panics must be catchable at every exported entry. +panic = "unwind" + +[dependencies] +# NDJSON control-plane protocol for the worker session (src/worker.rs) and +# the shm error formatting in src/ipc.rs. +serde = { version = "1", features = ["derive"] } +serde_json = "1" +# POSIX shm_open/mmap/munmap/shm_unlink constants + syscalls for the +# shared-memory frame-slot transport (src/ipc.rs). +libc = "0.2" + +# Every module call crosses the module C ABI as an `extern "C"` import +# (src/bridge/). The module crates below are REAL dependencies so their +# `#[no_mangle]` exports are linked into the final `liboakengine` cdylib: +# the dylib then carries the module C ABIs itself (oakundo_*, oakcommon_*, +# oaktimeline_*, oakcodec_*, oakaudio_*, oakrender_*, oaktask_*, +# oakplugin_*, oaknode_*) alongside the facade's oakengine_* exports. +# +# The crates are linked WITHOUT their `test-stubs` features so the real +# exports ship. Cross-module calls that the modules resolve with +# dlsym(RTLD_DEFAULT) (oaknode, oakplugin, oakrender) now resolve against +# the sibling modules inside the same dylib; the remaining undefined +# imports are the C++ host-provided symbols (`oakcore_audioparams_*`, +# `oakcore_rational_*` from liboakcore, `fb_*` from ffmpeg_bridge), which +# build.rs leaves as runtime lookups for the host app. +# +# Tests link the same crates; the dev-dependencies below re-declare +# oakcommon/oakplugin WITH `test-stubs` so their in-crate C ABI mocks +# (the ffmpeg_bridge replacement and the oakrender dlsym stubs) are +# compiled into the test binaries (features union with the normal +# dependencies for test builds). +# +# NOTE (oaktimeline/oaktask): their `test-stubs` features are never used +# here — the in-crate mocks define `oakundo_command_init` etc., which +# would collide with the real oakundo rlib in one binary. Without +# test-stubs their real exports reference the oaknode/oakundo/oakcommon +# C ABI symbols as link-time externs, provided by the sibling crates in +# the same dylib (or by the dev-dependency rlibs in a test binary). +# +# rustc would normally prune rlibs that are only touched through +# `extern "C"` imports from the link; src/linkage.rs anchors every crate +# (and oakcore-rs) so the linker pulls their object files. +oakcore-rs = { path = "../../oakcore-rs" } +oakundo = { path = "../../undo/rust" } +oakcommon = { path = "../../common/rust" } +oaktimeline = { path = "../../timeline/rust" } +oakcodec = { path = "../../codec/rust" } +oakaudio = { path = "../../audio/rust" } +oakrender = { path = "../../render/rust" } +oaktask = { path = "../../task/rust" } +oakplugin = { path = "../../plugin/rust" } +oaknode = { path = "../../node/rust" } + +[dev-dependencies] +# Test-only feature union (see the comment above): tests keep the +# in-crate mocks these features compile. +oakcommon = { path = "../../common/rust", features = ["test-stubs"] } +oakplugin = { path = "../../plugin/rust", features = ["test-stubs"] } diff --git a/src/facade/rust/README.md b/src/engine/rust/README.md similarity index 84% rename from src/facade/rust/README.md rename to src/engine/rust/README.md index fbb906d3c..88774e5e3 100644 --- a/src/facade/rust/README.md +++ b/src/engine/rust/README.md @@ -42,9 +42,16 @@ tests/ The facade's regular dependencies are `serde`/`serde_json` (the worker's NDJSON control-plane protocol, `src/worker.rs`) and `libc` (POSIX `shm_open`/`mmap`/`munmap`/`shm_unlink` for `src/ipc.rs`). Every module -call still crosses the module C ABI as an `extern "C"` import -(`src/bridge/`), resolved at the final app link against the module shared -libraries. +call crosses the module C ABI as an `extern "C"` import (`src/bridge/`), +and the module crates themselves are real dependencies: [`linkage`](src/linkage.rs) +anchors them so their `#[no_mangle]` exports are linked into the +`liboakengine` cdylib — the dylib carries the module C ABIs (oakundo_*, +oakcommon_*, oaktimeline_*, oakcodec_*, oakaudio_*, oakrender_*, +oaktask_*, oakplugin_*, oaknode_*) next to the facade's oakengine_* +exports. The only remaining imports are the C++ host symbols +(`oakcore_*` from liboakcore, `fb_*` from ffmpeg_bridge), which +`build.rs` leaves as runtime lookups (macOS `-undefined dynamic_lookup`) +resolved from the host Oak process. ### Handle mapping @@ -89,16 +96,16 @@ control-plane message serializers remain unwrapped. ## Testing -`cargo test` links the module crates' rlibs (dev-dependencies) so the -facade's bridge imports resolve: +The module crates are real dependencies, so `cargo test` links the same +rlibs the cdylib embeds; the dev-dependencies re-declare +`oakcommon`/`oakplugin` with their `test-stubs` features so the test +binaries keep the in-crate mocks (ffmpeg_bridge stub / render mocks): -- `oakcommon`/`oakplugin` use their `test-stubs` features (ffmpeg_bridge - stub / in-crate render mocks). - `oaknode`/`oaktimeline`/`oaktask` are linked WITHOUT their `test-stubs` features: their in-crate mocks would collide with the real oakundo rlib in one test binary. Without test-stubs their real exports reference the oaknode/oakundo/oakcommon C ABI symbols as link-time externs, which the - dev-dependency rlibs provide; oaknode itself resolves cross-module + sibling crate rlibs provide; oaknode itself resolves cross-module symbols at runtime with `dlsym(RTLD_DEFAULT)`. - `tests/common/mod.rs` defines the `oakcore_*` (liboakcore) and `fb_*` (libffmpeg_bridge) symbols the oakcodec/oakaudio rlibs reference, and @@ -106,7 +113,9 @@ facade's bridge imports resolve: factory so the oaknode serializer's dlsym lookups resolve in every test binary. - `src/lib.rs`'s test-only `test_link` forces the oakrender/oaknode/ - oaktimeline/oaktask rlibs into the lib unit-test binary. + oaktimeline/oaktask rlibs into the lib unit-test binary (the always-on + `src/linkage.rs` anchors are `#[cfg(not(test))]`; they are what embeds + the module C ABIs in the cdylib for `cargo build`). Families whose wrapped behavior requires the real module dylibs carry `#[ignore]` tests with a documented reason; the smoke tests here exercise @@ -118,7 +127,8 @@ payloads in both directions, including wraparound and full/empty edges. cargo test # 71 tests green + 1 ignored (lib 35: ipc 17 + worker 18; # integration: undo 3, common 4, audio 4, plugin 3, codec 5, # render 6, linkage 1, node 3, timeline 2 + 1 ignored, task 5) -cargo build # staticlib + rlib; module symbols resolve at the final app link +cargo build # cdylib embeds the module C ABIs; oakcore_*/fb_* stay + # runtime lookups (see build.rs) ``` ## FFI discipline diff --git a/src/engine/rust/build.rs b/src/engine/rust/build.rs new file mode 100644 index 000000000..788824278 --- /dev/null +++ b/src/engine/rust/build.rs @@ -0,0 +1,33 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Build-time link configuration for the `liboakengine` cdylib. +//! +//! The dylib now carries the module C ABIs itself (oakundo_*, oakcommon_*, +//! ... — see Cargo.toml), so the only remaining undefined imports are the +//! C++ host-provided symbols the modules call directly: `oakcore_*` +//! (liboakcore's `oakcore_audioparams_*` / `oakcore_rational_*`, called by +//! oakcodec) and `fb_find_best_pix_fmt_of_list` (ffmpeg_bridge, called by +//! oakcommon's pixel-format helper). Those live in the host Oak process, +//! which loads this dylib, so macOS `ld` must accept them as runtime +//! lookups instead of link-time errors. Only the cdylib gets this flag — +//! the rlib/staticlib (and the worker/cli consumers) are unaffected. + +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + println!("cargo:rustc-cdylib-link-arg=-Wl,-undefined,dynamic_lookup"); + } +} diff --git a/src/facade/rust/src/audio.rs b/src/engine/rust/src/audio.rs similarity index 100% rename from src/facade/rust/src/audio.rs rename to src/engine/rust/src/audio.rs diff --git a/src/facade/rust/src/bridge/audio.rs b/src/engine/rust/src/bridge/audio.rs similarity index 100% rename from src/facade/rust/src/bridge/audio.rs rename to src/engine/rust/src/bridge/audio.rs diff --git a/src/facade/rust/src/bridge/codec.rs b/src/engine/rust/src/bridge/codec.rs similarity index 100% rename from src/facade/rust/src/bridge/codec.rs rename to src/engine/rust/src/bridge/codec.rs diff --git a/src/facade/rust/src/bridge/common.rs b/src/engine/rust/src/bridge/common.rs similarity index 100% rename from src/facade/rust/src/bridge/common.rs rename to src/engine/rust/src/bridge/common.rs diff --git a/src/facade/rust/src/bridge/mod.rs b/src/engine/rust/src/bridge/mod.rs similarity index 100% rename from src/facade/rust/src/bridge/mod.rs rename to src/engine/rust/src/bridge/mod.rs diff --git a/src/facade/rust/src/bridge/node.rs b/src/engine/rust/src/bridge/node.rs similarity index 100% rename from src/facade/rust/src/bridge/node.rs rename to src/engine/rust/src/bridge/node.rs diff --git a/src/facade/rust/src/bridge/plugin.rs b/src/engine/rust/src/bridge/plugin.rs similarity index 100% rename from src/facade/rust/src/bridge/plugin.rs rename to src/engine/rust/src/bridge/plugin.rs diff --git a/src/facade/rust/src/bridge/render.rs b/src/engine/rust/src/bridge/render.rs similarity index 100% rename from src/facade/rust/src/bridge/render.rs rename to src/engine/rust/src/bridge/render.rs diff --git a/src/facade/rust/src/bridge/task.rs b/src/engine/rust/src/bridge/task.rs similarity index 100% rename from src/facade/rust/src/bridge/task.rs rename to src/engine/rust/src/bridge/task.rs diff --git a/src/facade/rust/src/bridge/timeline.rs b/src/engine/rust/src/bridge/timeline.rs similarity index 100% rename from src/facade/rust/src/bridge/timeline.rs rename to src/engine/rust/src/bridge/timeline.rs diff --git a/src/facade/rust/src/bridge/undo.rs b/src/engine/rust/src/bridge/undo.rs similarity index 100% rename from src/facade/rust/src/bridge/undo.rs rename to src/engine/rust/src/bridge/undo.rs diff --git a/src/facade/rust/src/codec.rs b/src/engine/rust/src/codec.rs similarity index 100% rename from src/facade/rust/src/codec.rs rename to src/engine/rust/src/codec.rs diff --git a/src/facade/rust/src/common.rs b/src/engine/rust/src/common.rs similarity index 100% rename from src/facade/rust/src/common.rs rename to src/engine/rust/src/common.rs diff --git a/src/facade/rust/src/deferred.rs b/src/engine/rust/src/deferred.rs similarity index 100% rename from src/facade/rust/src/deferred.rs rename to src/engine/rust/src/deferred.rs diff --git a/src/facade/rust/src/error.rs b/src/engine/rust/src/error.rs similarity index 100% rename from src/facade/rust/src/error.rs rename to src/engine/rust/src/error.rs diff --git a/src/facade/rust/src/handle.rs b/src/engine/rust/src/handle.rs similarity index 100% rename from src/facade/rust/src/handle.rs rename to src/engine/rust/src/handle.rs diff --git a/src/facade/rust/src/ipc.rs b/src/engine/rust/src/ipc.rs similarity index 100% rename from src/facade/rust/src/ipc.rs rename to src/engine/rust/src/ipc.rs diff --git a/src/facade/rust/src/lib.rs b/src/engine/rust/src/lib.rs similarity index 78% rename from src/facade/rust/src/lib.rs rename to src/engine/rust/src/lib.rs index 3d7b495bd..dadf1fed1 100644 --- a/src/facade/rust/src/lib.rs +++ b/src/engine/rust/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! # oakfacade — the `liboakengine` facade (Rust) +//! # oakengine — the `liboakengine` facade (Rust) //! //! Re-exports the frozen `oakengine_*` C ABI //! (`engine/include/oakengine/*.h`) verbatim on top of the module C ABIs @@ -42,12 +42,17 @@ //! //! ## Testing //! -//! `cargo test` links the module crates' rlibs (dev-dependencies) so the -//! bridge imports resolve; `tests/linkage.rs` references every crate to -//! force rustc to pull the rlibs into the link. Where a wrapped family -//! needs module behavior the crates do not implement yet, the engine -//! function is a documented stub and its test carries `#[ignore]` with a -//! reason (see README.md). +//! The module crates are real dependencies (see Cargo.toml) and +//! [`linkage`] anchors them into every link of this crate, so the module +//! C ABIs are embedded in the `liboakengine` cdylib next to the facade's +//! own exports. `cargo test` links the same crates' rlibs (plus the +//! `test-stubs` feature union declared in the dev-dependencies, which +//! compiles the oakcommon/oakplugin in-crate mocks); `tests/linkage.rs` +//! additionally references every crate for the integration-test binaries +//! and `test_link` (below) covers the unit-test binary. Where a wrapped +//! family needs module behavior the crates do not implement yet, the +//! engine function is a documented stub and its test carries `#[ignore]` +//! with a reason (see README.md). #![deny(unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] @@ -60,6 +65,8 @@ pub mod deferred; pub mod error; pub mod handle; pub mod ipc; +#[cfg(not(test))] +pub mod linkage; pub mod node; pub mod plugin; pub mod render; diff --git a/src/engine/rust/src/linkage.rs b/src/engine/rust/src/linkage.rs new file mode 100644 index 000000000..d40e63f9b --- /dev/null +++ b/src/engine/rust/src/linkage.rs @@ -0,0 +1,66 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Linkage anchors — force the module crates' rlibs into every link. +//! +//! The facade talks to the modules exclusively through `extern "C"` +//! imports (src/bridge/), so rustc would otherwise consider the module +//! crates unused and prune their rlibs from the link. This module +//! references one `#[no_mangle]` export of every module crate (and +//! `oakcore-rs`) from a `#[used]` static, which (a) marks each crate as +//! used so its rlib reaches the linker and (b) keeps the anchor alive so +//! the referenced object files are pulled. For the `liboakengine` cdylib +//! this is what actually embeds the module C ABIs (oakundo_*, +//! oakcommon_*, ...) into the dylib next to the facade's own oakengine_* +//! exports. +//! +//! The per-crate symbol mirrors the test-force-link in +//! tests/common/mod.rs (same paths, same `as usize` cast idiom), so the +//! crate/module paths are proven against the current module layouts. + +#![allow(dead_code)] + +/// Pull every module crate into the link. Mirrors +/// `tests/common/mod.rs::force_link`; the oakcommon XML/undo anchors are +/// repeated because oaknode's serializer resolves those C ABI symbols at +/// runtime via dlsym(RTLD_DEFAULT) and they must be present in the dylib +/// for that lookup to succeed. +fn force_link() -> usize { + let fns: [usize; 13] = [ + // oakcore-rs (pure value types; referenced so its rlib is linked). + oakcore_rs::Rational::new(1, 2).numerator() as usize, + // One exported C ABI symbol per module crate. + oakundo::ffi::undostack::oakundo_undostack_init as usize, + oakcommon::ffi::config::oakcommon_config_get_int as usize, + oaktimeline::ffi::marker::oaktimeline_marker_list_create as usize, + oakcodec::ffi::format::oakcodec_encoding_format_count as usize, + oakaudio::ffi::waveform::oakaudio_waveform_length as usize, + oakrender::ffi::cache::oakrender_cache_indicator_height as usize, + oaktask::ffi::manager::oaktask_manager_init as usize, + oakplugin::ffi::oakplugin_host_plugin_count as usize, + oaknode::ffi::project::oaknode_project_init as usize, + // oaknode's dlsym(RTLD_DEFAULT) targets (see tests/common/mod.rs). + oakcommon::ffi::xmlutils::oakcommon_xml_writer_init as usize, + oakcommon::ffi::xmlutils::oakcommon_xml_reader_init as usize, + oakundo::ffi::command::oakundo_command_init as usize, + ]; + fns.iter().sum() +} + +/// Keeps [`force_link`] (and through it every referenced export) alive in +/// the cdylib/staticlib even though nothing calls it directly. +#[used] +static FORCE_LINK_ANCHOR: fn() -> usize = force_link; diff --git a/src/facade/rust/src/node.rs b/src/engine/rust/src/node.rs similarity index 100% rename from src/facade/rust/src/node.rs rename to src/engine/rust/src/node.rs diff --git a/src/facade/rust/src/plugin.rs b/src/engine/rust/src/plugin.rs similarity index 100% rename from src/facade/rust/src/plugin.rs rename to src/engine/rust/src/plugin.rs diff --git a/src/facade/rust/src/render.rs b/src/engine/rust/src/render.rs similarity index 100% rename from src/facade/rust/src/render.rs rename to src/engine/rust/src/render.rs diff --git a/src/facade/rust/src/task.rs b/src/engine/rust/src/task.rs similarity index 100% rename from src/facade/rust/src/task.rs rename to src/engine/rust/src/task.rs diff --git a/src/facade/rust/src/timeline.rs b/src/engine/rust/src/timeline.rs similarity index 100% rename from src/facade/rust/src/timeline.rs rename to src/engine/rust/src/timeline.rs diff --git a/src/facade/rust/src/undo.rs b/src/engine/rust/src/undo.rs similarity index 100% rename from src/facade/rust/src/undo.rs rename to src/engine/rust/src/undo.rs diff --git a/src/facade/rust/src/worker.rs b/src/engine/rust/src/worker.rs similarity index 100% rename from src/facade/rust/src/worker.rs rename to src/engine/rust/src/worker.rs diff --git a/src/facade/rust/tests/audio.rs b/src/engine/rust/tests/audio.rs similarity index 99% rename from src/facade/rust/tests/audio.rs rename to src/engine/rust/tests/audio.rs index 123e0481c..2cfad0bcb 100644 --- a/src/facade/rust/tests/audio.rs +++ b/src/engine/rust/tests/audio.rs @@ -22,7 +22,7 @@ #[path = "common/mod.rs"] mod common; -use oakfacade::audio::{ +use oakengine::audio::{ oakengine_audio_clear_buffered_output, oakengine_audio_create_instance, oakengine_audio_destroy_instance, oakengine_audio_estimate_envelope_offset, oakengine_audio_get_output_device, oakengine_audio_hard_reset, diff --git a/src/facade/rust/tests/codec.rs b/src/engine/rust/tests/codec.rs similarity index 98% rename from src/facade/rust/tests/codec.rs rename to src/engine/rust/tests/codec.rs index fe540a086..e56e7581e 100644 --- a/src/facade/rust/tests/codec.rs +++ b/src/engine/rust/tests/codec.rs @@ -22,7 +22,7 @@ mod common; use std::ffi::{c_char, c_int}; -use oakfacade::codec::{ +use oakengine::codec::{ oakengine_encoding_codec_is_lossless, oakengine_encoding_codec_is_still_image, oakengine_encoding_codec_name, oakengine_encoding_filename_contains_digit_placeholder, oakengine_encoding_filename_remove_digit_placeholder, oakengine_encoding_format_audio_codec_count, @@ -45,7 +45,7 @@ use oakfacade::codec::{ oakengine_encoding_params_video_pix_fmt, oakengine_encoding_params_set_video_pix_fmt, oakengine_encoding_pix_fmt_index, }; -use oakfacade::common::OakVideoParamsPod; +use oakengine::common::OakVideoParamsPod; /// Container format / codec metadata queries. #[test] @@ -142,7 +142,7 @@ fn params_handle_round_trip() { let mut vp: OakVideoParamsPod = unsafe { std::mem::zeroed() }; assert_eq!( unsafe { - oakfacade::common::oakengine_video_params_make(&mut vp, 1920, 1080, 1001, 30000, 4, 1, 1, 0, 1, 1) + oakengine::common::oakengine_video_params_make(&mut vp, 1920, 1080, 1001, 30000, 4, 1, 1, 0, 1, 1) }, 0 ); diff --git a/src/facade/rust/tests/common.rs b/src/engine/rust/tests/common.rs similarity index 97% rename from src/facade/rust/tests/common.rs rename to src/engine/rust/tests/common.rs index 039f35580..038015a29 100644 --- a/src/facade/rust/tests/common.rs +++ b/src/engine/rust/tests/common.rs @@ -25,7 +25,7 @@ mod common; use std::ffi::{c_char, c_int}; -use oakfacade::common::{ +use oakengine::common::{ oakengine_config_get_int, oakengine_config_get_string, oakengine_config_load, oakengine_config_save, oakengine_config_set_error_handler, oakengine_config_set_int, oakengine_config_set_string, oakengine_video_params_bytes_per_pixel, @@ -87,12 +87,12 @@ fn config_error_handler() { assert_eq!(unsafe { oakengine_config_set_error_handler(Some(handler), std::ptr::null_mut()) }, 0); // Report an error through the handler. assert_eq!(unsafe { - oakfacade::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) + oakengine::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) }, 0); assert_eq!(CALLED.load(std::sync::atomic::Ordering::SeqCst), 1); // NULL handler clears; reporting then does not invoke. assert_eq!(unsafe { oakengine_config_set_error_handler(None, std::ptr::null_mut()) }, 0); - unsafe { oakfacade::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) }; + unsafe { oakengine::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) }; assert_eq!(CALLED.load(std::sync::atomic::Ordering::SeqCst), 1); } diff --git a/src/facade/rust/tests/common/mod.rs b/src/engine/rust/tests/common/mod.rs similarity index 100% rename from src/facade/rust/tests/common/mod.rs rename to src/engine/rust/tests/common/mod.rs diff --git a/src/facade/rust/tests/linkage.rs b/src/engine/rust/tests/linkage.rs similarity index 100% rename from src/facade/rust/tests/linkage.rs rename to src/engine/rust/tests/linkage.rs diff --git a/src/facade/rust/tests/node.rs b/src/engine/rust/tests/node.rs similarity index 97% rename from src/facade/rust/tests/node.rs rename to src/engine/rust/tests/node.rs index 34a8e4952..50972686c 100644 --- a/src/facade/rust/tests/node.rs +++ b/src/engine/rust/tests/node.rs @@ -28,7 +28,7 @@ mod common; use std::ffi::{c_char, c_int}; -use oakfacade::node::{ +use oakengine::node::{ oakengine_footage_borrow, oakengine_footage_last_error, oakengine_footage_probe, oakengine_node_connect, oakengine_node_disconnect, oakengine_node_factory_create_from_id, oakengine_node_factory_id_count, oakengine_node_factory_name_from_id, @@ -83,7 +83,7 @@ fn float_value(x: f64) -> OakNodeValue { } /// The index of the first project node whose type id matches `id`, or -1. -unsafe fn find_node(project: *mut oakfacade::handle::OakEngineProject, id: &str) -> c_int { +unsafe fn find_node(project: *mut oakengine::handle::OakEngineProject, id: &str) -> c_int { let count = unsafe { oakengine_project_node_count(project) }; for i in 0..count { let node = unsafe { oakengine_project_node_at(project, i) }; @@ -137,15 +137,15 @@ fn project_node_keyframe_lifecycle() { 0 ); assert_eq!( - unsafe { oakengine_project_set_filename(project, c"/tmp/oakfacade_node_test.ovexml".as_ptr()) }, + unsafe { oakengine_project_set_filename(project, c"/tmp/oakengine_node_test.ovexml".as_ptr()) }, 0 ); let len = unsafe { oakengine_project_filename(project, buf.as_mut_ptr(), 256) }; assert!(len > 0); - assert!(unsafe { read_buf(&mut buf) }.ends_with("oakfacade_node_test.ovexml")); + assert!(unsafe { read_buf(&mut buf) }.ends_with("oakengine_node_test.ovexml")); let len = unsafe { oakengine_project_name(project, buf.as_mut_ptr(), 256) }; assert!(len > 0); - assert_eq!(unsafe { read_buf(&mut buf) }, "oakfacade_node_test"); + assert_eq!(unsafe { read_buf(&mut buf) }, "oakengine_node_test"); // ---- factory + node creation --------------------------------------- let factory_count = oakengine_node_factory_id_count(); @@ -269,9 +269,9 @@ fn project_node_keyframe_lifecycle() { assert!((at.f[0] - 0.5).abs() < 1e-6); // ---- project save → fresh load round-trip --------------------------- - let path = c"/tmp/oakfacade_node_test.ovexml"; + let path = c"/tmp/oakengine_node_test.ovexml"; assert_eq!(unsafe { oakengine_project_save(project, path.as_ptr()) }, 0); - assert!(std::path::Path::new("/tmp/oakfacade_node_test.ovexml").exists()); + assert!(std::path::Path::new("/tmp/oakengine_node_test.ovexml").exists()); unsafe { oakengine_project_free(project) }; diff --git a/src/facade/rust/tests/plugin.rs b/src/engine/rust/tests/plugin.rs similarity index 97% rename from src/facade/rust/tests/plugin.rs rename to src/engine/rust/tests/plugin.rs index ba4483702..46404c03f 100644 --- a/src/facade/rust/tests/plugin.rs +++ b/src/engine/rust/tests/plugin.rs @@ -19,7 +19,7 @@ #[path = "common/mod.rs"] mod common; -use oakfacade::plugin::{ +use oakengine::plugin::{ oakengine_plugin_load_plugins, oakengine_plugin_node_push_button_clicked, oakengine_plugin_set_active_viewer_provider, oakengine_plugin_set_progress_reporter_factory, }; @@ -27,7 +27,7 @@ use oakfacade::plugin::{ /// Callback registration round-trips (NULL clears). #[test] fn provider_registration() { - unsafe extern "C" fn viewer(_userdata: *mut std::ffi::c_void) -> *mut oakfacade::handle::OakEngineNode { + unsafe extern "C" fn viewer(_userdata: *mut std::ffi::c_void) -> *mut oakengine::handle::OakEngineNode { std::ptr::null_mut() } assert_eq!(unsafe { diff --git a/src/facade/rust/tests/render.rs b/src/engine/rust/tests/render.rs similarity index 99% rename from src/facade/rust/tests/render.rs rename to src/engine/rust/tests/render.rs index 3a7956c0a..3d31a999d 100644 --- a/src/facade/rust/tests/render.rs +++ b/src/engine/rust/tests/render.rs @@ -26,7 +26,7 @@ mod common; use std::ffi::{c_char, c_double}; -use oakfacade::render::{ +use oakengine::render::{ oakengine_color_last_error, oakengine_color_manager_get_config_filename, oakengine_color_processor_convert_color, oakengine_color_processor_create, oakengine_color_processor_free, oakengine_color_processor_is_valid, diff --git a/src/facade/rust/tests/task.rs b/src/engine/rust/tests/task.rs similarity index 98% rename from src/facade/rust/tests/task.rs rename to src/engine/rust/tests/task.rs index 3e3e1ea3e..3883e371c 100644 --- a/src/facade/rust/tests/task.rs +++ b/src/engine/rust/tests/task.rs @@ -34,11 +34,11 @@ mod common; use std::ffi::{c_char, c_int, c_void}; -use oakfacade::node::{ +use oakengine::node::{ oakengine_node_free, oakengine_project_create, oakengine_project_free, oakengine_project_new, oakengine_project_root, oakengine_project_set_filename, }; -use oakfacade::task::{ +use oakengine::task::{ oakengine_cli_task_dialog_run, oakengine_task_cancel, oakengine_task_create_export, oakengine_task_create_project_import, oakengine_task_create_project_load, oakengine_task_create_project_load_otio, oakengine_task_create_project_save, @@ -187,7 +187,7 @@ fn project_task_lifecycle() { // ---- save task on a real project → sync run writes the file ---------- let save_path = std::env::temp_dir().join(format!( - "oakfacade_task_save_{}.ovexml", + "oakengine_task_save_{}.ovexml", std::process::id() )); let save_c = std::ffi::CString::new(save_path.to_str().unwrap()).unwrap(); @@ -217,7 +217,7 @@ fn project_task_lifecycle() { // project's own filename; NULL without one, a real task with one. ------ assert!(unsafe { oakengine_task_create_project_save_otio(project) }.is_null()); assert_eq!( - unsafe { oakengine_project_set_filename(project, c"/tmp/oakfacade_task_otio.otio".as_ptr()) }, + unsafe { oakengine_project_set_filename(project, c"/tmp/oakengine_task_otio.otio".as_ptr()) }, 0 ); let otio_task = unsafe { oakengine_task_create_project_save_otio(project) }; diff --git a/src/facade/rust/tests/timeline.rs b/src/engine/rust/tests/timeline.rs similarity index 99% rename from src/facade/rust/tests/timeline.rs rename to src/engine/rust/tests/timeline.rs index 6171933a0..e87c8bcd3 100644 --- a/src/facade/rust/tests/timeline.rs +++ b/src/engine/rust/tests/timeline.rs @@ -31,12 +31,12 @@ mod common; use std::ffi::{c_char, c_int}; -use oakfacade::handle::{box_handle, OakEngineNode}; -use oakfacade::node::{ +use oakengine::handle::{box_handle, OakEngineNode}; +use oakengine::node::{ oakengine_footage_borrow, oakengine_project_create, oakengine_project_free, oakengine_project_new, }; -use oakfacade::timeline::{ +use oakengine::timeline::{ oakengine_block_get_range, oakengine_block_get_track, oakengine_block_is_enabled, oakengine_block_is_gap, oakengine_block_link_count, oakengine_block_next, oakengine_block_prev, oakengine_block_set_enabled, oakengine_block_set_length_and_media_out, @@ -103,7 +103,7 @@ fn force_runtime_syms() -> usize { /// Convert a facade `CHandle` to the layout-identical oaknode `CHandle` /// (distinct Rust types over the same C ABI struct). -fn to_node_handle(h: oakfacade::handle::CHandle) -> oaknode::handle::CHandle { +fn to_node_handle(h: oakengine::handle::CHandle) -> oaknode::handle::CHandle { oaknode::handle::CHandle { ctx: h.ctx, addref: h.addref, @@ -113,8 +113,8 @@ fn to_node_handle(h: oakfacade::handle::CHandle) -> oaknode::handle::CHandle { } /// Convert an oaknode `CHandle` back to the facade `CHandle`. -fn to_facade_handle(h: oaknode::handle::CHandle) -> oakfacade::handle::CHandle { - oakfacade::handle::CHandle { +fn to_facade_handle(h: oaknode::handle::CHandle) -> oakengine::handle::CHandle { + oakengine::handle::CHandle { ctx: h.ctx, addref: h.addref, release: h.release, diff --git a/src/facade/rust/tests/undo.rs b/src/engine/rust/tests/undo.rs similarity index 93% rename from src/facade/rust/tests/undo.rs rename to src/engine/rust/tests/undo.rs index af90d04f5..d714d98f3 100644 --- a/src/facade/rust/tests/undo.rs +++ b/src/engine/rust/tests/undo.rs @@ -28,7 +28,7 @@ mod common; use std::ffi::{c_char, c_int, c_void}; use std::sync::atomic::{AtomicI32, Ordering}; -use oakfacade::undo::{ +use oakengine::undo::{ oakengine_undo_can_redo, oakengine_undo_can_undo, oakengine_undo_clear, oakengine_undo_command_create, oakengine_undo_command_create_multi, oakengine_undo_command_free, oakengine_undo_command_multi_add_child, @@ -53,16 +53,28 @@ static CMD_FREE_COUNT: AtomicI32 = AtomicI32::new(0); static STK_REDO_COUNT: AtomicI32 = AtomicI32::new(0); static STK_UNDO_COUNT: AtomicI32 = AtomicI32::new(0); +/// Stack-test callbacks: bump only the `STK_*` counters. They must not +/// touch the `CMD_*` counters — the command-lifecycle tests reset and +/// assert those in parallel threads, so a stray bump here would race. unsafe extern "C" fn redo_cb(_userdata: *mut c_void) { - CMD_REDO_COUNT.fetch_add(1, Ordering::SeqCst); STK_REDO_COUNT.fetch_add(1, Ordering::SeqCst); } unsafe extern "C" fn undo_cb(_userdata: *mut c_void) { - CMD_UNDO_COUNT.fetch_add(1, Ordering::SeqCst); STK_UNDO_COUNT.fetch_add(1, Ordering::SeqCst); } +/// Command-lifecycle-only callbacks: bump only the `CMD_*` counters. The +/// serialized stack test runs in a parallel thread and must not flip +/// these. +unsafe extern "C" fn cmd_redo_cb(_userdata: *mut c_void) { + CMD_REDO_COUNT.fetch_add(1, Ordering::SeqCst); +} + +unsafe extern "C" fn cmd_undo_cb(_userdata: *mut c_void) { + CMD_UNDO_COUNT.fetch_add(1, Ordering::SeqCst); +} + unsafe extern "C" fn free_cb(_userdata: *mut c_void) { CMD_FREE_COUNT.fetch_add(1, Ordering::SeqCst); } @@ -77,8 +89,8 @@ fn command_create_redo_undo_free() { let cmd = unsafe { oakengine_undo_command_create( c"custom".as_ptr(), - Some(redo_cb), - Some(undo_cb), + Some(cmd_redo_cb), + Some(cmd_undo_cb), Some(free_cb), std::ptr::null_mut(), ) @@ -108,8 +120,8 @@ fn multi_command_add_child_count_redo() { let child = unsafe { oakengine_undo_command_create( c"child".as_ptr(), - Some(redo_cb), - Some(undo_cb), + Some(cmd_redo_cb), + Some(cmd_undo_cb), None, std::ptr::null_mut(), ) diff --git a/src/facade/rust/Cargo.toml b/src/facade/rust/Cargo.toml deleted file mode 100644 index 36b20e72e..000000000 --- a/src/facade/rust/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -name = "oakfacade" -version = "0.1.0" -edition = "2021" -description = "Oak Video Editor facade: re-exports the frozen oakengine_* C ABI over the module C ABIs (Rust)" -license = "GPL-3.0-or-later" - -[lib] -crate-type = ["staticlib", "rlib"] - -[profile.release] -# FFI discipline: panics must be catchable at every exported entry. -panic = "unwind" - -[dependencies] -# NDJSON control-plane protocol for the worker session (src/worker.rs) and -# the shm error formatting in src/ipc.rs. -serde = { version = "1", features = ["derive"] } -serde_json = "1" -# POSIX shm_open/mmap/munmap/shm_unlink constants + syscalls for the -# shared-memory frame-slot transport (src/ipc.rs). -libc = "0.2" - -# Every module call crosses the module C ABI as an `extern "C"` import -# (src/bridge/), resolved at the final link against the module shared -# libraries (see README.md). -# -# `cargo test` links the module crates' rlibs instead (dev-dependencies -# below): the crates' `#[no_mangle]` exports satisfy the facade's bridge -# imports, so the smoke tests exercise the real module code where the -# crates implement it. Tests reference every crate so rustc pulls the -# rlibs into the link (see tests/common/mod.rs). -# -# test-stubs on oakcommon/oakplugin compiles those crates' in-crate C ABI -# mocks: oakcommon's stub replaces the ffmpeg_bridge symbol, and -# oakplugin's stubs replace its runtime dlsym lookups. -# -# NOTE (oaktimeline/oaktask): linked WITHOUT their `test-stubs` features. -# Their in-crate mocks define `oakundo_command_init` etc., which would -# collide with the real oakundo rlib in one test binary; without -# test-stubs their real exports reference the oaknode/oakundo/oakcommon -# C ABI symbols as link-time externs, which the dev-dependency rlibs -# (oaknode, oakundo, oakcommon[test-stubs]) provide. oaknode itself -# resolves cross-module symbols at runtime with dlsym(RTLD_DEFAULT), which -# finds the linked rlibs in the test binary (see src/bridge/node.rs). -[dev-dependencies] -oakundo = { path = "../../undo/rust" } -oakcodec = { path = "../../codec/rust" } -oakaudio = { path = "../../audio/rust" } -oakrender = { path = "../../render/rust" } -oakcommon = { path = "../../common/rust", features = ["test-stubs"] } -oakplugin = { path = "../../plugin/rust", features = ["test-stubs"] } -oaknode = { path = "../../node/rust" } -oaktimeline = { path = "../../timeline/rust" } -oaktask = { path = "../../task/rust" } diff --git a/worker/rust/Cargo.lock b/worker/rust/Cargo.lock index b7d0c4e1c..a7c52ae5d 100644 --- a/worker/rust/Cargo.lock +++ b/worker/rust/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.6" @@ -82,6 +97,24 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -144,6 +177,12 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "cc" version = "1.4.2" @@ -151,7 +190,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", ] [[package]] @@ -166,6 +214,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.6.6" @@ -259,6 +318,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -274,18 +342,65 @@ dependencies = [ "litrs", ] +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "ffmpeg-next" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a" +dependencies = [ + "bitflags 2.13.1", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b" +dependencies = [ + "bindgen", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + [[package]] name = "find-msvc-tools" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -354,6 +469,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "glow" version = "0.16.0" @@ -459,12 +580,31 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hexf-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "tiff", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -481,6 +621,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -616,6 +765,32 @@ dependencies = [ "paste", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "naga" version = "25.0.1" @@ -634,7 +809,7 @@ dependencies = [ "log", "num-traits", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "strum", "thiserror 2.0.20", @@ -650,6 +825,16 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -660,30 +845,100 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "oak-worker" version = "0.1.0" dependencies = [ "clap", - "oakfacade", + "oakengine", "oakrender", "serde", "serde_json", ] +[[package]] +name = "oakaudio" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "oakcodec" +version = "0.1.0" +dependencies = [ + "ffmpeg-next", + "oakcore-rs", +] + +[[package]] +name = "oakcommon" +version = "0.1.0" +dependencies = [ + "image", + "log", + "oakcore-rs", + "ocio-rs", + "quick-xml", +] + [[package]] name = "oakcore-rs" version = "0.1.0" [[package]] -name = "oakfacade" +name = "oakengine" version = "0.1.0" dependencies = [ "libc", + "oakaudio", + "oakcodec", + "oakcommon", + "oakcore-rs", + "oaknode", + "oakplugin", + "oakrender", + "oaktask", + "oaktimeline", + "oakundo", "serde", "serde_json", ] +[[package]] +name = "oaknode" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "oakotio" +version = "0.1.0" +dependencies = [ + "oakcore-rs", + "quick-xml", + "serde", + "serde_json", +] + +[[package]] +name = "oakplugin" +version = "0.1.0" +dependencies = [ + "cc", +] + [[package]] name = "oakrender" version = "0.1.0" @@ -693,6 +948,28 @@ dependencies = [ "wgpu", ] +[[package]] +name = "oaktask" +version = "0.1.0" +dependencies = [ + "oakcore-rs", + "oakotio", +] + +[[package]] +name = "oaktimeline" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + +[[package]] +name = "oakundo" +version = "0.1.0" +dependencies = [ + "oakcore-rs", +] + [[package]] name = "objc" version = "0.2.7" @@ -811,6 +1088,27 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.47" @@ -841,6 +1139,35 @@ dependencies = [ "bitflags 2.13.1", ] +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -853,6 +1180,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustversion" version = "1.0.23" @@ -901,6 +1234,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap", "itoa", "memchr", "serde", @@ -908,12 +1242,24 @@ dependencies = [ "zmij", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "slab" version = "0.4.12" @@ -1049,6 +1395,20 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1067,6 +1427,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1138,6 +1504,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wgpu" version = "25.0.2" @@ -1187,7 +1559,7 @@ dependencies = [ "portable-atomic", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.20", "wgpu-core-deps-apple", @@ -1468,3 +1840,18 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/worker/rust/Cargo.toml b/worker/rust/Cargo.toml index 348c5aa8c..f145407fd 100644 --- a/worker/rust/Cargo.toml +++ b/worker/rust/Cargo.toml @@ -33,11 +33,11 @@ serde_json = "1" # The liboakengine facade (Rust): its worker module owns the whole worker # runtime — render backend selection through the oakrender module C ABI, # the startup handshake and the NDJSON control loop -# (`oakfacade::worker::worker_main`, the port of engine/src/capi/worker.cpp). +# (`oakengine::worker::worker_main`, the port of engine/src/capi/worker.cpp). # This binary is a thin shell over it, like worker/workermain.cpp. Its ipc # module provides the real shared-memory frame-slot transport -# (`oakfacade::ipc`) that src/session.rs + src/transport.rs attach through. -oakfacade = { path = "../../src/facade/rust" } +# (`oakengine::ipc`) that src/session.rs + src/transport.rs attach through. +oakengine = { path = "../../src/engine/rust" } # The oakrender module crate (the Rust rewrite of the oakrender module): # its C ABI (include/render/renderer.h) is how the facade's worker diff --git a/worker/rust/build.rs b/worker/rust/build.rs new file mode 100644 index 000000000..96f63152e --- /dev/null +++ b/worker/rust/build.rs @@ -0,0 +1,33 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Link configuration for the `oak-worker` binary. +//! +//! The facade rlib (src/engine/rust) links the module C ABIs into any +//! consumer that pulls its codec surface — oak-worker's use of +//! `oakengine::worker` transitively pulls the facade's codec module, whose +//! oakcodec references carry a few C++-host imports (`oakcore_audioparams_*` +//! from liboakcore, `fb_*` from ffmpeg_bridge). Those live in the host Oak +//! process and are only reachable on media-decode paths this worker never +//! exercises; the CMake worker has the same property through the +//! liboakengine dylib (whose build.rs allows runtime lookups). Mirror that +//! here so the standalone Rust worker binary links. + +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + println!("cargo:rustc-link-arg=-Wl,-undefined,dynamic_lookup"); + } +} diff --git a/worker/rust/src/main.rs b/worker/rust/src/main.rs index b45b5d930..dc4235e8c 100644 --- a/worker/rust/src/main.rs +++ b/worker/rust/src/main.rs @@ -19,7 +19,7 @@ //! A thin shell over the facade, mirroring `worker/workermain.cpp`: all //! runtime logic — render backend selection (dynamic -> OpenGL fallback //! through the oakrender module C ABI), the startup handshake and the -//! NDJSON control loop — lives in `oakfacade::worker` (the Rust port of +//! NDJSON control loop — lives in `oakengine::worker` (the Rust port of //! `engine/src/capi/worker.cpp`, contract in //! `engine/include/oakengine/worker.h`). This crate keeps only the CLI //! surface (arg parsing) and the in-process session mirror @@ -37,7 +37,7 @@ use std::process::exit; use clap::Parser; // Force-link the oakrender module crate: the facade's worker module -// (oakfacade::worker) initializes the render backend through the oakrender +// (oakengine::worker) initializes the render backend through the oakrender // C ABI, but its bridge imports are `extern "C"` declarations — nothing in // the worker source names the crate, so without this the oakrender rlib // would not be added to the link and those imports would stay undefined. @@ -46,7 +46,7 @@ use oakrender as _; /// Protocol version announced in the startup handshake /// (`k_protocol_version` in worker.cpp). Mirrors -/// `oakfacade::worker::PROTOCOL_VERSION`. +/// `oakengine::worker::PROTOCOL_VERSION`. pub const PROTOCOL_VERSION: i32 = 1; /// CLI surface (the C++ worker scans argv for `--backend`; clap formalizes @@ -68,7 +68,7 @@ fn main() { let args = Args::parse(); // The facade's worker_main is the C++ oakengine_worker_main() — the // whole worker flow. Like workermain.cpp, this main only forwards. - exit(oakfacade::worker::worker_main(&args.backend.to_ascii_lowercase())); + exit(oakengine::worker::worker_main(&args.backend.to_ascii_lowercase())); } /// Log a worker-side message to stderr, mirroring worker.cpp `log_error()` diff --git a/worker/rust/src/session.rs b/worker/rust/src/session.rs index 9774181e5..40119f52f 100644 --- a/worker/rust/src/session.rs +++ b/worker/rust/src/session.rs @@ -16,7 +16,7 @@ //! The worker-side session state machine — the in-process mirror of //! `OakWorkerSession` in `engine/src/capi/worker.cpp` (whose production -//! Rust port lives in `oakfacade::worker`). +//! Rust port lives in `oakengine::worker`). //! //! The session holds the attached shared-memory frame-slot pools //! ([`crate::transport::AttachedPools`]) and the shutdown flag, and @@ -61,7 +61,7 @@ impl WorkerSession { } /// The attached output pool (the worker->main frame-slot pool). - pub fn output_pool(&self) -> Option<&oakfacade::ipc::FrameSlotPool> { + pub fn output_pool(&self) -> Option<&oakengine::ipc::FrameSlotPool> { self.pools.as_ref().map(|p| &p.output_pool) } @@ -196,7 +196,7 @@ impl Default for WorkerSession { #[cfg(test)] mod tests { use super::*; - use oakfacade::ipc::{FrameSlotPool, SharedMemoryRegion, ShmMode}; + use oakengine::ipc::{FrameSlotPool, SharedMemoryRegion, ShmMode}; use serde_json::json; /// A unique, temporary POSIX segment key for a test. diff --git a/worker/rust/src/transport.rs b/worker/rust/src/transport.rs index 11fc39764..91759e5b1 100644 --- a/worker/rust/src/transport.rs +++ b/worker/rust/src/transport.rs @@ -20,7 +20,7 @@ //! shared-memory segments holding an `olive::ipc::FrameSlotPool` — a fixed //! pool of frame slots whose free/ready queues are synchronized by the //! lock-free single-producer/single-consumer `SpscRingBuffer`. That -//! machinery is implemented in the facade crate (`oakfacade::ipc`, the +//! machinery is implemented in the facade crate (`oakengine::ipc`, the //! Rust port of `engine/render/ipc/` behind //! `engine/include/oakengine/ipc.h`) — this module is the worker-side //! transport over it. @@ -36,7 +36,7 @@ //! as before: `oaknode` is a `todo!()` skeleton and the oakrender crate //! does not yet evaluate an arbitrary loaded graph to a frame. -use oakfacade::ipc::{FrameSlotPool, SharedMemoryRegion, ShmMode}; +use oakengine::ipc::{FrameSlotPool, SharedMemoryRegion, ShmMode}; use crate::ipc::HandshakeMsg;