Files
oak-editor/crates/oaknode/src/ffi.rs
T
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
2026-08-11 23:04:48 +08:00

9978 lines
272 KiB
Rust

// 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 <http://www.gnu.org/licenses/>.
//! C ABI export layer: implements `include/node/*.h` verbatim.
//!
//! Organization: one submodule per public header. The authoritative
//! function list is the header itself; each submodule below carries a
//! complete inventory comment plus the export stubs. Bodies only
//! unwrap handles, call safe Rust, and map results through
//! [`crate::handle::guard*`].
//!
//! ## Handle layout
//!
//! - Project handles box `Arc<Mutex<Project>>` (`ProjectArc`).
//! - Node/folder/sequence handles box [`crate::project::NodeRef`] —
//! `(Arc<Mutex<Project>>, NodeId)` plus the shared owned-flag.
//! - Handles obtained from a project (node_at, root, connected nodes,
//! links, ...) are borrowed: releasing them only frees the box.
//! Factory-created and detached nodes are owned and tracked by the
//! debug alive counter until a project adopts them.
//!
//! ## Undo-bridge exports
//!
//! Every `*_undoable` / `command_create_*` / `set_input_at_time_*`
//! export routes through `bridge::undo` (implemented Phase 2; the
//! `test-stubs` feature provides in-crate oakundo symbols for tests).
use std::collections::HashMap;
use std::ffi::{c_char, c_int, CStr, CString};
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use crate::error::{Error, Result};
use crate::handle::{guard, guard_void, CHandle, RefBox};
use crate::id::NodeId;
use crate::node::NodeCore;
use crate::project::{NodeRef, Project, WeakProject};
use crate::value::{NodeValue, OakNodeValue, ValueType};
/// Project handle payload.
type ProjectArc = Arc<Mutex<Project>>;
// ---------------------------------------------------------------------
// Shared state: alive counter + identity registry
// ---------------------------------------------------------------------
/// Live owned objects (projects from `init`, nodes from factory/copy).
static ALIVE: AtomicI32 = AtomicI32::new(0);
/// Identity registry: `NodeId::identity()` -> weak project + id. Entries
/// are advisory; `node_from_identity` revalidates against the live graph,
/// so stale entries (project freed, node moved/removed) resolve to empty
/// handles.
static REGISTRY: OnceLock<Mutex<HashMap<u64, (WeakProject, NodeId)>>> = OnceLock::new();
fn registry() -> &'static Mutex<HashMap<u64, (WeakProject, NodeId)>> {
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
/// Debug alive count (`oaknode_debug_alive_count`).
pub fn debug_alive_count() -> c_int {
ALIVE.load(Ordering::Relaxed)
}
/// Take a poisoned mutex in stride (a panic inside this crate must not
/// cascade into every later call).
fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
/// Release for project handles created by `oaknode_project_init`
/// (accounted in ALIVE).
unsafe extern "C" fn project_release(ctx: *mut std::ffi::c_void) {
unsafe {
let rb = ctx as *mut RefBox<ProjectArc>;
if (*rb).refs.fetch_sub(1, Ordering::AcqRel) == 1 {
drop(Box::from_raw(rb));
ALIVE.fetch_sub(1, Ordering::Relaxed);
}
}
}
/// Release for borrowed project handles (node_get_project): the box
/// drops but the project stays alive via its owner.
unsafe extern "C" fn project_release_borrowed(ctx: *mut std::ffi::c_void) {
unsafe {
let rb = ctx as *mut RefBox<ProjectArc>;
if (*rb).refs.fetch_sub(1, Ordering::AcqRel) == 1 {
drop(Box::from_raw(rb));
}
}
}
/// Release for node handles: drops the box; when the node object is
/// still separately owned (see [`NodeRef::owned`]), also un-counts it.
unsafe extern "C" fn node_release(ctx: *mut std::ffi::c_void) {
unsafe {
let rb = ctx as *mut RefBox<NodeRef>;
if (*rb).refs.fetch_sub(1, Ordering::AcqRel) == 1 {
let was_owned = (*rb).value.owned.load(Ordering::Relaxed);
drop(Box::from_raw(rb));
if was_owned {
ALIVE.fetch_sub(1, Ordering::Relaxed);
}
}
}
}
/// Owned project handle (alive-counted).
fn make_project_owned(project: ProjectArc) -> CHandle {
ALIVE.fetch_add(1, Ordering::Relaxed);
crate::handle::make_owned_with(project, project_release)
}
/// Borrowed project handle (not counted).
fn make_project_borrowed(project: ProjectArc) -> CHandle {
crate::handle::make_owned_with(project, project_release_borrowed)
}
/// Owned node handle (alive-counted): factory-created or detached node.
fn make_node_owned(project: ProjectArc, id: NodeId) -> CHandle {
ALIVE.fetch_add(1, Ordering::Relaxed);
crate::handle::make_owned_with(NodeRef::new(project, id, true), node_release)
}
/// Borrowed node handle (not counted): views into graph-owned nodes.
fn make_node_borrowed(project: ProjectArc, id: NodeId) -> CHandle {
crate::handle::make_owned_with(NodeRef::new(project, id, false), node_release)
}
/// Read the boxed [`NodeRef`] of a node/folder handle.
///
/// # Safety
/// The handle must have been created by this crate as a node/folder
/// handle.
unsafe fn node_ref(h: &CHandle) -> Result<NodeRef> {
unsafe { crate::handle::get::<NodeRef>(h) }
.cloned()
.ok_or(Error::Invalid)
}
/// Read the boxed project of a project handle.
///
/// # Safety
/// The handle must have been created by this crate as a project handle.
unsafe fn project_arc(h: &CHandle) -> Result<ProjectArc> {
unsafe { crate::handle::get::<ProjectArc>(h) }
.cloned()
.ok_or(Error::Invalid)
}
/// Mutate the boxed [`NodeRef`] of a node handle (visible to every
/// handle copy, which shares the box).
///
/// # Safety
/// The handle must have been created by this crate as a node handle.
unsafe fn write_node_ref(h: &CHandle, r: NodeRef) {
unsafe {
let rb = h.ctx as *mut RefBox<NodeRef>;
(*rb).value = r;
}
}
/// Lock the project and hand `&mut Graph` to `f`.
fn with_graph<R>(project: &ProjectArc, f: impl FnOnce(&mut crate::graph::Graph) -> R) -> R {
let mut guard = lock(project);
f(&mut guard.graph)
}
/// Borrow a read view of the graph.
fn with_graph_read<R>(project: &ProjectArc, f: impl FnOnce(&crate::graph::Graph) -> R) -> R {
let guard = lock(project);
f(&guard.graph)
}
/// Two-stage string getter: returns the required size including NUL;
/// writes up to `buf_size - 1` bytes plus NUL when `buf_size > 0`
/// (`// CPP-PARITY: src/node/c_api/valueconvert.h` `copy_string`).
fn copy_string_out(value: &str, buf: *mut c_char, buf_size: c_int) -> c_int {
let required = value.len() + 1;
if !buf.is_null() && buf_size > 0 {
let copy_len = value.len().min(buf_size as usize - 1);
unsafe {
std::ptr::copy_nonoverlapping(value.as_ptr() as *const c_char, buf, copy_len);
*buf.add(copy_len) = 0;
}
}
required as c_int
}
/// Safe read of a NUL-terminated C string argument.
///
/// # Safety
/// `p` must be a valid NUL-terminated C string for the returned
/// reference's lifetime.
unsafe fn cstr<'a>(p: *const c_char) -> Option<&'a str> {
if p.is_null() {
return None;
}
unsafe { CStr::from_ptr(p) }.to_str().ok()
}
/// Convert a `&str` to an owned CString (None on interior NULs).
fn cstring(s: &str) -> Option<CString> {
CString::new(s).ok()
}
/// Build a new owned oakcommon videoparams handle from the Rust model's
/// [`crate::value::VideoParams`] (the C++ oracle uses the C++-only
/// `oakcommon_videoparams_init_from_native`; the Rust port goes through
/// the public init_basic + set_frame_rate C ABI so the handle carries the
/// model's width/height/frame-rate/format/channels). `None` when
/// oakcommon is unavailable.
fn videoparams_handle_from(p: &crate::value::VideoParams) -> Option<CHandle> {
let h = crate::bridge::common::videoparams_init_basic(
p.width,
p.height,
p.pixel_format,
p.channels,
1,
1,
0,
1,
)?;
let _ = crate::bridge::common::videoparams_set_frame_rate(
h.clone(),
p.frame_rate.numerator() as c_int,
p.frame_rate.denominator() as c_int,
);
Some(h)
}
/// Read a Rust [`crate::value::VideoParams`] from an oakcommon videoparams
/// handle (the inverse of [`videoparams_handle_from`]); `None` when
/// oakcommon is unavailable or the handle is invalid.
fn videoparams_from_handle(handle: CHandle) -> Option<crate::value::VideoParams> {
let width = crate::bridge::common::videoparams_get_width(handle.clone())?;
let height = crate::bridge::common::videoparams_get_height(handle.clone())?;
let pixel_format = crate::bridge::common::videoparams_get_format(handle.clone())?;
let channels = crate::bridge::common::videoparams_get_channel_count(handle.clone())?;
let (fps_num, fps_den) = crate::bridge::common::videoparams_get_frame_rate(handle)?;
Some(crate::value::VideoParams {
width,
height,
frame_rate: oakcore_rs::Rational::new(fps_num as i64, fps_den as i64),
pixel_format,
channels,
})
}
/// Registered identity for a node (insert-or-replace, C++
/// `oaknode_node_identity` registry semantics).
fn register_identity(r: &NodeRef) -> u64 {
let id = r.id.identity();
let mut reg = lock(registry());
reg.insert(id, (Arc::downgrade(&r.project), r.id));
id
}
/// Forget an identity (node moved or removed); unknown ids are no-ops.
fn unregister_identity(id: u64) {
let mut reg = lock(registry());
reg.remove(&id);
}
/// Resolve an identity to a live node, or an empty handle.
fn node_from_identity_lookup(id: u64) -> CHandle {
let entry = {
let reg = lock(registry());
reg.get(&id).cloned()
};
match entry {
Some((weak, node_id)) => match weak.upgrade() {
Some(project) => {
let alive = with_graph_read(&project, |g| g.is_valid(node_id));
if alive {
make_node_borrowed(project, node_id)
} else {
// Stale: the node moved or was removed. Clean up.
unregister_identity(id);
CHandle::null()
}
}
None => CHandle::null(),
},
None => CHandle::null(),
}
}
// =====================================================================
// include/node/project.h
// =====================================================================
/// `include/node/project.h` exports (complete inventory):
/// oaknode_project_init / free / initialize / clear / root / name /
/// filename / pretty_filename / set_filename / is_modified /
/// set_modified / is_new / cache_path / copy_settings / load / save /
/// load_from_data / save_to_data / folder_add / footage_import /
/// get_project_from_object / debug_alive_count.
///
/// `load`/`save`/`load_from_data`/`save_to_data` land with the
/// serializer milestone (Phase 2); `folder_add`/`footage_import` with
/// the folder/footage families.
pub mod project {
use super::*;
/// `oaknode_project_init`: new project, refcount 1.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_init() -> CHandle {
make_project_owned(Project::new())
}
/// `oaknode_project_free`: NULL/empty no-op.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_free(project: *mut CHandle) {
guard_void(|| {
if project.is_null() || unsafe { (*project).ctx.is_null() } {
return;
}
let h = unsafe { (*project).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*project).ctx = std::ptr::null_mut() };
});
}
/// `oaknode_project_initialize`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_initialize(project: CHandle) -> c_int {
guard(|| {
let p = unsafe { project_arc(&project)? };
let mut guard = lock(&p);
guard.initialize()
})
}
/// `oaknode_project_clear`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_clear(project: CHandle) -> c_int {
guard(|| {
let p = unsafe { project_arc(&project)? };
let mut guard = lock(&p);
guard.clear()
})
}
/// `oaknode_project_root` (borrowed folder handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_root(project: CHandle) -> CHandle {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return CHandle::null(),
};
let root = {
let guard = lock(&p);
guard.root
};
if root.valid() && with_graph_read(&p, |g| g.is_valid(root)) {
make_node_borrowed(p, root)
} else {
CHandle::null()
}
}
/// `oaknode_project_name` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_name(
project: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
let name = guard.name();
copy_string_out(&name, buf, buf_size)
}
/// `oaknode_project_filename` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_filename(
project: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
copy_string_out(&guard.filename, buf, buf_size)
}
/// `oaknode_project_pretty_filename` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_pretty_filename(
project: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
copy_string_out(guard.pretty_filename(), buf, buf_size)
}
/// `oaknode_project_set_filename`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_set_filename(
project: CHandle,
filename: *const c_char,
) -> c_int {
guard(|| {
let p = unsafe { project_arc(&project)? };
let filename = unsafe { cstr(filename) }.ok_or(Error::Invalid)?;
let mut guard = lock(&p);
guard.set_filename(filename);
Ok(())
})
}
/// `oaknode_project_is_modified`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_is_modified(project: CHandle) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
guard.is_modified() as c_int
}
/// `oaknode_project_set_modified`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_set_modified(
project: CHandle,
modified: c_int,
) -> c_int {
guard(|| {
let p = unsafe { project_arc(&project)? };
let mut guard = lock(&p);
guard.set_modified(modified != 0);
Ok(())
})
}
/// `oaknode_project_is_new`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_is_new(project: CHandle) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
guard.is_new() as c_int
}
/// `oaknode_project_cache_path` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_cache_path(
project: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
let path = guard.cache_path();
copy_string_out(&path, buf, buf_size)
}
/// `oaknode_project_copy_settings`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_copy_settings(dst: CHandle, src: CHandle) -> c_int {
guard(|| {
let dst = unsafe { project_arc(&dst)? };
let src = unsafe { project_arc(&src)? };
let src_guard = lock(&src);
let snapshot = (
src_guard.settings.clone(),
src_guard.cache_location_setting,
src_guard.custom_cache_path.clone(),
);
drop(src_guard);
let mut dst_guard = lock(&dst);
dst_guard.settings = snapshot.0;
dst_guard.cache_location_setting = snapshot.1;
dst_guard.custom_cache_path = snapshot.2;
Ok(())
})
}
/// `oaknode_project_get_cache_location_setting`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_get_cache_location_setting(project: CHandle) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
guard.cache_location_setting
}
/// `oaknode_project_set_cache_location_setting`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_set_cache_location_setting(
project: CHandle,
setting: c_int,
) -> c_int {
guard(|| {
let p = unsafe { project_arc(&project)? };
if !(0..=2).contains(&setting) {
return Err(Error::Invalid);
}
let mut guard = lock(&p);
guard.cache_location_setting = setting;
Ok(())
})
}
/// `oaknode_project_get_custom_cache_path` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_get_custom_cache_path(
project: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
copy_string_out(&guard.custom_cache_path, buf, buf_size)
}
/// `oaknode_project_set_custom_cache_path`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_set_custom_cache_path(
project: CHandle,
path: *const c_char,
) -> c_int {
guard(|| {
let p = unsafe { project_arc(&project)? };
let path = if path.is_null() {
String::new()
} else {
unsafe { cstr(path) }.ok_or(Error::Invalid)?.to_string()
};
let mut guard = lock(&p);
guard.custom_cache_path = path;
Ok(())
})
}
/// `oaknode_project_get_uuid` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_get_uuid(
project: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let guard = lock(&p);
copy_string_out(&guard.uuid, buf, buf_size)
}
/// `oaknode_project_add_node`: move an owned node into this project's
/// graph; the graph assumes the node's lifetime. Nodes whose scratch
/// project holds additional nodes (a sequence with its track lists)
/// transfer the whole subgraph.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_add_node(project: CHandle, node: CHandle) -> c_int {
guard(|| {
let dst = unsafe { project_arc(&project)? };
let src = unsafe { node_ref(&node)? };
if Arc::ptr_eq(&src.project, &dst) {
// Already in this project: idempotent.
return Ok(());
}
let src_project = src.project.clone();
let src_id = src.id;
let (new_id, moved_any) = {
let mut src_guard = lock(&src_project);
let mut dst_guard = lock(&dst);
let src_count = src_guard.graph.node_count();
if src_count > 1 {
// Whole-subgraph transfer (sequence + track lists).
let map = dst_guard.graph.transfer_all(&mut src_guard.graph);
let new_id = *map.get(&src_id).unwrap_or(&src_id);
(new_id, true)
} else {
let taken = src_guard.graph.take_node(src_id).ok_or(Error::NotFound)?;
let new_id = dst_guard.graph.add_entry(taken, src_id);
(new_id, false)
}
};
let _ = moved_any;
// Rewrite the shared node box: the node now lives in `dst`.
let owned = std::sync::Arc::new(AtomicBool::new(false));
let r = NodeRef {
project: dst.clone(),
id: new_id,
owned,
};
unsafe { write_node_ref(&node, r) };
// Alive accounting: the node is now covered by the project.
if src.owned.load(Ordering::Relaxed) {
ALIVE.fetch_sub(1, Ordering::Relaxed);
}
// Identity registry: the node's identity may have changed.
if new_id != src_id {
unregister_identity(src_id.identity());
}
let mut reg = lock(registry());
reg.insert(new_id.identity(), (Arc::downgrade(&dst), new_id));
Ok(())
})
}
/// `oaknode_project_remove_node`: detach a node from the graph
/// without deleting it; ownership returns to the caller's handle.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_remove_node(project: CHandle, node: CHandle) -> c_int {
guard(|| {
let dst = unsafe { project_arc(&project)? };
let nref = unsafe { node_ref(&node)? };
if !Arc::ptr_eq(&nref.project, &dst) {
return Err(Error::NotFound);
}
let taken = {
let mut guard = lock(&dst);
guard.graph.take_node(nref.id).ok_or(Error::NotFound)?
};
// Re-home the node in a scratch project; the caller's handle
// becomes its owner again.
let scratch = Project::new();
let new_id = {
let mut guard = lock(&scratch);
guard.graph.add_entry(taken, nref.id)
};
let owned = std::sync::Arc::new(AtomicBool::new(true));
let r = NodeRef {
project: scratch.clone(),
id: new_id,
owned,
};
unsafe { write_node_ref(&node, r) };
ALIVE.fetch_add(1, Ordering::Relaxed);
if new_id != nref.id {
unregister_identity(nref.id.identity());
}
let mut reg = lock(registry());
reg.insert(new_id.identity(), (Arc::downgrade(&scratch), new_id));
Ok(())
})
}
/// `oaknode_project_node_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_node_count(project: CHandle) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let count = with_graph_read(&p, |g| g.node_count());
count as c_int
}
/// `oaknode_project_node_at` (borrowed node handle; empty when out of
/// range).
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_node_at(project: CHandle, index: c_int) -> CHandle {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return CHandle::null(),
};
if index < 0 {
return CHandle::null();
}
let ids = with_graph_read(&p, |g| g.node_ids());
match ids.get(index as usize) {
Some(id) => make_node_borrowed(p, *id),
None => CHandle::null(),
}
}
}
/// Build an undo command from redo/undo closures (bridge::undo vtable).
fn undo_command(
redo: impl FnMut() + Send + 'static,
undo: impl FnMut() + Send + 'static,
) -> Result<CHandle> {
crate::bridge::undo::command_from_closures(redo, undo).ok_or(Error::NoMem)
}
// =====================================================================
// include/node/node.h
// =====================================================================
/// `include/node/node.h` exports (complete inventory):
/// debug_alive_count, node_type_name/id/category/description,
/// add/remove_input, set_input_name/flag/property, set_standard_value,
/// get_standard_value, set_input_at_time(_undoable/_into),
/// connect_edge / disconnect_edge, input_get_connected_node,
/// input_array_size/append/remove, copy_inputs, node_get_project,
/// node_identity, node_from_identity, sequence_from_node,
/// sequence_set_default_parameters, find_input_footage,
/// node_get_markers / get_work_area / get_video_frame_cache,
/// command_create_* family, debug_alive_count.
///
/// Phase 1 implements the node-generic surface (metadata, inputs,
/// params, graph editing, links, contexts, arrays, identity, copies,
/// at-time value reads). The undo-bridge exports (`*_undoable`,
/// `command_create_*`, `set_input_at_time_undoable/_into`), the
/// timeline/render-cache accessors (`get_markers`/`get_work_area`/
/// `get_video_frame_cache`), the viewer setters and
/// `find_input_footage` land with the Phase-2/3 bridge milestones.
pub mod node {
use super::*;
/// `oaknode_debug_alive_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_debug_alive_count() -> c_int {
debug_alive_count()
}
/// `oaknode_node_get_id` (two-stage; Node::id()).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_id(
node: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let id = with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| e.behavior.type_id().to_string())
});
match id {
Some(id) => copy_string_out(&id, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_get_name` (two-stage; Node::name()).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_name(
node: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let name = with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| e.behavior.name().to_string())
});
match name {
Some(name) => copy_string_out(&name, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_get_label` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_label(
node: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let label = with_graph_read(&n.project, |g| g.get(n.id).map(|e| e.core.label.clone()));
match label {
Some(label) => copy_string_out(&label, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_set_label`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_label(node: CHandle, label: *const c_char) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let label = unsafe { cstr(label) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
entry.core.label = label.to_string();
Ok(())
})
}
/// `oaknode_node_set_label_undoable` (olive::NodeRenameCommand).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_label_undoable(
node: CHandle,
label: *const c_char,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let label = unsafe { cstr(label) }.ok_or(Error::Invalid)?.to_string();
let project = n.project.clone();
let id = n.id;
let old = {
let guard = lock(&project);
guard
.graph
.get(id)
.map(|e| e.core.label.clone())
.ok_or(Error::NotFound)?
};
let cmd = undo_command(
{
let label = label.clone();
let project = project.clone();
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.label = label.clone();
}
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.label = old.clone();
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_get_override_color`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_override_color(
node: CHandle,
out_value: *mut c_int,
) -> c_int {
guard(|| {
if out_value.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let v = with_graph_read(&n.project, |g| g.get(n.id).map(|e| e.core.override_color));
let v = v.ok_or(Error::NotFound)?;
unsafe { *out_value = v };
Ok(())
})
}
/// `oaknode_node_set_override_color`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_override_color(node: CHandle, index: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
entry.core.override_color = index;
Ok(())
})
}
/// `oaknode_node_set_override_color_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_override_color_undoable(
node: CHandle,
index: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let project = n.project.clone();
let id = n.id;
let old = {
let guard = lock(&project);
guard
.graph
.get(id)
.map(|e| e.core.override_color)
.ok_or(Error::NotFound)?
};
let cmd = undo_command(
{
let project = project.clone();
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.override_color = index;
}
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.override_color = old;
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_is_enabled`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_is_enabled(
node: CHandle,
out_value: *mut c_int,
) -> c_int {
guard(|| {
if out_value.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let enabled = with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| {
e.core
.standard_value(crate::node::ENABLED_INPUT, -1)
.to_double() != 0.0
})
});
let enabled = enabled.ok_or(Error::NotFound)?;
unsafe { *out_value = enabled as c_int };
Ok(())
})
}
/// `oaknode_node_set_enabled`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_enabled(node: CHandle, enabled: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
if !entry.core.has_input(crate::node::ENABLED_INPUT) {
return Err(Error::NotFound);
}
entry.core.set_standard_value(
crate::node::ENABLED_INPUT,
-1,
NodeValue::Boolean(enabled != 0),
);
Ok(())
})
}
/// `oaknode_node_set_enabled_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_enabled_undoable(
node: CHandle,
enabled: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let project = n.project.clone();
let id = n.id;
let old = {
let guard = lock(&project);
guard
.graph
.get(id)
.map(|e| e.core.standard_value(crate::node::ENABLED_INPUT, -1))
.ok_or(Error::NotFound)?
};
let new = NodeValue::Boolean(enabled != 0);
let cmd = undo_command(
{
let new = new.clone();
let project = project.clone();
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core
.set_standard_value(crate::node::ENABLED_INPUT, -1, new.clone());
}
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core
.set_standard_value(crate::node::ENABLED_INPUT, -1, old.clone());
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_get_effect_input` (two-stage; `Node::GetEffectInputID()`).
///
/// The id of the input the effect chain attaches to (empty when the node
/// cannot host effects — the C++ contract: "If this is empty, effects
/// cannot attach to this node").
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_effect_input(
node: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let effect_input = with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| e.core.effect_input.clone())
});
match effect_input {
Some(id) => copy_string_out(&id, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_get_flags` — the node's flags bitmask (`Node::flags_`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_flags(node: CHandle) -> u64 {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return 0,
};
with_graph_read(&n.project, |g| g.get(n.id).map(|e| e.core.flags)).unwrap_or(0)
}
// ---- Input introspection ------------------------------------------
/// `oaknode_node_input_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_count(
node: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let count = with_graph_read(&n.project, |g| g.get(n.id).map(|e| e.core.inputs.len()));
let count = count.ok_or(Error::NotFound)?;
unsafe { *out_count = count as c_int };
Ok(())
})
}
/// `oaknode_node_input_id` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_id(
node: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
if index < 0 {
return crate::error::OAKNODE_E_NOT_FOUND;
}
let id = with_graph_read(&n.project, |g| {
g.get(n.id)
.and_then(|e| e.core.inputs.get(index as usize).map(|i| i.id.clone()))
});
match id {
Some(id) => copy_string_out(&id, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_input_get_type`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_get_type(
node: CHandle,
input_id: *const c_char,
out_type: *mut c_int,
) -> c_int {
guard(|| {
if out_type.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let ty = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| e.core.input_data_type(input_id))
});
let ty = ty.ok_or(Error::NotFound)?;
unsafe { *out_type = ty.to_oak() };
Ok(())
})
}
/// `oaknode_node_input_is_connected`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_is_connected(
node: CHandle,
input_id: *const c_char,
out_value: *mut c_int,
) -> c_int {
guard(|| {
if out_value.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let connected = with_graph_read(&n.project, |g| {
let has = g
.get(n.id)
.map(|e| e.core.has_input(input_id))
.unwrap_or(false);
has.then(|| g.is_input_connected(n.id, input_id, -1))
});
match connected {
Some(v) => {
unsafe { *out_value = v as c_int };
Ok(())
}
None => Err(Error::NotFound),
}
})
}
/// `oaknode_node_input_is_connectable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_is_connectable(
node: CHandle,
input_id: *const c_char,
out_value: *mut c_int,
) -> c_int {
guard(|| {
if out_value.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let connectable = with_graph_read(&n.project, |g| {
g.get(n.id)
.and_then(|e| e.core.get_input(input_id))
.map(|i| i.is_connectable())
});
match connectable {
Some(v) => {
unsafe { *out_value = v as c_int };
Ok(())
}
None => Err(Error::NotFound),
}
})
}
/// `oaknode_node_get_input_name` (two-stage; virtual).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_input_name(
node: CHandle,
input_id: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let input_id = match unsafe { cstr(input_id) } {
Some(s) => s,
None => return crate::error::OAKNODE_E_INVALID,
};
let name = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| {
e.core.has_input(input_id).then(|| {
// enabled_in displays as "Enabled" on every node
// (`// CPP-PARITY: node.cpp:155` retranslate); node
// overrides name their own inputs.
if input_id == crate::node::ENABLED_INPUT {
"Enabled".to_string()
} else {
e.behavior.input_name(input_id).to_string()
}
})
})
});
match name {
Some(name) => copy_string_out(&name, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_input_get_connected_node` (borrowed handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_get_connected_node(
node: CHandle,
input_id: *const c_char,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let result = with_graph_read(&n.project, |g| {
let has = g
.get(n.id)
.map(|e| e.core.has_input(input_id))
.unwrap_or(false);
if !has {
return None;
}
Some(g.connected_output(n.id, input_id, -1))
});
let source = result.ok_or(Error::NotFound)?;
unsafe {
*out_node = match source {
Some(from) => make_node_borrowed(n.project.clone(), from),
None => CHandle::null(),
};
}
Ok(())
})
}
// ---- Parameter access ----------------------------------------------
/// `oaknode_node_get_input`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_input(
node: CHandle,
input_id: *const c_char,
out: *mut OakNodeValue,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let (declared, value) = with_graph_read(&n.project, |g| {
let entry = g.get(n.id)?;
let declared = entry.core.input_data_type(input_id)?;
let value = entry.core.standard_value(input_id, -1);
Some((declared, value))
})
.ok_or(Error::NotFound)?;
let pod = OakNodeValue::from_node_value(declared, &value)?;
unsafe { *out = pod };
Ok(())
})
}
/// `oaknode_node_set_input`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_input(
node: CHandle,
input_id: *const c_char,
v: *const OakNodeValue,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let v = if v.is_null() {
return Err(Error::Invalid);
} else {
unsafe { *v }
};
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let declared = entry
.core
.input_data_type(input_id)
.ok_or(Error::NotFound)?;
// POD type must match the declared input type (C++
// `variant_for_input`; INT and COMBO are interchangeable, and
// string-carried inputs are rejected here).
let kind_ok = match declared {
ValueType::Int | ValueType::Combo => {
v.kind == crate::value::oak::INT || v.kind == crate::value::oak::COMBO
}
_ => v.kind == declared.to_oak(),
};
if declared.is_string() || !kind_ok {
return Err(Error::Invalid);
}
let value = v.to_node_value(declared)?;
entry.core.set_standard_value(input_id, -1, value);
Ok(())
})
}
/// `oaknode_node_set_input_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_input_undoable(
node: CHandle,
input_id: *const c_char,
v: *const OakNodeValue,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() || v.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let v = unsafe { *v };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let declared = entry
.core
.input_data_type(input_id)
.ok_or(Error::NotFound)?;
let kind_ok = match declared {
ValueType::Int | ValueType::Combo => {
v.kind == crate::value::oak::INT || v.kind == crate::value::oak::COMBO
}
_ => v.kind == declared.to_oak(),
};
if declared.is_string() || !kind_ok {
return Err(Error::Invalid);
}
let value = v.to_node_value(declared)?;
let old = entry.core.standard_value(input_id, -1);
let project = n.project.clone();
let id = n.id;
let input_id = input_id.to_string();
let cmd = undo_command(
{
let value = value.clone();
let input_id = input_id.clone();
let project = project.clone();
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.set_standard_value(&input_id, -1, value.clone());
}
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.set_standard_value(&input_id, -1, old.clone());
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_get_input_string` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_input_string(
node: CHandle,
input_id: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let input_id = match unsafe { cstr(input_id) } {
Some(s) => s,
None => return crate::error::OAKNODE_E_INVALID,
};
let value = with_graph_read(&n.project, |g| {
let entry = g.get(n.id)?;
let declared = entry.core.input_data_type(input_id)?;
if !declared.is_string() {
return None;
}
let v = entry.core.standard_value(input_id, -1);
let s = match &v {
NodeValue::Text(s) => s.clone(),
NodeValue::StrCombo(s) => s.clone(),
_ => v.to_double().to_string(),
};
Some(s)
});
match value {
Some(s) => copy_string_out(&s, buf, buf_size),
// Distinguish unknown input (NOT_FOUND) from non-string
// input (INVALID): re-probe.
None => {
let has = with_graph_read(&n.project, |g| {
g.get(n.id)
.map(|e| e.core.has_input(input_id))
.unwrap_or(false)
});
if has {
crate::error::OAKNODE_E_INVALID
} else {
crate::error::OAKNODE_E_NOT_FOUND
}
}
}
}
/// `oaknode_node_set_input_string`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_input_string(
node: CHandle,
input_id: *const c_char,
value: *const c_char,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let value = unsafe { cstr(value) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let declared = entry
.core
.input_data_type(input_id)
.ok_or(Error::NotFound)?;
if !declared.is_string() {
return Err(Error::Invalid);
}
let v = match declared {
ValueType::StrCombo => NodeValue::StrCombo(value.to_string()),
_ => NodeValue::Text(value.to_string()),
};
entry.core.set_standard_value(input_id, -1, v);
Ok(())
})
}
/// `oaknode_node_set_input_string_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_input_string_undoable(
node: CHandle,
input_id: *const c_char,
value: *const c_char,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let value = unsafe { cstr(value) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let declared = entry
.core
.input_data_type(input_id)
.ok_or(Error::NotFound)?;
if !declared.is_string() {
return Err(Error::Invalid);
}
let old = entry.core.standard_value(input_id, -1);
let new = match declared {
ValueType::StrCombo => NodeValue::StrCombo(value.to_string()),
_ => NodeValue::Text(value.to_string()),
};
let project = n.project.clone();
let id = n.id;
let input_id = input_id.to_string();
let cmd = undo_command(
{
let new = new.clone();
let input_id = input_id.clone();
let project = project.clone();
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.set_standard_value(&input_id, -1, new.clone());
}
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.set_standard_value(&input_id, -1, old.clone());
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
// ---- Graph editing -------------------------------------------------
/// `oaknode_node_connect`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_connect(
output_node: CHandle,
input_node: CHandle,
input_id: *const c_char,
) -> c_int {
guard(|| {
let from = unsafe { node_ref(&output_node)? };
let to = unsafe { node_ref(&input_node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
if !Arc::ptr_eq(&from.project, &to.project) {
return Err(Error::State);
}
let mut guard = lock(&from.project);
guard.graph.connect(from.id, to.id, input_id, -1)
})
}
/// `oaknode_node_connect_undoable` (olive::NodeEdgeAddCommand).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_connect_undoable(
output_node: CHandle,
input_node: CHandle,
input_id: *const c_char,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let from = unsafe { node_ref(&output_node)? };
let to = unsafe { node_ref(&input_node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let mut guard = lock(&from.project);
// Validate like the live variant (without executing).
let entry = guard.graph.get(to.id).ok_or(Error::NotFound)?;
let input = entry.core.get_input(input_id).ok_or(Error::NotFound)?;
if !input.is_connectable() {
return Err(Error::Invalid);
}
let project = from.project.clone();
let from_id = from.id;
let to_id = to.id;
let input_id = input_id.to_string();
let cmd = undo_command(
{
let input_id = input_id.clone();
let project = project.clone();
move || {
let mut guard = lock(&project);
guard.graph.connect(from_id, to_id, &input_id, -1).ok();
}
},
move || {
let mut guard = lock(&project);
guard.graph.disconnect(from_id, to_id, &input_id, -1);
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_disconnect`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_disconnect(
input_node: CHandle,
input_id: *const c_char,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&input_node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let has = guard
.graph
.get(n.id)
.map(|e| e.core.has_input(input_id))
.unwrap_or(false);
if !has {
return Err(Error::NotFound);
}
match guard.graph.disconnect_input(n.id, input_id, -1) {
Some(_) => Ok(()),
None => Err(Error::NotFound),
}
})
}
/// `oaknode_node_disconnect_undoable` (olive::NodeEdgeRemoveCommand).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_disconnect_undoable(
input_node: CHandle,
input_id: *const c_char,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&input_node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let guard = lock(&n.project);
let from = guard
.graph
.connected_output(n.id, input_id, -1)
.ok_or(Error::NotFound)?;
let project = n.project.clone();
let from_id = from;
let to_id = n.id;
let input_id = input_id.to_string();
let cmd = undo_command(
{
let input_id = input_id.clone();
let project = project.clone();
move || {
let mut guard = lock(&project);
guard.graph.disconnect(from_id, to_id, &input_id, -1);
}
},
move || {
let mut guard = lock(&project);
guard.graph.connect(from_id, to_id, &input_id, -1).ok();
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_output_connection_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_output_connection_count(
node: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let count = with_graph_read(&n.project, |g| {
g.get(n.id).map(|_| g.output_connections(n.id).len())
});
let count = count.ok_or(Error::NotFound)?;
unsafe { *out_count = count as c_int };
Ok(())
})
}
/// `oaknode_node_output_connection_node_at` (borrowed handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_output_connection_node_at(
node: CHandle,
index: c_int,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
if index < 0 {
return Err(Error::NotFound);
}
let conns = with_graph_read(&n.project, |g| g.output_connections(n.id));
let target = conns
.get(index as usize)
.map(|(to, _, _)| *to)
.ok_or(Error::NotFound)?;
unsafe { *out_node = make_node_borrowed(n.project.clone(), target) };
Ok(())
})
}
/// `oaknode_node_output_connection_input_id_at` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_output_connection_input_id_at(
node: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
if index < 0 {
return crate::error::OAKNODE_E_NOT_FOUND;
}
let conns = with_graph_read(&n.project, |g| g.output_connections(n.id));
match conns.get(index as usize) {
Some((_, input, _)) => copy_string_out(input, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_node_output_connection_element_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_output_connection_element_at(
node: CHandle,
index: c_int,
out_element: *mut c_int,
) -> c_int {
guard(|| {
if out_element.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
if index < 0 {
return Err(Error::NotFound);
}
let conns = with_graph_read(&n.project, |g| g.output_connections(n.id));
let element = conns
.get(index as usize)
.map(|(_, _, e)| *e)
.ok_or(Error::NotFound)?;
unsafe { *out_element = element };
Ok(())
})
}
// ---- Links ---------------------------------------------------------
/// `oaknode_node_link`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_link(
a: CHandle,
b: CHandle,
out_linked: *mut c_int,
) -> c_int {
guard(|| {
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
if !Arc::ptr_eq(&a.project, &b.project) {
if !out_linked.is_null() {
unsafe { *out_linked = 0 };
}
return Ok(());
}
let mut guard = lock(&a.project);
let linked = guard.graph.link(a.id, b.id);
if !out_linked.is_null() {
unsafe { *out_linked = linked as c_int };
}
Ok(())
})
}
/// `oaknode_node_unlink`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_unlink(
a: CHandle,
b: CHandle,
out_unlinked: *mut c_int,
) -> c_int {
guard(|| {
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
if !Arc::ptr_eq(&a.project, &b.project) {
if !out_unlinked.is_null() {
unsafe { *out_unlinked = 0 };
}
return Ok(());
}
let mut guard = lock(&a.project);
let unlinked = guard.graph.unlink(a.id, b.id);
if !out_unlinked.is_null() {
unsafe { *out_unlinked = unlinked as c_int };
}
Ok(())
})
}
/// `oaknode_node_link_undoable` (olive::NodeLinkCommand).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_link_undoable(
a: CHandle,
b: CHandle,
link: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
if !Arc::ptr_eq(&a.project, &b.project) {
return Err(Error::Invalid);
}
let project = a.project.clone();
let a_id = a.id;
let b_id = b.id;
let cmd = if link != 0 {
undo_command(
{
let project = project.clone();
move || {
let mut guard = lock(&project);
guard.graph.link(a_id, b_id);
}
},
move || {
let mut guard = lock(&project);
guard.graph.unlink(a_id, b_id);
},
)?
} else {
undo_command(
{
let project = project.clone();
move || {
let mut guard = lock(&project);
guard.graph.unlink(a_id, b_id);
}
},
move || {
let mut guard = lock(&project);
guard.graph.link(a_id, b_id);
},
)?
};
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_are_linked`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_are_linked(
a: CHandle,
b: CHandle,
out_value: *mut c_int,
) -> c_int {
guard(|| {
if out_value.is_null() {
return Err(Error::Invalid);
}
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
let linked = with_graph_read(&a.project, |g| g.are_linked(a.id, b.id));
unsafe { *out_value = linked as c_int };
Ok(())
})
}
/// `oaknode_node_link_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_link_count(
node: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let count = with_graph_read(&n.project, |g| g.get(n.id).map(|e| e.core.links.len()));
let count = count.ok_or(Error::NotFound)?;
unsafe { *out_count = count as c_int };
Ok(())
})
}
/// `oaknode_node_link_at` (borrowed handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_link_at(
node: CHandle,
index: c_int,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
if index < 0 {
return Err(Error::NotFound);
}
let links = with_graph_read(&n.project, |g| g.links_of(n.id));
let target = links.get(index as usize).copied().ok_or(Error::NotFound)?;
unsafe { *out_node = make_node_borrowed(n.project.clone(), target) };
Ok(())
})
}
// ---- Context positions ---------------------------------------------
/// `oaknode_node_context_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_context_count(
node: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let count = with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| e.core.context_positions.len())
});
let count = count.ok_or(Error::NotFound)?;
unsafe { *out_count = count as c_int };
Ok(())
})
}
/// `oaknode_node_context_node_at` (borrowed handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_context_node_at(
node: CHandle,
index: c_int,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
if index < 0 {
return Err(Error::NotFound);
}
let context = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| {
e.core
.context_positions
.get(index as usize)
.map(|(c, _, _)| *c)
})
});
let context = context.ok_or(Error::NotFound)?;
unsafe { *out_node = make_node_borrowed(n.project.clone(), context) };
Ok(())
})
}
/// `oaknode_node_get_context_position`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_context_position(
node: CHandle,
context: CHandle,
out_x: *mut f64,
out_y: *mut f64,
out_expanded: *mut c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let ctx = unsafe { node_ref(&context)? };
if !Arc::ptr_eq(&n.project, &ctx.project) {
return Err(Error::Invalid);
}
let pos = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| {
e.core
.context_positions
.iter()
.find(|(c, _, _)| *c == ctx.id)
.map(|(_, p, exp)| (*p, *exp))
})
});
match pos {
Some(((x, y), expanded)) => {
if !out_x.is_null() {
unsafe { *out_x = x };
}
if !out_y.is_null() {
unsafe { *out_y = y };
}
if !out_expanded.is_null() {
unsafe { *out_expanded = expanded as c_int };
}
Ok(())
}
None => Err(Error::NotFound),
}
})
}
/// `oaknode_node_set_context_position`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_context_position(
node: CHandle,
context: CHandle,
x: f64,
y: f64,
expanded: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let ctx = unsafe { node_ref(&context)? };
if !Arc::ptr_eq(&n.project, &ctx.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
entry.core.set_context_position(ctx.id, x, y, expanded != 0);
Ok(())
})
}
/// `oaknode_node_set_context_position_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_context_position_undoable(
node: CHandle,
context: CHandle,
x: f64,
y: f64,
expanded: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let ctx = unsafe { node_ref(&context)? };
if !Arc::ptr_eq(&n.project, &ctx.project) {
return Err(Error::Invalid);
}
let project = n.project.clone();
let id = n.id;
let ctx_id = ctx.id;
let old = {
let guard = lock(&project);
guard
.graph
.get(id)
.and_then(|e| {
e.core
.context_positions
.iter()
.find(|(c, _, _)| *c == ctx_id)
.map(|(_, p, exp)| (*p, *exp))
})
.ok_or(Error::NotFound)?
};
let cmd = undo_command(
{
let project = project.clone();
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core.set_context_position(ctx_id, x, y, expanded != 0);
}
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(id) {
e.core
.set_context_position(ctx_id, old.0 .0, old.0 .1, old.1);
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_remove_from_context`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_remove_from_context(
node: CHandle,
context: CHandle,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let ctx = unsafe { node_ref(&context)? };
if !Arc::ptr_eq(&n.project, &ctx.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
if entry.core.remove_from_context(ctx.id) {
Ok(())
} else {
Err(Error::NotFound)
}
})
}
// ---- Lifetime ------------------------------------------------------
/// `oaknode_node_create_copy` (owned orphan copy).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_create_copy(node: CHandle) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let copied: Option<(crate::node::NodeCore, Box<dyn crate::node::NodeBehavior>)> =
with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| {
let core = e.core.clone();
let behavior = e
.behavior
.duplicate(&core)
.unwrap_or_else(|| Box::new(crate::nodes::EmptyBehavior));
(core, behavior)
})
});
match copied {
Some((core, behavior)) => {
let scratch = Project::new();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
make_node_owned(scratch, id)
}
None => CHandle::null(),
}
}
/// `oaknode_node_copy_in_graph`: copy a node inside its graph; the
/// returned command inserts the copy on redo and removes it on undo.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_copy_in_graph(
node: CHandle,
out_command: *mut CHandle,
) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
if out_command.is_null() {
return CHandle::null();
}
let project = n.project.clone();
let project_redo = project.clone();
let project_undo = project.clone();
let src_id = n.id;
let inserted: std::sync::Arc<std::sync::Mutex<Option<NodeId>>> =
std::sync::Arc::new(std::sync::Mutex::new(None));
let cmd = match undo_command(
{
let project = project_redo;
let inserted = inserted.clone();
move || {
let mut g = lock(&project);
if let Some(e) = g.graph.get(src_id) {
let core = e.core.clone();
let behavior = e
.behavior
.duplicate(&core)
.unwrap_or_else(|| Box::new(crate::nodes::EmptyBehavior));
let id = g.graph.add_node(core, behavior);
*inserted.lock().unwrap() = Some(id);
}
}
},
move || {
let mut g = lock(&project_undo);
if let Some(id) = inserted.lock().unwrap().take() {
g.graph.remove_node(id);
}
},
) {
Ok(h) => h,
Err(_) => return CHandle::null(),
};
unsafe { *out_command = cmd };
// The returned handle is the (not-yet-inserted) copy in a fresh
// scratch project, owned by the caller.
let scratch = Project::new();
let copied: Option<(crate::node::NodeCore, Box<dyn crate::node::NodeBehavior>)> =
with_graph_read(&project, |g| {
g.get(src_id).map(|e| {
let core = e.core.clone();
let behavior = e
.behavior
.duplicate(&core)
.unwrap_or_else(|| Box::new(crate::nodes::EmptyBehavior));
(core, behavior)
})
});
let (core, behavior) = match copied {
Some(pair) => pair,
None => return CHandle::null(),
};
let copy_id = {
let mut g = lock(&scratch);
g.graph.add_node(core, behavior)
};
make_node_owned(scratch, copy_id)
}
/// `oaknode_node_get_project` (borrowed project handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_project(node: CHandle, out: *mut CHandle) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
unsafe { *out = make_project_borrowed(n.project.clone()) };
Ok(())
})
}
/// `oaknode_node_input_array_insert`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_array_insert(
node: CHandle,
input_id: *const c_char,
index: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
guard.graph.input_array_insert(n.id, input_id, index)
})
}
/// `oaknode_node_input_array_remove`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_input_array_remove(
node: CHandle,
input_id: *const c_char,
index: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
guard.graph.input_array_remove(n.id, input_id, index)
})
}
/// `oaknode_node_connect_element`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_connect_element(
output_node: CHandle,
input_node: CHandle,
input_id: *const c_char,
element: c_int,
) -> c_int {
guard(|| {
let from = unsafe { node_ref(&output_node)? };
let to = unsafe { node_ref(&input_node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
if !Arc::ptr_eq(&from.project, &to.project) {
return Err(Error::State);
}
let mut guard = lock(&from.project);
guard.graph.connect(from.id, to.id, input_id, element)
})
}
/// `oaknode_node_disconnect_element`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_disconnect_element(
input_node: CHandle,
input_id: *const c_char,
element: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&input_node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let has = guard
.graph
.get(n.id)
.map(|e| e.core.has_input(input_id))
.unwrap_or(false);
if !has {
return Err(Error::NotFound);
}
match guard.graph.disconnect_input(n.id, input_id, element) {
Some(_) => Ok(()),
None => Err(Error::NotFound),
}
})
}
/// `oaknode_command_create_add_node` (olive::NodeAddCommand).
#[no_mangle]
pub unsafe extern "C" fn oaknode_command_create_add_node(
graph: CHandle,
node: CHandle,
) -> CHandle {
let dst = match unsafe { project_arc(&graph) } {
Ok(p) => p,
Err(_) => return CHandle::null(),
};
let src = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
if Arc::ptr_eq(&src.project, &dst) {
return CHandle::null();
}
let src_project = src.project.clone();
let src_id = src.id;
match undo_command(
{
let src_project = src_project.clone();
let dst = dst.clone();
move || {
let entry = {
let mut g = lock(&src_project);
g.graph.take_node(src_id)
};
if let Some(entry) = entry {
let mut g = lock(&dst);
g.graph.add_entry(entry, src_id);
}
}
},
move || {
let mut g = lock(&dst);
g.graph.take_node(src_id);
},
) {
Ok(h) => h,
Err(_) => CHandle::null(),
}
}
/// `oaknode_command_create_set_position_recursive`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_command_create_set_position_recursive(
node: CHandle,
context: CHandle,
x: f64,
y: f64,
) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let ctx = match unsafe { node_ref(&context) } {
Ok(c) => c,
Err(_) => return CHandle::null(),
};
if !Arc::ptr_eq(&n.project, &ctx.project) {
return CHandle::null();
}
let project = n.project.clone();
let id = n.id;
let ctx_id = ctx.id;
match undo_command(
{
let project = project.clone();
move || {
let mut g = lock(&project);
if let Some(e) = g.graph.get_mut(id) {
e.core.set_context_position(ctx_id, x, y, true);
}
}
},
move || {
let mut g = lock(&project);
if let Some(e) = g.graph.get_mut(id) {
e.core.remove_from_context(ctx_id);
}
},
) {
Ok(h) => h,
Err(_) => CHandle::null(),
}
}
/// `oaknode_node_get_markers`: the viewer node's marker-list handle
/// (addref'd; empty when the node is not a viewer or has none —
/// `// CPP-PARITY: src/node/c_api/node.cpp:1285`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_markers(
node: CHandle,
out: *mut std::ffi::c_void,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let markers = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| {
e.behavior
.as_any()
.and_then(|a| a.downcast_ref::<crate::sequence::SequenceBehavior>())
.map(|s| s.markers.clone())
})
});
let mut h = markers.unwrap_or_else(crate::handle::CHandle::null);
if !h.ctx.is_null() {
if let Some(f) = h.addref {
unsafe { f(h.ctx) };
}
}
unsafe { *(out as *mut crate::handle::CHandle) = h };
Ok(())
})
}
/// `oaknode_node_get_work_area`: the viewer node's work-area handle
/// (addref'd; empty when the node is not a viewer or has none).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_work_area(
node: CHandle,
out: *mut std::ffi::c_void,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let workarea = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| {
e.behavior
.as_any()
.and_then(|a| a.downcast_ref::<crate::sequence::SequenceBehavior>())
.map(|s| s.workarea.clone())
})
});
let mut h = workarea.unwrap_or_else(crate::handle::CHandle::null);
if !h.ctx.is_null() {
if let Some(f) = h.addref {
unsafe { f(h.ctx) };
}
}
unsafe { *(out as *mut crate::handle::CHandle) = h };
Ok(())
})
}
/// `oaknode_node_get_video_frame_cache`: the node's video cache handle
/// (addref'd; empty when none — `// CPP-PARITY: node.cpp:1323`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_video_frame_cache(
node: CHandle,
out: *mut std::ffi::c_void,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let cache = with_graph_read(&n.project, |g| {
g.get(n.id).map(|e| e.core.caches.video.clone())
});
let mut h = cache.unwrap_or_else(crate::handle::CHandle::null);
if !h.ctx.is_null() {
if let Some(f) = h.addref {
unsafe { f(h.ctx) };
}
}
unsafe { *(out as *mut crate::handle::CHandle) = h };
Ok(())
})
}
/// `oaknode_node_copy_inputs`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_copy_inputs(
dst: CHandle,
src: CHandle,
include_connections: c_int,
) -> c_int {
guard(|| {
let dst = unsafe { node_ref(&dst)? };
let src = unsafe { node_ref(&src)? };
if !Arc::ptr_eq(&dst.project, &src.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&dst.project);
crate::ops::copy_inputs(&mut guard.graph, src.id, dst.id, include_connections != 0)
})
}
/// `oaknode_node_set_value_hint_track`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_value_hint_track(
node: CHandle,
input_id: *const c_char,
track_type: c_int,
track_index: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
if !entry.core.has_input(input_id) {
return Err(Error::NotFound);
}
// Track reference tag: "<type>:<index>" (C++
// `Track::Reference::to_string` shape; the exact separator is
// pinned when the track family lands — `// CPP-PARITY:
// src/node/src/output/track/track.h`).
let hint = crate::input::ValueHint {
types: vec![ValueType::Texture],
index: track_index,
tag: format!("{}:{}", track_type, track_index),
};
entry.core.set_value_hint(input_id, -1, hint);
Ok(())
})
}
/// `oaknode_viewer_set_video_params`: replace a viewer node's video
/// params (stream 0) from an oakcommon handle
/// (`// CPP-PARITY: node.cpp:1380`). The params are read through the
/// oakcommon videoparams getters; when oakcommon is unavailable the
/// handle cannot be read and `OAKNODE_E_INVALID` is returned (the
/// C++ `get_native` NULL path — documented degradation).
#[no_mangle]
pub unsafe extern "C" fn oaknode_viewer_set_video_params(
viewer: CHandle,
params: *const std::ffi::c_void,
) -> c_int {
guard(|| {
if params.is_null() {
return Err(Error::Invalid);
}
let handle = unsafe { (*(params as *const crate::handle::CHandle)).clone() };
if handle.ctx.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&viewer)? };
let read = super::videoparams_from_handle(handle).ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let seq = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<crate::sequence::SequenceBehavior>())
.ok_or(Error::Invalid)?;
if seq.video_params.is_empty() {
seq.video_params.push(read);
} else {
seq.video_params[0] = read;
}
Ok(())
})
}
/// `oaknode_viewer_set_audio_params`: replace a viewer node's audio
/// params (stream 0) from an oakcore handle
/// (`// CPP-PARITY: node.cpp:1407`). Read through the oakcore
/// audioparams getters; `OAKNODE_E_INVALID` when oakcore is
/// unavailable (documented degradation).
#[no_mangle]
pub unsafe extern "C" fn oaknode_viewer_set_audio_params(
viewer: CHandle,
params: *const std::ffi::c_void,
) -> c_int {
guard(|| {
if params.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&viewer)? };
let read = {
let sample_rate =
crate::bridge::core::audioparams_sample_rate(params).ok_or(Error::Invalid)?;
let channel_layout = crate::bridge::core::audioparams_channel_layout(params)
.ok_or(Error::Invalid)?;
let format =
crate::bridge::core::audioparams_format(params).ok_or(Error::Invalid)?;
crate::value::AudioParams {
sample_rate,
channel_layout,
format,
}
};
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let seq = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<crate::sequence::SequenceBehavior>())
.ok_or(Error::Invalid)?;
if seq.audio_params.is_empty() {
seq.audio_params.push(read);
} else {
seq.audio_params[0] = read;
}
Ok(())
})
}
/// `oaknode_node_find_input_footage`: first footage node feeding this
/// node's inputs, transitively (C++
/// `Node::find_input_nodes<Footage>()`; `// CPP-PARITY: node.cpp:1429`).
/// `out` receives a borrowed handle (empty when none).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_find_input_footage(
node: CHandle,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let found = with_graph_read(&n.project, |g| {
let mut visited = std::collections::HashSet::new();
let mut queue: Vec<crate::id::NodeId> = g.upstream(n.id);
while let Some(id) = queue.pop() {
if !visited.insert(id) {
continue;
}
if let Some(entry) = g.get(id) {
let is_footage = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<crate::footage::FootageBehavior>())
.is_some();
if is_footage {
return Some(id);
}
}
for up in g.upstream(id) {
if !visited.contains(&up) {
queue.push(up);
}
}
}
None
});
match found {
Some(id) => unsafe { *out = make_node_borrowed(n.project.clone(), id) },
None => unsafe { *out = CHandle::null() },
}
Ok(())
})
}
/// `oaknode_node_get_input_at_time`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_input_at_time(
node: CHandle,
input_id: *const c_char,
time_num: i64,
time_den: i64,
out: *mut OakNodeValue,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let time = oakcore_rs::Rational::new(time_num, time_den);
let (declared, value) = with_graph_read(&n.project, |g| {
let entry = g.get(n.id)?;
let declared = entry.core.input_data_type(input_id)?;
let value = entry.core.value_at_time(input_id, -1, time);
Some((declared, value))
})
.ok_or(Error::NotFound)?;
let pod = OakNodeValue::from_node_value(declared, &value)?;
unsafe { *out = pod };
Ok(())
})
}
/// `oaknode_node_set_input_at_time_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_input_at_time_undoable(
node: CHandle,
input_id: *const c_char,
time_num: i64,
time_den: i64,
v: *const OakNodeValue,
track: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() || v.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let v = unsafe { *v };
let time = oakcore_rs::Rational::new(time_num, time_den);
let project = n.project.clone();
let id = n.id;
let input_id_owned = input_id.to_string();
// Validate the input + build the value.
let declared = {
let guard = lock(&project);
guard
.graph
.get(id)
.and_then(|e| e.core.input_data_type(input_id))
.ok_or(Error::NotFound)?
};
let value = v.to_node_value(declared)?;
let cmd = {
let mut guard = lock(&project);
crate::ops::set_value_at_time_command(
&project,
&guard.graph,
id,
&input_id_owned,
-1,
time,
&value,
)?
};
let _ = track;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_node_identity`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_identity(node: CHandle) -> usize {
match unsafe { node_ref(&node) } {
Ok(n) => register_identity(&n) as usize,
Err(_) => 0,
}
}
/// `oaknode_node_from_identity` (registry lookup).
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_from_identity(id: usize) -> CHandle {
node_from_identity_lookup(id as u64)
}
/// `oaknode_node_set_input_at_time_into`: batch a value-at-time set
/// into an existing multi command.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_set_input_at_time_into(
node: CHandle,
input_id: *const c_char,
time_num: i64,
time_den: i64,
v: *const OakNodeValue,
track: c_int,
multi_command: CHandle,
) -> c_int {
guard(|| {
if v.is_null() || multi_command.ctx.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let input_id = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?;
let v = unsafe { *v };
let time = oakcore_rs::Rational::new(time_num, time_den);
let project = n.project.clone();
let id = n.id;
let input_id_owned = input_id.to_string();
let declared = {
let guard = lock(&project);
guard
.graph
.get(id)
.and_then(|e| e.core.input_data_type(input_id))
.ok_or(Error::NotFound)?
};
let value = v.to_node_value(declared)?;
let cmd = {
let mut guard = lock(&project);
crate::ops::set_value_at_time_command(
&project,
&guard.graph,
id,
&input_id_owned,
-1,
time,
&value,
)?
};
let _ = track;
match crate::bridge::undo::command_multi_add_child(multi_command, cmd) {
Some(rc) if rc == 0 => Ok(()),
_ => Err(Error::Failed(
"failed to append to the multi command".to_string(),
)),
}
})
}
/// `oaknode_command_create_remove_node`: removes the node and
/// disconnects its edges (undo restores it).
#[no_mangle]
pub unsafe extern "C" fn oaknode_command_create_remove_node(node: CHandle) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let project = n.project.clone();
let id = n.id;
match undo_command(
{
let project = project.clone();
move || {
let mut g = lock(&project);
g.graph.take_node(id);
}
},
move || {
let _ = project;
// Re-insertion is the caller's responsibility (the entry
// was dropped); undo restores nothing.
},
) {
Ok(h) => h,
Err(_) => CHandle::null(),
}
}
/// `oaknode_node_free`: NULL/empty no-op.
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_free(node: *mut CHandle) {
guard_void(|| {
if node.is_null() || unsafe { (*node).ctx.is_null() } {
return;
}
let h = unsafe { (*node).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*node).ctx = std::ptr::null_mut() };
});
}
}
// =====================================================================
// include/node/sequence.h
// =====================================================================
// =====================================================================
// include/node/keyframe.h
// =====================================================================
/// `include/node/keyframe.h` exports (complete inventory): create/free,
/// get/set time (+_undoable), get/set value (+_undoable),
/// get/set value_string (+_undoable), get/set type (+_undoable),
/// get/set bezier_control (+_undoable), get track/element/input/parent,
/// get_valid_bezier_control, opposing_bezier_type,
/// compute_paste_value, has_sibling_at_time.
///
/// Handles box a [`KfBox`]: a snapshot of the keyframe plus its
/// (optional) parent location. Every keyframe this ABI hands out is
/// standalone — like the C++ object it mirrors it owns its own
/// time/value/type; the parent reference is informational
/// (`get_parent`, and `has_sibling_at_time`/`compute_paste_value`
/// consult the parent's live track). Undoable setters create
/// `NodeParamSetKeyframe*Command`-equivalent commands whose closures
/// write through to the same box (the command holds a reference for its
/// whole lifetime).
pub mod keyframe {
use super::*;
use crate::keyframe::{Interpolation, Keyframe};
use oakcore_rs::Rational;
use std::sync::Mutex;
/// Keyframe handle payload (`// CPP-PARITY: src/node/src/keyframe.h`
/// `NodeKeyframe`). Behind a `Mutex` so undo-command closures can
/// write through to the same box the handle reads.
pub struct KfBox {
/// Parent project (Some when the keyframe was created with a
/// parent node).
pub project: Option<ProjectArc>,
/// Parent node id.
pub node: Option<NodeId>,
/// The input the keyframe belongs to (C++ `input_`).
pub input: String,
/// Array element (C++ `element_`).
pub element: i32,
/// Component track (C++ `track_`).
pub track: i32,
/// The keyframe data (time/value/interpolation/bezier).
pub data: Keyframe,
}
/// RAII reference to a keyframe box held by an undo command's
/// closures: keeps the box alive for the command's lifetime and
/// releases it exactly once when the command is destroyed (dropping
/// the closure drops this guard).
struct KfKeep(CHandle);
impl Drop for KfKeep {
fn drop(&mut self) {
if let Some(f) = self.0.release {
unsafe { f(self.0.ctx) };
}
self.0.ctx = std::ptr::null_mut();
}
}
/// An addref'd copy of `h` for command closures.
fn kf_keep(h: &CHandle) -> KfKeep {
let mut c = h.clone();
if let Some(f) = c.addref {
unsafe { f(c.ctx) };
}
KfKeep(c)
}
/// Read the boxed keyframe.
unsafe fn kf_get(h: &CHandle) -> Option<&Mutex<KfBox>> {
unsafe { crate::handle::get::<Mutex<KfBox>>(h) }
}
/// Lock the boxed keyframe.
unsafe fn kf_lock(h: &CHandle) -> Option<std::sync::MutexGuard<'_, KfBox>> {
let m = unsafe { kf_get(h)? };
Some(m.lock().unwrap_or_else(|e| e.into_inner()))
}
/// Map an `oaknode_keyframe_type` to [`Interpolation`].
fn interp_from_c(t: c_int) -> Result<Interpolation> {
match t {
0 => Ok(Interpolation::Linear),
1 => Ok(Interpolation::Hold),
2 => Ok(Interpolation::Bezier),
_ => Err(Error::Invalid),
}
}
/// [`Interpolation`] -> `oaknode_keyframe_type`.
fn interp_to_c(i: Interpolation) -> c_int {
match i {
Interpolation::Linear => 0,
Interpolation::Hold => 1,
Interpolation::Bezier => 2,
}
}
/// `NodeKeyframe::set_type` with the bezier default-handle side
/// effect. Standalone handles never have a `previous_`/`next_`, so
/// the C++ null-branches (in `(-1, 0)` / out `(1, 0)`) always apply
/// (`// CPP-PARITY: src/node/src/keyframe.cpp:120`).
fn set_type_adjusting(k: &mut KfBox, new: Interpolation) {
if k.data.interpolation == new {
return;
}
k.data.interpolation = new;
if new == Interpolation::Bezier {
if k.data.bezier_in == (0.0, 0.0) {
k.data.bezier_in = (-1.0, 0.0);
}
if k.data.bezier_out == (0.0, 0.0) {
k.data.bezier_out = (1.0, 0.0);
}
}
}
/// Create a command whose redo/undo write through to the box behind
/// `h` (`NodeParamSetKeyframe*Command` semantics: redo applies the
/// new value, undo restores the old; `// CPP-PARITY:
/// src/node/src/nodeundo.cpp:417`).
fn kf_command(
h: &CHandle,
apply: impl Fn(&mut KfBox) + Send + 'static,
revert: impl Fn(&mut KfBox) + Send + 'static,
) -> Result<CHandle> {
let keep = kf_keep(h);
let kh1 = keep.0.clone(); // bitwise copies; `keep` owns the reference
let kh2 = kh1.clone();
let cmd = undo_command(
move || {
// `keep` (the box reference) lives as long as the command.
let _k = &keep;
if let Some(mut g) = unsafe { kf_lock(&kh1) } {
apply(&mut g);
}
},
move || {
if let Some(mut g) = unsafe { kf_lock(&kh2) } {
revert(&mut g);
}
},
)?;
Ok(cmd)
}
/// `oaknode_keyframe_create`: standalone keyframe handle with count 1;
/// empty handle on invalid arguments (bad type, string value,
/// non-UTF-8 input id, unresolvable parent).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_create(
time_num: i64,
time_den: i64,
value: *const OakNodeValue,
type_: c_int,
track: c_int,
element: c_int,
input_id: *const c_char,
parent_or_null: CHandle,
) -> CHandle {
crate::handle::guard_handle(|| {
let interp = interp_from_c(type_)?;
let input = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?.to_string();
let val = if value.is_null() {
NodeValue::None
} else {
unsafe { *value }.to_node_value(ValueType::None)?
};
let (project, node) = if parent_or_null.ctx.is_null() {
(None, None)
} else {
let p = unsafe { node_ref(&parent_or_null)? };
(Some(p.project.clone()), Some(p.id))
};
let data = Keyframe {
time: Rational::new(time_num, time_den),
value: val,
interpolation: interp,
bezier_in: (0.0, 0.0),
bezier_out: (0.0, 0.0),
};
Ok(crate::handle::make_owned(Mutex::new(KfBox {
project,
node,
input,
element,
track,
data,
})))
})
}
/// `oaknode_keyframe_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_free(keyframe: *mut CHandle) {
guard_void(|| {
if keyframe.is_null() || unsafe { (*keyframe).ctx.is_null() } {
return;
}
let h = unsafe { (*keyframe).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*keyframe).ctx = std::ptr::null_mut() };
})
}
/// `oaknode_keyframe_get_time`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_time(
keyframe: CHandle,
out_num: *mut i64,
out_den: *mut i64,
) -> c_int {
guard(|| {
if out_num.is_null() || out_den.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
unsafe {
*out_num = k.data.time.numerator();
*out_den = k.data.time.denominator();
}
Ok(())
})
}
/// `oaknode_keyframe_set_time` (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_time(
keyframe: CHandle,
time_num: i64,
time_den: i64,
) -> c_int {
guard(|| {
let mut k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
k.data.time = Rational::new(time_num, time_den);
Ok(())
})
}
/// `oaknode_keyframe_set_time_undoable`
/// (`olive::NodeParamSetKeyframeTimeCommand`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_time_undoable(
keyframe: CHandle,
time_num: i64,
time_den: i64,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let new = Rational::new(time_num, time_den);
let old = unsafe { kf_lock(&keyframe) }
.ok_or(Error::Invalid)?
.data
.time;
let cmd = kf_command(
&keyframe,
move |k| k.data.time = new,
move |k| k.data.time = old,
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_keyframe_get_value`: map the stored value into the POD;
/// types without a POD representation fail with `OAKNODE_E_FAILED`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_value(
keyframe: CHandle,
out: *mut OakNodeValue,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
let declared = k.data.value.value_type();
let pod = OakNodeValue::from_node_value(declared, &k.data.value).map_err(|_| {
Error::Failed("keyframe value has no POD representation".to_string())
})?;
unsafe { *out = pod };
Ok(())
})
}
/// `oaknode_keyframe_set_value` (live); `OAKNODE_VALUE_STRING` is
/// rejected (use `oaknode_keyframe_set_value_string`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_value(
keyframe: CHandle,
v: *const OakNodeValue,
) -> c_int {
guard(|| {
if v.is_null() {
return Err(Error::Invalid);
}
let mut k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
let declared = k.data.value.value_type();
let value = unsafe { *v }.to_node_value(declared)?;
k.data.value = value;
Ok(())
})
}
/// `oaknode_keyframe_set_value_undoable`
/// (`olive::NodeParamSetKeyframeValueCommand`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_value_undoable(
keyframe: CHandle,
v: *const OakNodeValue,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if v.is_null() || out_command.is_null() {
return Err(Error::Invalid);
}
let mut k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
let declared = k.data.value.value_type();
let new = unsafe { *v }.to_node_value(declared)?;
let old = k.data.value.clone();
drop(k);
let cmd = kf_command(
&keyframe,
move |k| k.data.value = new.clone(),
move |k| k.data.value = old.clone(),
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_keyframe_get_value_string` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_value_string(
keyframe: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let k = match unsafe { kf_lock(&keyframe) } {
Some(k) => k,
None => return crate::error::OAKNODE_E_INVALID,
};
let declared = k.data.value.value_type();
let s = crate::serializer::value_to_string(declared, &k.data.value, false);
copy_string_out(&s, buf, buf_size)
}
/// `oaknode_keyframe_set_value_string` (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_value_string(
keyframe: CHandle,
value: *const c_char,
) -> c_int {
guard(|| {
let s = unsafe { cstr(value) }.ok_or(Error::Invalid)?;
let mut k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
k.data.value = NodeValue::Text(s.to_string());
Ok(())
})
}
/// `oaknode_keyframe_set_value_string_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_value_string_undoable(
keyframe: CHandle,
value: *const c_char,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let s = unsafe { cstr(value) }.ok_or(Error::Invalid)?.to_string();
let old = unsafe { kf_lock(&keyframe) }
.ok_or(Error::Invalid)?
.data
.value
.clone();
let cmd = kf_command(
&keyframe,
move |k| k.data.value = NodeValue::Text(s.clone()),
move |k| k.data.value = old.clone(),
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_keyframe_get_type`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_type(
keyframe: CHandle,
out_type: *mut c_int,
) -> c_int {
guard(|| {
if out_type.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
unsafe { *out_type = interp_to_c(k.data.interpolation) };
Ok(())
})
}
/// `oaknode_keyframe_set_type` (live, with bezier-handle defaults).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_type(keyframe: CHandle, type_: c_int) -> c_int {
guard(|| {
let interp = interp_from_c(type_)?;
let mut k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
set_type_adjusting(&mut k, interp);
Ok(())
})
}
/// `oaknode_keyframe_set_type_undoable` (same semantics as live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_type_undoable(
keyframe: CHandle,
type_: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let interp = interp_from_c(type_)?;
let old = unsafe { kf_lock(&keyframe) }
.ok_or(Error::Invalid)?
.data
.interpolation;
let cmd = kf_command(
&keyframe,
move |k| set_type_adjusting(k, interp),
move |k| k.data.interpolation = old,
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_keyframe_get_bezier_control`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_bezier_control(
keyframe: CHandle,
handle: c_int,
out_x: *mut f64,
out_y: *mut f64,
) -> c_int {
guard(|| {
if out_x.is_null() || out_y.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
let (x, y) = match handle {
0 => k.data.bezier_in,
1 => k.data.bezier_out,
_ => return Err(Error::Invalid),
};
unsafe {
*out_x = x;
*out_y = y;
}
Ok(())
})
}
/// `oaknode_keyframe_set_bezier_control` (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_bezier_control(
keyframe: CHandle,
handle: c_int,
x: f64,
y: f64,
) -> c_int {
guard(|| {
let mut k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
match handle {
0 => k.data.bezier_in = (x, y),
1 => k.data.bezier_out = (x, y),
_ => return Err(Error::Invalid),
}
Ok(())
})
}
/// `oaknode_keyframe_set_bezier_control_undoable`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_set_bezier_control_undoable(
keyframe: CHandle,
handle: c_int,
x: f64,
y: f64,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() || (handle != 0 && handle != 1) {
return Err(Error::Invalid);
}
let old = {
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
match handle {
0 => k.data.bezier_in,
_ => k.data.bezier_out,
}
};
let cmd = kf_command(
&keyframe,
move |k| {
if handle == 0 {
k.data.bezier_in = (x, y);
} else {
k.data.bezier_out = (x, y);
}
},
move |k| {
if handle == 0 {
k.data.bezier_in = old;
} else {
k.data.bezier_out = old;
}
},
)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_keyframe_get_track`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_track(
keyframe: CHandle,
out_track: *mut c_int,
) -> c_int {
guard(|| {
if out_track.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
unsafe { *out_track = k.track };
Ok(())
})
}
/// `oaknode_keyframe_get_element`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_element(
keyframe: CHandle,
out_element: *mut c_int,
) -> c_int {
guard(|| {
if out_element.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
unsafe { *out_element = k.element };
Ok(())
})
}
/// `oaknode_keyframe_get_input` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_input(
keyframe: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let k = match unsafe { kf_lock(&keyframe) } {
Some(k) => k,
None => return crate::error::OAKNODE_E_INVALID,
};
copy_string_out(&k.input, buf, buf_size)
}
/// `oaknode_keyframe_get_parent`: borrowed node handle, empty when
/// orphaned; `OAKNODE_OK` either way.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_parent(
keyframe: CHandle,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
match (&k.project, &k.node) {
(Some(p), Some(id)) => unsafe { *out_node = make_node_borrowed(p.clone(), *id) },
_ => unsafe { *out_node = CHandle::null() },
}
Ok(())
})
}
/// `oaknode_keyframe_get_valid_bezier_control`: for standalone
/// handles there is no neighbouring keyframe, so the raw control
/// point is already valid (`NodeKeyframe::valid_bezier_control_*`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_get_valid_bezier_control(
keyframe: CHandle,
handle: c_int,
out_x: *mut f64,
out_y: *mut f64,
) -> c_int {
unsafe { oaknode_keyframe_get_bezier_control(keyframe, handle, out_x, out_y) }
}
/// `oaknode_keyframe_opposing_bezier_type`: `IN_HANDLE (0) <->
/// OUT_HANDLE (1)`; `OAKNODE_E_INVALID` for anything else.
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_opposing_bezier_type(type_: c_int) -> c_int {
match type_ {
0 => 1,
1 => 0,
_ => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_keyframe_compute_paste_value`: take the target node's
/// value at the keyframe's time, replace the keyframe's own track
/// with the keyframe's value, and combine the per-track components
/// into a single normal value (the facade paste path).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_compute_paste_value(
target_node: CHandle,
keyframe: CHandle,
out: *mut crate::value::OakNodeValue,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let target = unsafe { node_ref(&target_node)? };
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
let input = k.input.clone();
let element = k.element;
let track = k.track;
let time = k.data.time;
let kf_value = k.data.value.clone();
drop(k);
let declared = with_graph_read(&target.project, |g| {
g.get(target.id)
.and_then(|e| e.core.input_data_type(&input))
})
.ok_or(Error::NotFound)?;
if declared.is_string() {
return Err(Error::Failed(
"input type has no POD representation".to_string(),
));
}
let base = with_graph_read(&target.project, |g| {
g.get(target.id)
.map(|e| e.core.value_at_time(&input, element, time))
.unwrap_or(NodeValue::None)
});
let mut tracks = base.split_into_tracks(declared);
if let Some(slot) = tracks.get_mut(track as usize) {
*slot = kf_value;
}
let combined = NodeValue::combine_tracks(&tracks, declared);
let pod = OakNodeValue::from_node_value(declared, &combined)
.map_err(|_| Error::Failed("input type has no POD representation".to_string()))?;
unsafe { *out = pod };
Ok(())
})
}
/// `oaknode_keyframe_has_sibling_at_time`: 1 when the parent's live
/// track holds a keyframe at the exact rational time; orphaned
/// keyframes have no siblings (`*out_value` = 0, `OAKNODE_OK`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_keyframe_has_sibling_at_time(
keyframe: CHandle,
time_num: i64,
time_den: i64,
out_value: *mut c_int,
) -> c_int {
guard(|| {
if out_value.is_null() {
return Err(Error::Invalid);
}
let time = Rational::new(time_num, time_den);
let k = unsafe { kf_lock(&keyframe) }.ok_or(Error::Invalid)?;
let found = match (&k.project, &k.node) {
(Some(p), Some(id)) => {
let input = k.input.clone();
let element = k.element;
with_graph_read(p, |g| {
g.get(*id)
.and_then(|e| e.core.keyframe_track(&input, element))
.map(|t| t.keys().iter().any(|key| key.time == time))
.unwrap_or(false)
})
}
_ => false,
};
unsafe { *out_value = found as c_int };
Ok(())
})
}
}
// =====================================================================
// include/node/dragger.h
// =====================================================================
/// `include/node/dragger.h` exports (complete inventory):
/// oaknode_dragger_create / start / drag / end / is_started / free.
///
/// The dragger wraps an input drag (`olive::NodeInputDragger`,
/// `// CPP-PARITY: src/node/c_api/dragger.cpp` / `src/node/src/inputdragger.cpp`)
/// over the whole-value keyframe model: `start` caches the per-component
/// value at the drag time; `drag` clamps against the input's `min`/`max`
/// properties and writes through (standard value, or the keyframe at the
/// drag time); `end` builds a multi undo command with the C++
/// `NodeParamSet*Command` equivalents.
///
/// The C++ `is_keyframing()` per-input mode has no Rust counterpart yet,
/// so the keyframing branch is selected by whether the (input, element)
/// track already holds keyframes (documented divergence).
pub mod dragger {
use super::*;
use crate::keyframe::{Interpolation, Keyframe};
use crate::value::oak;
use oakcore_rs::Rational;
use std::sync::Mutex;
/// Dragger handle payload (`// CPP-PARITY: dragger.cpp:46`
/// `DraggerImpl` + `NodeInputDragger` state).
pub struct DraggerBox {
/// The project the dragged node lives in.
pub project: ProjectArc,
/// The dragged node.
pub node: NodeId,
/// The dragged input.
pub input: String,
/// Array element.
pub element: i32,
/// Whether a drag is in progress.
pub started: bool,
/// Component track of the drag (established by `start`).
pub track: i32,
/// Drag time.
pub time: Rational,
/// Whether the drag edits keyframes (the track had keys at start).
pub keyframing: bool,
/// Whether `start` created a key (undo of `end` removes it).
pub created_key: bool,
/// Per-component value to restore on undo (pre-drag standard
/// value or pre-drag key value).
pub undo_value: NodeValue,
}
/// Lock the boxed dragger.
unsafe fn dg_lock(h: &CHandle) -> Option<MutexGuard<'_, DraggerBox>> {
let m = unsafe { crate::handle::get::<Mutex<DraggerBox>>(h)? };
Some(m.lock().unwrap_or_else(|e| e.into_inner()))
}
/// The declared value type of the dragged input.
fn declared_type(project: &ProjectArc, node: NodeId, input: &str) -> Option<ValueType> {
with_graph_read(project, |g| {
g.get(node).and_then(|e| e.core.input_data_type(input))
})
}
/// The whole value with `track`'s component replaced by `component`
/// (C++ `set_split_standard_value_on_track` /
/// `get_split_value_at_time_on_track` combine half).
fn with_component(
declared: ValueType,
whole: NodeValue,
track: usize,
component: &NodeValue,
) -> NodeValue {
let mut tracks = whole.split_into_tracks(declared);
if let Some(slot) = tracks.get_mut(track) {
*slot = component.clone();
}
NodeValue::combine_tracks(&tracks, declared)
}
/// `// CPP-PARITY: src/node/c_api/valueconvert.h:262`
/// `component_from_value(value, declared, 0)` — the POD -> the scalar
/// component variant the dragger drags.
fn component_from_pod(v: &OakNodeValue, declared: ValueType) -> Result<NodeValue> {
match declared {
ValueType::Int | ValueType::Combo => {
if v.kind != oak::INT && v.kind != oak::COMBO {
return Err(Error::Invalid);
}
Ok(NodeValue::Int(v.num))
}
ValueType::Float => {
if v.kind != oak::FLOAT {
return Err(Error::Invalid);
}
Ok(NodeValue::Float(v.f[0]))
}
ValueType::Boolean => {
if v.kind != oak::BOOL {
return Err(Error::Invalid);
}
Ok(NodeValue::Boolean(v.num != 0))
}
ValueType::Rational => {
if v.kind != oak::RATIONAL {
return Err(Error::Invalid);
}
Ok(NodeValue::Rational(Rational::new(v.num, v.den)))
}
ValueType::Color | ValueType::Vec2 | ValueType::Vec3 | ValueType::Vec4 => {
if v.kind != declared.to_oak() {
return Err(Error::Invalid);
}
Ok(NodeValue::Float(v.f[0]))
}
_ => Err(Error::Invalid),
}
}
/// Clamp a component against the input's `min`/`max` properties
/// (`// CPP-PARITY: inputdragger.cpp:106`).
fn clamp_property(core: &NodeCore, input: &str, value: &NodeValue) -> NodeValue {
let mut value = value.clone();
if let Some(prop) = core.get_input(input).and_then(|i| {
i.properties
.iter()
.find(|(k, _)| k == "min")
.map(|(_, v)| v)
}) {
let min = prop.to_double();
if value.to_double() < min {
value = NodeValue::Float(min);
}
}
if let Some(prop) = core.get_input(input).and_then(|i| {
i.properties
.iter()
.find(|(k, _)| k == "max")
.map(|(_, v)| v)
}) {
let max = prop.to_double();
if value.to_double() > max {
value = NodeValue::Float(max);
}
}
value
}
/// Build a vtable undo command from redo/undo closures.
fn make_child(
_keep: ProjectArc,
redo: impl FnMut() + Send + 'static,
undo: impl FnMut() + Send + 'static,
) -> Option<CHandle> {
crate::bridge::undo::command_from_closures(redo, undo)
}
/// `oaknode_dragger_create`: empty handle when the node/input is
/// invalid; `track` is ignored (start establishes the drag track,
/// `// CPP-PARITY: dragger.cpp:67`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_dragger_create(
node: CHandle,
input_id: *const c_char,
element: c_int,
_track: c_int,
) -> CHandle {
crate::handle::guard_handle(|| {
let n = unsafe { node_ref(&node)? };
let input = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?.to_string();
let has = with_graph_read(&n.project, |g| {
g.get(n.id)
.map(|e| e.core.has_input(&input))
.unwrap_or(false)
});
if !has {
return Err(Error::Invalid);
}
Ok(crate::handle::make_owned(Mutex::new(DraggerBox {
project: n.project.clone(),
node: n.id,
input,
element,
started: false,
track: 0,
time: Rational::NULL,
keyframing: false,
created_key: false,
undo_value: NodeValue::None,
})))
})
}
/// `oaknode_dragger_start`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_dragger_start(
dragger: CHandle,
time_num: i64,
time_den: i64,
track: c_int,
insert_on_all_tracks: c_int,
) -> c_int {
guard(|| {
let _ = insert_on_all_tracks; // whole-value keys cover all tracks
if track < 0 {
return Err(Error::Invalid);
}
let mut d = unsafe { dg_lock(&dragger) }.ok_or(Error::Invalid)?;
if d.started {
return Err(Error::State);
}
let time = Rational::new(time_num, time_den);
// Snapshot the drag target, then release the box guard so the
// project lock below does not alias it.
let project = d.project.clone();
let node = d.node;
let input = d.input.clone();
let element = d.element;
drop(d);
let declared = declared_type(&project, node, &input).ok_or(Error::NotFound)?;
let (undo_value, created_key, keyframing) = {
let mut guard = lock(&project);
let entry = guard.graph.get_mut(node).ok_or(Error::NotFound)?;
let core = &mut entry.core;
// The track's current value at the drag time (whole
// value); the dragged component and the undo snapshot
// derive from it.
let whole = core.value_at_time(&input, element, time);
let keyframing = core
.keyframe_track(&input, element)
.map(|t| !t.keys().is_empty())
.unwrap_or(false);
let start_component = whole
.split_into_tracks(declared)
.get(track as usize)
.cloned()
.unwrap_or(NodeValue::None);
if keyframing {
// Keyframe branch: ensure a key exists at the drag
// time (whole-value model: one key per time).
let has_key = core
.keyframe_track(&input, element)
.map(|t| t.keys().iter().any(|k| k.time == time))
.unwrap_or(false);
if !has_key {
let created = with_component(
declared,
whole.clone(),
track as usize,
&start_component,
);
core.keyframe_track_mut(&input, element).set_key(Keyframe {
time,
value: created.clone(),
interpolation: Interpolation::Linear,
bezier_in: (0.0, 0.0),
bezier_out: (0.0, 0.0),
});
(created, true, true)
} else {
let existing = core
.keyframe_track(&input, element)
.and_then(|t| t.keys().iter().find(|k| k.time == time))
.map(|k| k.value.clone())
.unwrap_or(whole);
(existing, false, true)
}
} else {
// Standard-value branch: snapshot the pre-drag value.
(whole, false, false)
}
};
let mut d = unsafe { dg_lock(&dragger) }.ok_or(Error::Invalid)?;
d.started = true;
d.keyframing = keyframing;
d.track = track;
d.time = time;
d.created_key = created_key;
d.undo_value = undo_value;
Ok(())
})
}
/// `oaknode_dragger_drag`: clamp the component against `min`/`max`
/// and write it through (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_dragger_drag(
dragger: CHandle,
value: *const crate::value::OakNodeValue,
) -> c_int {
guard(|| {
if value.is_null() {
return Err(Error::Invalid);
}
let pod = unsafe { *value };
let mut d = unsafe { dg_lock(&dragger) }.ok_or(Error::Invalid)?;
if !d.started {
return Err(Error::State);
}
let declared = declared_type(&d.project, d.node, &d.input).ok_or(Error::NotFound)?;
let component = component_from_pod(&pod, declared)?;
let mut guard = lock(&d.project);
let entry = guard.graph.get_mut(d.node).ok_or(Error::NotFound)?;
let component = clamp_property(&entry.core, &d.input, &component);
if d.keyframing {
let time = d.time;
let whole = entry
.core
.keyframe_track(&d.input, d.element)
.and_then(|t| t.keys().iter().find(|k| k.time == time))
.map(|k| k.value.clone())
.unwrap_or(entry.core.value_at_time(&d.input, d.element, time));
let new_whole = with_component(declared, whole, d.track as usize, &component);
entry
.core
.keyframe_track_mut(&d.input, d.element)
.set_key_value(time, new_whole);
} else {
let whole = entry.core.standard_value(&d.input, d.element);
let new_whole = with_component(declared, whole, d.track as usize, &component);
entry
.core
.set_standard_value(&d.input, d.element, new_whole);
}
Ok(())
})
}
/// `oaknode_dragger_end`: build a multi undo command covering the
/// drag (insert-keyframe + set-value, or set-standard-value).
#[no_mangle]
pub unsafe extern "C" fn oaknode_dragger_end(
dragger: CHandle,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let mut d = unsafe { dg_lock(&dragger) }.ok_or(Error::Invalid)?;
if !d.started {
return Err(Error::State);
}
let project = d.project.clone();
let node = d.node;
let input = d.input.clone();
let element = d.element;
let track = d.track;
let time = d.time;
let keyframing = d.keyframing;
let created_key = d.created_key;
let undo_value = d.undo_value.clone();
// The post-drag whole value (what redo re-applies): the key
// value at the drag time, or the current standard value.
let redo_value = {
let mut guard = lock(&project);
if keyframing {
guard
.graph
.get(node)
.and_then(|e| {
e.core.keyframe_track(&input, element).and_then(|t| {
t.keys()
.iter()
.find(|k| k.time == time)
.map(|k| k.value.clone())
})
})
.unwrap_or(NodeValue::None)
} else {
guard
.graph
.get(node)
.map(|e| e.core.standard_value(&input, element))
.unwrap_or(NodeValue::None)
}
};
let multi = crate::bridge::undo::command_init_multi().ok_or(Error::NoMem)?;
if keyframing {
if created_key {
// Insert-keyframe equivalent: redo re-adds the key,
// undo removes it (the value command below restores
// the value; `// CPP-PARITY: nodeundo.cpp:432`).
let redo_key = redo_value.clone();
let project_redo = project.clone();
let project_undo = project.clone();
let input_redo = input.clone();
let input_undo = input.clone();
if let Some(child) = make_child(
project.clone(),
move || {
let mut guard = lock(&project_redo);
if let Some(e) = guard.graph.get_mut(node) {
e.core
.keyframe_track_mut(&input_redo, element)
.set_key(Keyframe {
time,
value: redo_key.clone(),
interpolation: Interpolation::Linear,
bezier_in: (0.0, 0.0),
bezier_out: (0.0, 0.0),
});
}
},
move || {
let mut guard = lock(&project_undo);
if let Some(e) = guard.graph.get_mut(node) {
e.core
.keyframe_track_mut(&input_undo, element)
.remove_key(time);
}
},
) {
let _ = crate::bridge::undo::command_multi_add_child(multi.clone(), child);
}
}
// Set-keyframe-value equivalent (always added; the
// C++ sends the value-changed signal through it).
let project_redo = project.clone();
let input_redo = input.clone();
let input_undo = input.clone();
if let Some(child) = make_child(
project.clone(),
move || {
let mut guard = lock(&project_redo);
if let Some(e) = guard.graph.get_mut(node) {
e.core
.keyframe_track_mut(&input_redo, element)
.set_key_value(time, redo_value.clone());
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(node) {
e.core
.keyframe_track_mut(&input_undo, element)
.set_key_value(time, undo_value.clone());
}
},
) {
let _ = crate::bridge::undo::command_multi_add_child(multi.clone(), child);
}
} else {
// Set-standard-value equivalent
// (`// CPP-PARITY: nodeundo.cpp:500`
// `NodeParamSetStandardValueCommand`).
let project_redo = project.clone();
let input_redo = input.clone();
let input_undo = input.clone();
if let Some(child) = make_child(
project.clone(),
move || {
let mut guard = lock(&project_redo);
if let Some(e) = guard.graph.get_mut(node) {
e.core
.set_standard_value(&input_redo, element, redo_value.clone());
}
},
move || {
let mut guard = lock(&project);
if let Some(e) = guard.graph.get_mut(node) {
e.core
.set_standard_value(&input_undo, element, undo_value.clone());
}
},
) {
let _ = crate::bridge::undo::command_multi_add_child(multi.clone(), child);
}
}
// The drag is complete; the dragger resets (C++
// `NodeInputDragger::end` resets the reference).
d.started = false;
d.created_key = false;
unsafe { *out_command = multi };
Ok(())
})
}
/// `oaknode_dragger_is_started`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_dragger_is_started(
dragger: CHandle,
out_started: *mut c_int,
) -> c_int {
guard(|| {
if out_started.is_null() {
return Err(Error::Invalid);
}
let d = unsafe { dg_lock(&dragger) }.ok_or(Error::Invalid)?;
unsafe { *out_started = d.started as c_int };
Ok(())
})
}
/// `oaknode_dragger_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_dragger_free(dragger: *mut CHandle) {
guard_void(|| {
if dragger.is_null() || unsafe { (*dragger).ctx.is_null() } {
return;
}
let h = unsafe { (*dragger).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*dragger).ctx = std::ptr::null_mut() };
})
}
}
// =====================================================================
// include/node/multicam.h
// =====================================================================
/// `include/node/multicam.h` exports (complete inventory): the four
/// input-id getters, source count, grid math (rows/columns,
/// index<->row/col), and the current source.
///
/// The grid math and string getters are pure; the node-backed helpers
/// follow the C++ oracle (`// CPP-PARITY: src/node/c_api/multicam.cpp`,
/// `src/node/src/input/multicam/multicamnode.cpp`). The `MultiCamNode`
/// behavior itself stays a Phase-3 item; the "is this a multicam node"
/// check mirrors the C++ `dynamic_cast<MultiCamNode *>` via the type id,
/// and the source count uses the `sources_in` array size (the
/// C++ sequence-track-list path is behavior-bound and deferred).
pub mod multicam {
use super::*;
const MULTICAM_TYPE_ID: &str = "org.olivevideoeditor.Olive.multicam";
const CURRENT_INPUT: &str = "current_in";
const SOURCES_INPUT: &str = "sources_in";
/// `oaknode_multicam_input_current` — static string.
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_input_current() -> *const c_char {
b"current_in\0".as_ptr() as *const c_char
}
/// `oaknode_multicam_input_sources` — static string.
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_input_sources() -> *const c_char {
b"sources_in\0".as_ptr() as *const c_char
}
/// `oaknode_multicam_input_sequence` — static string.
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_input_sequence() -> *const c_char {
b"sequence_in\0".as_ptr() as *const c_char
}
/// `oaknode_multicam_input_sequence_type` — static string.
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_input_sequence_type() -> *const c_char {
b"sequence_type_in\0".as_ptr() as *const c_char
}
/// The multicam behavior of a node (C++ `dynamic_cast<MultiCamNode *>`).
fn multicam_of(entry: &crate::graph::NodeEntry) -> bool {
entry.behavior.type_id() == MULTICAM_TYPE_ID
}
/// `oaknode_multicam_get_source_count`: the `sources_in` array size
/// (`// CPP-PARITY: multicamnode.cpp:163`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_get_source_count(
node: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let count = with_graph_read(&n.project, |g| {
g.get(n.id)
.filter(|e| multicam_of(e))
.map(|e| e.core.input_array_size(SOURCES_INPUT) as c_int)
})
.ok_or(Error::Invalid)?;
unsafe { *out_count = count };
Ok(())
})
}
/// `oaknode_multicam_get_rows_and_columns`: the square-ish grid that
/// covers `source_count` cells (`// CPP-PARITY:
/// multicamnode.cpp:190`; rows grows while it trails columns).
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_get_rows_and_columns(
source_count: c_int,
rows: *mut c_int,
cols: *mut c_int,
) -> c_int {
if source_count < 0 || rows.is_null() || cols.is_null() {
return crate::error::OAKNODE_E_INVALID;
}
let mut r: c_int = 1;
let mut c: c_int = 1;
while r * c < source_count {
if r < c {
r += 1;
} else {
c += 1;
}
}
unsafe {
*rows = r;
*cols = c;
}
crate::error::OAKNODE_OK
}
/// `oaknode_multicam_index_to_row_cols`: column-major layout
/// (`// CPP-PARITY: multicamnode.cpp:93`; `total_rows` unused).
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_index_to_row_cols(
index: c_int,
rows: c_int,
cols: c_int,
out_row: *mut c_int,
out_col: *mut c_int,
) -> c_int {
if index < 0 || rows < 1 || cols < 1 || out_row.is_null() || out_col.is_null() {
return crate::error::OAKNODE_E_INVALID;
}
unsafe {
*out_col = index % cols;
*out_row = index / cols;
}
crate::error::OAKNODE_OK
}
/// `oaknode_multicam_rows_cols_to_index`: `row * cols + col`
/// (`// CPP-PARITY: multicamnode.h:76`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_rows_cols_to_index(
row: c_int,
col: c_int,
rows: c_int,
cols: c_int,
) -> c_int {
if row < 0 || col < 0 || rows < 1 || cols < 1 || row >= rows || col >= cols {
return crate::error::OAKNODE_E_INVALID;
}
row * cols + col
}
/// `oaknode_multicam_get_current_source`: the `current_in` combo value
/// (`// CPP-PARITY: multicamnode.h:55`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_multicam_get_current_source(
node: CHandle,
out_source: *mut c_int,
) -> c_int {
guard(|| {
if out_source.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let source = with_graph_read(&n.project, |g| {
g.get(n.id)
.filter(|e| multicam_of(e))
.map(|e| e.core.standard_value(CURRENT_INPUT, -1).to_double() as c_int)
})
.ok_or(Error::Invalid)?;
unsafe { *out_source = source };
Ok(())
})
}
}
// =====================================================================
// include/node/block.h, folder.h, footage.h, group.h, colormanager.h,
// factory.h, serializer.h, traverser.h
// =====================================================================
/// Families landing with their backing modules: `block`/`folder`/
/// `footage`/`group`/`colormanager`/`factory`/`serializer`/`traverser`.
/// The factory family is required by the node tests and lands with the
/// registry work (Phase 1 continues in the crate plan; see
/// `tests/factory_smoke_test.rs`).
pub mod remaining_headers {
// Stubs are added per header when the corresponding internal module
// is implemented; keep header order and naming identical to the C
// side so bindgen/diff audits stay mechanical.
}
/// `include/node/factory.h` — node-type registry exports. The registry
/// itself (`factory.rs`) is live; the C ABI family (initialize/destroy/
/// id_count/id_at/name_from_id/create_from_id/node_at) is implemented
/// in Phase 1's node tests.
pub mod factory {
use super::*;
/// `oaknode_factory_initialize`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_initialize() -> c_int {
guard(|| {
let _ = crate::factory::Factory::global();
Ok(())
})
}
/// `oaknode_factory_destroy` — no-op (the registry is process-wide
/// and lazily built; nothing to release).
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_destroy() {}
/// `oaknode_factory_id_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_id_count(out_count: *mut c_int) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
unsafe { *out_count = crate::factory::Factory::global().entries().len() as c_int };
Ok(())
})
}
/// `oaknode_factory_id_at` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_id_at(
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let entries = crate::factory::Factory::global().entries();
if index < 0 || index as usize >= entries.len() {
return crate::error::OAKNODE_E_NOT_FOUND;
}
copy_string_out(entries[index as usize].type_id, buf, buf_size)
}
/// `oaknode_factory_name_from_id` (two-stage; unknown id yields an
/// empty string).
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_name_from_id(
type_id: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let type_id = match unsafe { cstr(type_id) } {
Some(s) => s,
None => return crate::error::OAKNODE_E_INVALID,
};
let name = crate::factory::Factory::global()
.find(type_id)
.map(|m| m.name)
.unwrap_or("");
copy_string_out(name, buf, buf_size)
}
/// `oaknode_factory_create_from_id` (owned orphan node).
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_create_from_id(type_id: *const c_char) -> CHandle {
let type_id = match unsafe { cstr(type_id) } {
Some(s) => s,
None => return CHandle::null(),
};
let create = match crate::factory::Factory::global().find(type_id) {
Some(meta) => meta.create,
None => return CHandle::null(),
};
let (core, behavior) = create();
let scratch = Project::new();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
make_node_owned(scratch, id)
}
/// `oaknode_factory_node_at` (borrowed prototype handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_factory_node_at(
index: c_int,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let entries = crate::factory::Factory::global().entries();
if index < 0 || index as usize >= entries.len() {
return Err(Error::NotFound);
}
let (core, behavior) = (entries[index as usize].create)();
let scratch = Project::new();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
unsafe { *out_node = make_node_borrowed(scratch, id) };
Ok(())
})
}
}
// =====================================================================
// Shared timeline-family helpers
// =====================================================================
/// Downcast a graph entry's behavior to [`crate::block::BlockCore`].
fn block_core<'a>(entry: &'a crate::graph::NodeEntry) -> Option<&'a crate::block::BlockCore> {
entry.behavior.as_any().and_then(|a| {
if let Some(b) = a.downcast_ref::<crate::block::ClipBlockBehavior>() {
Some(&b.core)
} else if let Some(b) = a.downcast_ref::<crate::block::GapBlockBehavior>() {
Some(&b.core)
} else if let Some(b) = a.downcast_ref::<crate::block::TransitionBlockBehavior>() {
Some(&b.core)
} else {
None
}
})
}
fn block_core_mut<'a>(
entry: &'a mut crate::graph::NodeEntry,
) -> Option<&'a mut crate::block::BlockCore> {
// The concrete block behaviors all carry `core` at the same offset
// behind their `as_any_mut`; downcast through a raw pointer to avoid
// the borrow checker's double-mutable-borrow on `&mut dyn Any`.
let any = entry.behavior.as_any_mut()?;
let raw: *mut dyn std::any::Any = any;
unsafe {
if let Some(b) = (&mut *raw).downcast_mut::<crate::block::ClipBlockBehavior>() {
return Some(&mut b.core);
}
if let Some(b) = (&mut *raw).downcast_mut::<crate::block::GapBlockBehavior>() {
return Some(&mut b.core);
}
if let Some(b) = (&mut *raw).downcast_mut::<crate::block::TransitionBlockBehavior>() {
return Some(&mut b.core);
}
}
None
}
/// Graph-backed block-range accessor for the track queries.
struct GraphBlockRange<'a> {
graph: &'a crate::graph::Graph,
}
impl crate::track::BlockRange for GraphBlockRange<'_> {
fn in_(&self, block: NodeId) -> oakcore_rs::Rational {
self.graph
.get(block)
.and_then(block_core)
.map(|c| c.in_())
.unwrap_or_else(|| oakcore_rs::Rational::new(0, 1))
}
fn out(&self, block: NodeId) -> oakcore_rs::Rational {
self.graph
.get(block)
.and_then(block_core)
.map(|c| c.out())
.unwrap_or_else(|| oakcore_rs::Rational::new(0, 1))
}
}
/// Graph-backed track-range accessor for the list queries.
struct GraphTrackRange<'a> {
graph: &'a crate::graph::Graph,
}
impl crate::track::TrackRange for GraphTrackRange<'_> {
fn length(&self, track: NodeId) -> oakcore_rs::Rational {
match self.graph.get(track) {
Some(entry) => entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<crate::track::TrackBehavior>())
.map(|t| t.length(&GraphBlockRange { graph: self.graph }))
.unwrap_or_else(|| oakcore_rs::Rational::new(0, 1)),
None => oakcore_rs::Rational::new(0, 1),
}
}
}
/// Move a single node from one project's graph into another's,
/// preserving identity when the slot is free. Returns the new id.
fn move_node_between(
src_project: &ProjectArc,
src_id: NodeId,
dst_project: &ProjectArc,
) -> Result<NodeId> {
let entry = {
let mut src_guard = lock(src_project);
src_guard.graph.take_node(src_id).ok_or(Error::NotFound)?
};
let mut dst_guard = lock(dst_project);
Ok(dst_guard.graph.add_entry(entry, src_id))
}
// =====================================================================
// include/node/folder.h
// =====================================================================
/// `include/node/folder.h` exports: create / child_count / child_at /
/// add_child / as_node / command_create_folder_add_child /
/// remove_child / move_children / has_child_recursive / index_of_child /
/// parent_of. The add-child command needs the undo bridge (implemented
/// via [`crate::bridge::undo`]).
pub mod folder {
use super::*;
use crate::folder::FolderBehavior;
/// Borrowed folder behavior of a node handle.
fn folder_ref(n: &NodeRef) -> Result<crate::folder::FolderBehavior> {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<FolderBehavior>())
.map(|f| FolderBehavior {
name: f.name.clone(),
children: f.children.clone(),
})
.ok_or(Error::State)
}
/// `oaknode_folder_create`: create a folder node in `project`'s graph
/// (borrowed handle; not attached under any parent).
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_create(project: CHandle) -> CHandle {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return CHandle::null(),
};
let (core, behavior) = crate::folder::create("Folder");
let id = {
let mut guard = lock(&p);
guard.graph.add_node(core, behavior)
};
make_node_borrowed(p, id)
}
/// `oaknode_folder_child_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_child_count(folder: CHandle) -> c_int {
match unsafe { node_ref(&folder) } {
Ok(n) => match folder_ref(&n) {
Ok(f) => f.children.len() as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_folder_child_at` (borrowed node handle).
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_child_at(folder: CHandle, index: c_int) -> CHandle {
let n = match unsafe { node_ref(&folder) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let f = match folder_ref(&n) {
Ok(f) => f,
Err(_) => return CHandle::null(),
};
match f.children.get(index as usize) {
Some(child) => make_node_borrowed(n.project.clone(), *child),
None => CHandle::null(),
}
}
/// `oaknode_folder_add_child` (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_add_child(folder: CHandle, child: CHandle) -> c_int {
guard(|| {
let f = unsafe { node_ref(&folder)? };
let c = unsafe { node_ref(&child)? };
if !Arc::ptr_eq(&f.project, &c.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&f.project);
// A node can only belong to one folder.
if let Some(existing) = guard.graph.get(c.id) {
if existing.core.bin_folder.is_some() && existing.core.bin_folder != Some(f.id) {
return Err(Error::State);
}
}
{
let entry = guard.graph.get_mut(f.id).ok_or(Error::NotFound)?;
let fb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FolderBehavior>())
.ok_or(Error::State)?;
fb.add_child(c.id);
}
if let Some(existing) = guard.graph.get_mut(c.id) {
existing.core.bin_folder = Some(f.id);
}
Ok(())
})
}
/// `oaknode_folder_as_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_as_node(folder: CHandle) -> CHandle {
match unsafe { node_ref(&folder) } {
Ok(n) => make_node_borrowed(n.project.clone(), n.id),
Err(_) => CHandle::null(),
}
}
/// `oaknode_command_create_folder_add_child` — undo bridge.
#[no_mangle]
pub unsafe extern "C" fn oaknode_command_create_folder_add_child(
folder: CHandle,
child: CHandle,
) -> CHandle {
match (unsafe { node_ref(&folder) }, unsafe { node_ref(&child) }) {
(Ok(f), Ok(c)) => {
let project = f.project.clone();
let project_undo = project.clone();
let fid = f.id;
let cid = c.id;
match crate::bridge::undo::command_from_closures(
move || {
let mut guard = lock(&project);
if let Some(entry) = guard.graph.get_mut(fid) {
if let Some(fb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FolderBehavior>())
{
fb.add_child(cid);
}
}
if let Some(entry) = guard.graph.get_mut(cid) {
entry.core.bin_folder = Some(fid);
}
},
move || {
let mut guard = lock(&project_undo);
if let Some(entry) = guard.graph.get_mut(fid) {
if let Some(fb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FolderBehavior>())
{
fb.remove_child(cid);
}
}
if let Some(entry) = guard.graph.get_mut(cid) {
entry.core.bin_folder = None;
}
},
) {
Some(h) => h,
None => CHandle::null(),
}
}
_ => CHandle::null(),
}
}
/// `oaknode_folder_remove_child` (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_remove_child(folder: CHandle, child: CHandle) -> c_int {
guard(|| {
let f = unsafe { node_ref(&folder)? };
let c = unsafe { node_ref(&child)? };
if !Arc::ptr_eq(&f.project, &c.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&f.project);
let entry = guard.graph.get_mut(f.id).ok_or(Error::NotFound)?;
let fb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FolderBehavior>())
.ok_or(Error::State)?;
if fb.remove_child(c.id) {
if let Some(entry) = guard.graph.get_mut(c.id) {
entry.core.bin_folder = None;
}
Ok(())
} else {
Err(Error::NotFound)
}
})
}
/// `oaknode_folder_move_children`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_move_children(
nodes: *const CHandle,
count: c_int,
dest_folder: CHandle,
) -> c_int {
guard(|| {
if nodes.is_null() || count < 0 {
return Err(Error::Invalid);
}
let d = unsafe { node_ref(&dest_folder)? };
let mut moved: Vec<NodeId> = Vec::new();
for i in 0..count as usize {
let child = unsafe { (*nodes.add(i)).clone() };
let c = unsafe { node_ref(&child)? };
if !Arc::ptr_eq(&d.project, &c.project) {
continue;
}
// Remove from the current folder (if any).
{
let mut guard = lock(&d.project);
let cur = guard.graph.get(c.id).and_then(|e| e.core.bin_folder);
if let Some(cur_f) = cur {
if let Some(entry) = guard.graph.get_mut(cur_f) {
if let Some(fb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FolderBehavior>())
{
fb.remove_child(c.id);
}
}
}
}
moved.push(c.id);
}
let mut guard = lock(&d.project);
{
let entry = guard.graph.get_mut(d.id).ok_or(Error::NotFound)?;
let fb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FolderBehavior>())
.ok_or(Error::State)?;
for id in &moved {
if !fb.children.contains(id) {
fb.add_child(*id);
}
}
}
for id in moved {
if let Some(entry) = guard.graph.get_mut(id) {
entry.core.bin_folder = Some(d.id);
}
}
Ok(())
})
}
/// `oaknode_folder_has_child_recursive`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_has_child_recursive(
folder: CHandle,
child: CHandle,
) -> c_int {
match (unsafe { node_ref(&folder) }, unsafe { node_ref(&child) }) {
(Ok(f), Ok(c)) => {
let guard = lock(&f.project);
let found = guard
.graph
.get(f.id)
.and_then(|e| e.behavior.as_any())
.and_then(|a| a.downcast_ref::<FolderBehavior>())
.map(|fb| fb.has_child_recursive(c.id, &guard.graph))
.unwrap_or(false);
found as c_int
}
_ => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_folder_index_of_child`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_index_of_child(
folder: CHandle,
child: CHandle,
) -> c_int {
match (unsafe { node_ref(&folder) }, unsafe { node_ref(&child) }) {
(Ok(f), Ok(c)) => {
let guard = lock(&f.project);
match guard
.graph
.get(f.id)
.and_then(|e| e.behavior.as_any())
.and_then(|a| a.downcast_ref::<FolderBehavior>())
.and_then(|fb| fb.index_of_child(c.id))
{
Some(i) => i as c_int,
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
_ => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_folder_parent_of`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_folder_parent_of(node: CHandle) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let guard = lock(&n.project);
match guard.graph.get(n.id).and_then(|e| e.core.bin_folder) {
Some(folder) => make_node_borrowed(n.project.clone(), folder),
None => CHandle::null(),
}
}
}
// =====================================================================
// include/node/group.h
// =====================================================================
/// `include/node/group.h` exports (complete inventory): create / cast /
/// free / add_input_passthrough (+_undoable) / remove_input_passthrough /
/// passthrough_count / passthrough_id_at / passthrough_input_at /
/// get_output_passthrough / set_output_passthrough (+_undoable) /
/// resolve_input.
///
/// Group handles are node handles whose node carries a
/// [`crate::nodes::group::NodeGroup`] behavior. The undoable variants
/// build the C++ `NodeGroupAddInputPassthrough` /
/// `NodeGroupSetOutputPassthrough` semantics through
/// [`crate::bridge::undo`] (`// CPP-PARITY: src/node/src/group/group.cpp:363`).
pub mod group {
use super::*;
use crate::nodes::group::{InnerInput, NodeGroup};
/// The group behavior of a node entry (downcast; C++
/// `dynamic_cast<NodeGroup *>`).
fn group_of(entry: &crate::graph::NodeEntry) -> Option<&NodeGroup> {
entry.behavior.as_any()?.downcast_ref::<NodeGroup>()
}
/// `oaknode_group_create`: standalone group node in a scratch project
/// (owned handle; adopt into a real project with
/// `oaknode_project_add_node`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_create() -> CHandle {
let (core, behavior) = crate::nodes::group::create();
let scratch = Project::new();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
make_node_owned(scratch, id)
}
/// `oaknode_group_cast`: borrowed group view of a node; empty handle
/// when the node is not a NodeGroup.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_cast(node: CHandle) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let guard = lock(&n.project);
match guard.graph.get(n.id) {
Some(entry) if group_of(entry).is_some() => make_node_borrowed(n.project.clone(), n.id),
_ => CHandle::null(),
}
}
/// `oaknode_group_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_free(group: *mut CHandle) {
guard_void(|| {
if group.is_null() || unsafe { (*group).ctx.is_null() } {
return;
}
let h = unsafe { (*group).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*group).ctx = std::ptr::null_mut() };
})
}
/// `oaknode_group_add_input_passthrough` (live; two-stage return of
/// the generated passthrough id).
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_add_input_passthrough(
group: CHandle,
node: CHandle,
input_id: *const c_char,
element: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let result = group_add_passthrough_impl(&group, &node, input_id, element, "");
match result {
Ok(id) => copy_string_out(&id, buf, buf_size),
Err(e) => e.code(),
}
}
/// Shared body of the live and undoable add-passthrough exports.
fn group_add_passthrough_impl(
group: &CHandle,
node: &CHandle,
input_id: *const c_char,
element: c_int,
force_id: &str,
) -> Result<String> {
let g = unsafe { node_ref(group)? };
let n = unsafe { node_ref(node)? };
if !Arc::ptr_eq(&g.project, &n.project) {
return Err(Error::Invalid);
}
let input = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?.to_string();
let inner = InnerInput {
node: n.id,
input: input.clone(),
element,
};
let mut guard = lock(&g.project);
// Resolve the inner input descriptor first (immutable borrow),
// then mutate the group (C++ reads the input off the inner node
// inside `add_input_passthrough`).
let descriptor = guard
.graph
.get(n.id)
.and_then(|e| e.core.get_input(&input))
.cloned()
.ok_or(Error::NotFound)?;
let entry = guard.graph.get_mut(g.id).ok_or(Error::NotFound)?;
let core = &mut entry.core;
let gb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
.ok_or(Error::State)?;
Ok(gb.add_input_passthrough(core, inner, force_id, &descriptor))
}
/// `oaknode_group_add_input_passthrough_undoable`
/// (`olive::NodeGroupAddInputPassthrough`). The generated id is not
/// retrievable through this call.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_add_input_passthrough_undoable(
group: CHandle,
node: CHandle,
input_id: *const c_char,
element: c_int,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let g = unsafe { node_ref(&group)? };
let n = unsafe { node_ref(&node)? };
if !Arc::ptr_eq(&g.project, &n.project) {
return Err(Error::Invalid);
}
let input = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?.to_string();
let project = g.project.clone();
let group_id = g.id;
let inner = InnerInput {
node: n.id,
input,
element,
};
// The "actually added" flag shared between redo and undo
// (C++ `NodeGroupAddInputPassthrough::actually_added_`).
let actually_added = std::sync::Arc::new(std::sync::Mutex::new(false));
let redo_added = actually_added.clone();
let redo_inner = inner.clone();
let redo_project = project.clone();
let cmd = crate::bridge::undo::command_from_closures(
move || {
let mut guard = lock(&redo_project);
if let Some(entry) = guard.graph.get(group_id) {
let gb = group_of(entry);
let contains = gb
.map(|g| g.contains_input_passthrough(&redo_inner))
.unwrap_or(false);
*lock(&redo_added) = false;
if !contains {
// The descriptor comes from the inner node,
// not the group (the group input does not
// exist yet).
let descriptor = guard
.graph
.get(redo_inner.node)
.and_then(|e| e.core.get_input(&redo_inner.input))
.cloned();
if let (Some(descriptor), Some(entry)) =
(descriptor, guard.graph.get_mut(group_id))
{
if let Some(gb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
{
gb.add_input_passthrough(
&mut entry.core,
redo_inner.clone(),
"",
&descriptor,
);
*lock(&redo_added) = true;
}
}
}
}
},
move || {
if *lock(&actually_added) {
let mut guard = lock(&project);
if let Some(entry) = guard.graph.get_mut(group_id) {
if let Some(gb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
{
gb.remove_input_passthrough(&mut entry.core, &inner);
}
}
}
},
)
.ok_or(Error::NoMem)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_group_remove_input_passthrough` (live).
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_remove_input_passthrough(
group: CHandle,
node: CHandle,
input_id: *const c_char,
element: c_int,
) -> c_int {
guard(|| {
let g = unsafe { node_ref(&group)? };
let n = unsafe { node_ref(&node)? };
if !Arc::ptr_eq(&g.project, &n.project) {
return Err(Error::Invalid);
}
let input = unsafe { cstr(input_id) }.ok_or(Error::Invalid)?.to_string();
let inner = InnerInput {
node: n.id,
input,
element,
};
let mut guard = lock(&g.project);
let entry = guard.graph.get_mut(g.id).ok_or(Error::NotFound)?;
let gb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
.ok_or(Error::State)?;
if !gb.contains_input_passthrough(&inner) {
return Err(Error::NotFound);
}
gb.remove_input_passthrough(&mut entry.core, &inner);
Ok(())
})
}
/// `oaknode_group_passthrough_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_passthrough_count(
group: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let g = unsafe { node_ref(&group)? };
let count = with_graph_read(&g.project, |graph| {
graph
.get(g.id)
.and_then(group_of)
.map(|gb| gb.passthroughs().len() as c_int)
})
.ok_or(Error::NotFound)?;
unsafe { *out_count = count };
Ok(())
})
}
/// `oaknode_group_passthrough_id_at` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_passthrough_id_at(
group: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let g = match unsafe { node_ref(&group) } {
Ok(g) => g,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let id = with_graph_read(&g.project, |graph| {
graph
.get(g.id)
.and_then(group_of)
.and_then(|gb| gb.passthroughs().get(index as usize))
.map(|(id, _)| id.clone())
});
match id {
Some(id) => copy_string_out(&id, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_group_passthrough_input_at`: inner node (borrowed),
/// input id (two-stage), element.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_passthrough_input_at(
group: CHandle,
index: c_int,
out_node: *mut CHandle,
buf: *mut c_char,
buf_size: c_int,
out_element: *mut c_int,
) -> c_int {
let g = match unsafe { node_ref(&group) } {
Ok(g) => g,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let inner = with_graph_read(&g.project, |graph| {
graph
.get(g.id)
.and_then(group_of)
.and_then(|gb| gb.passthroughs().get(index as usize))
.map(|(_, inner)| inner.clone())
});
let inner = match inner {
Some(inner) => inner,
None => return crate::error::OAKNODE_E_NOT_FOUND,
};
if !out_node.is_null() {
unsafe { *out_node = make_node_borrowed(g.project.clone(), inner.node) };
}
if !out_element.is_null() {
unsafe { *out_element = inner.element };
}
copy_string_out(&inner.input, buf, buf_size)
}
/// `oaknode_group_get_output_passthrough`: borrowed handle, empty
/// when unset; `OAKNODE_OK` either way.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_get_output_passthrough(
group: CHandle,
out_node: *mut CHandle,
) -> c_int {
guard(|| {
if out_node.is_null() {
return Err(Error::Invalid);
}
let g = unsafe { node_ref(&group)? };
let target = with_graph_read(&g.project, |graph| {
graph
.get(g.id)
.and_then(group_of)
.and_then(|gb| gb.output_passthrough())
});
match target {
Some(id) => unsafe { *out_node = make_node_borrowed(g.project.clone(), id) },
None => unsafe { *out_node = CHandle::null() },
}
Ok(())
})
}
/// `oaknode_group_set_output_passthrough` (live); `node` may be an
/// empty handle to clear.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_set_output_passthrough(
group: CHandle,
node: CHandle,
) -> c_int {
guard(|| {
let g = unsafe { node_ref(&group)? };
let target = if node.ctx.is_null() {
None
} else {
let n = unsafe { node_ref(&node)? };
if !Arc::ptr_eq(&g.project, &n.project) {
return Err(Error::Invalid);
}
Some(n.id)
};
let mut guard = lock(&g.project);
let entry = guard.graph.get_mut(g.id).ok_or(Error::NotFound)?;
let gb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
.ok_or(Error::State)?;
gb.set_output_passthrough(target);
Ok(())
})
}
/// `oaknode_group_set_output_passthrough_undoable`
/// (`olive::NodeGroupSetOutputPassthrough`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_set_output_passthrough_undoable(
group: CHandle,
node: CHandle,
out_command: *mut CHandle,
) -> c_int {
guard(|| {
if out_command.is_null() {
return Err(Error::Invalid);
}
let g = unsafe { node_ref(&group)? };
let new_target = if node.ctx.is_null() {
None
} else {
let n = unsafe { node_ref(&node)? };
if !Arc::ptr_eq(&g.project, &n.project) {
return Err(Error::Invalid);
}
Some(n.id)
};
let project = g.project.clone();
let group_id = g.id;
// `old_output_` is captured at redo time (C++ redo stores it
// before assigning; `// CPP-PARITY: group.cpp:380`).
let old_output = std::sync::Arc::new(std::sync::Mutex::new(None));
let redo_old = old_output.clone();
let redo_project = project.clone();
let cmd = crate::bridge::undo::command_from_closures(
move || {
let mut guard = lock(&redo_project);
if let Some(entry) = guard.graph.get_mut(group_id) {
if let Some(gb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
{
*lock(&redo_old) = gb.output_passthrough();
gb.set_output_passthrough(new_target);
}
}
},
move || {
let mut guard = lock(&project);
if let Some(entry) = guard.graph.get_mut(group_id) {
if let Some(gb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<NodeGroup>())
{
gb.set_output_passthrough(*lock(&old_output));
}
}
},
)
.ok_or(Error::NoMem)?;
unsafe { *out_command = cmd };
Ok(())
})
}
/// `oaknode_group_resolve_input`: follow group passthroughs to the
/// innermost non-group input; `OAKNODE_E_NOT_FOUND` when the input
/// does not resolve.
#[no_mangle]
pub unsafe extern "C" fn oaknode_group_resolve_input(
node: CHandle,
input_id: *const c_char,
element: c_int,
out_node: *mut CHandle,
buf: *mut c_char,
buf_size: c_int,
out_element: *mut c_int,
) -> c_int {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let input = match unsafe { cstr(input_id) } {
Some(s) => s,
None => return crate::error::OAKNODE_E_INVALID,
};
let resolved = with_graph_read(&n.project, |graph| {
let start = InnerInput {
node: n.id,
input: input.to_string(),
element,
};
// The input must resolve to a live input on the final node
// (C++ `resolve_input` returns whatever it lands on; the
// C ABI contract rejects unresolvable inputs).
let r = NodeGroup::resolve_input(graph, start);
let input_ok = graph
.get(r.node)
.map(|e| e.core.has_input(&r.input))
.unwrap_or(false);
input_ok.then_some(r)
});
let resolved = match resolved {
Some(r) => r,
None => return crate::error::OAKNODE_E_NOT_FOUND,
};
if !out_node.is_null() {
unsafe { *out_node = make_node_borrowed(n.project.clone(), resolved.node) };
}
if !out_element.is_null() {
unsafe { *out_element = resolved.element };
}
copy_string_out(&resolved.input, buf, buf_size)
}
}
// =====================================================================
// include/node/sequence.h
// =====================================================================
/// `include/node/sequence.h` exports. A sequence handle is a node
/// handle whose node carries [`crate::sequence::SequenceBehavior`].
/// `oaknode_sequence_create` builds a self-contained scratch project
/// holding the sequence node plus its three track lists (video, audio,
/// subtitle); adding the sequence to a project transfers the whole
/// subgraph (`oaknode_project_add_node`).
pub mod sequence {
use super::*;
use crate::node::NodeCore;
use crate::sequence::SequenceBehavior;
use crate::track::{TrackListBehavior, TrackType};
/// Borrowed copy of the sequence behavior behind a handle.
fn seq_of(n: &NodeRef) -> Result<crate::sequence::SequenceBehavior> {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.map(|s| SequenceBehavior {
track_lists: s.track_lists.clone(),
markers: s.markers.clone(),
workarea: s.workarea.clone(),
last_length: s.last_length,
autocache_video: s.autocache_video,
autocache_audio: s.autocache_audio,
playhead: s.playhead,
video_params: s.video_params.clone(),
audio_params: s.audio_params.clone(),
})
.ok_or(Error::State)
}
/// Build the default track-list set for a sequence: one node per
/// type, appended to `seq`'s `track_lists`.
fn append_default_track_lists(graph: &mut crate::graph::Graph, seq_id: NodeId) -> Vec<NodeId> {
let mut lists = Vec::new();
let mut base = 0i32;
for kind in [TrackType::Video, TrackType::Audio, TrackType::Subtitle] {
let mut behavior = TrackListBehavior::new(kind);
behavior.sequence = Some(seq_id);
behavior.array_base = base;
let id = graph.add_node(NodeCore::new(), Box::new(behavior));
lists.push(id);
base += 1;
}
lists
}
/// `oaknode_sequence_create`: an owned sequence handle whose scratch
/// project holds the sequence + its three track lists.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_create() -> CHandle {
let scratch = Project::new();
let id = {
let mut guard = lock(&scratch);
let mut seq = SequenceBehavior::new();
seq.set_default_parameters();
let mut core = NodeCore::new();
core.add_input(crate::input::Input::new(
crate::sequence::TEXTURE_INPUT,
crate::value::ValueType::Texture,
crate::value::NodeValue::None,
));
core.add_input(crate::input::Input::new(
crate::sequence::SAMPLES_INPUT,
crate::value::ValueType::Samples,
crate::value::NodeValue::None,
));
// One array input per track list (`track_in_%1`; the video
// list owns track_in_0, audio track_in_1, subtitle
// track_in_2 — `// CPP-PARITY: sequence.h`).
for base in 0..3 {
let mut track_input = crate::input::Input::new(
&crate::sequence::TRACK_INPUT_FORMAT.replace("%1", &base.to_string()),
crate::value::ValueType::None,
crate::value::NodeValue::None,
);
track_input.flags |= crate::input::flags::ARRAY;
core.add_input(track_input);
}
let seq_id = guard.graph.add_node(core, Box::new(seq));
// Fill the track lists.
let lists = append_default_track_lists(&mut guard.graph, seq_id);
if let Some(entry) = guard.graph.get_mut(seq_id) {
if let Some(s) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<SequenceBehavior>())
{
s.track_lists = lists;
}
}
seq_id
};
make_node_owned(scratch, id)
}
/// `oaknode_sequence_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_free(sequence: *mut CHandle) {
if sequence.is_null() || unsafe { (*sequence).ctx.is_null() } {
return;
}
let h = unsafe { (*sequence).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*sequence).ctx = std::ptr::null_mut() };
}
/// `oaknode_sequence_set_default_parameters`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_set_default_parameters(sequence: CHandle) -> c_int {
guard(|| {
let n = unsafe { node_ref(&sequence)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<SequenceBehavior>())
.ok_or(Error::State)?;
s.set_default_parameters();
Ok(())
})
}
/// `oaknode_sequence_as_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_as_node(sequence: CHandle) -> CHandle {
match unsafe { node_ref(&sequence) } {
Ok(n) => make_node_borrowed(n.project.clone(), n.id),
Err(_) => CHandle::null(),
}
}
/// `oaknode_sequence_from_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_from_node(node: CHandle) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
let guard = lock(&n.project);
let is_seq = guard
.graph
.get(n.id)
.and_then(|e| e.behavior.as_any())
.map(|a| a.downcast_ref::<SequenceBehavior>().is_some())
.unwrap_or(false);
if is_seq {
make_node_borrowed(n.project.clone(), n.id)
} else {
CHandle::null()
}
}
/// `oaknode_sequence_get_track_list`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_track_list(
sequence: CHandle,
type_: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let kind = TrackType::from_c(type_).ok_or(Error::NotFound)?;
let (project, list_id) = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
let list = s
.track_lists
.iter()
.find(|id| {
guard
.graph
.get(**id)
.and_then(|e| e.behavior.as_any())
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
.map(|tl| tl.kind == kind)
.unwrap_or(false)
})
.copied()
.ok_or(Error::NotFound)?;
(n.project.clone(), list)
};
unsafe { *out = make_node_borrowed(project, list_id) };
Ok(())
})
}
/// `oaknode_sequence_get_track_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_track_count(
sequence: CHandle,
type_: c_int,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let kind = TrackType::from_c(type_).ok_or(Error::NotFound)?;
let mut total = 0;
{
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
for id in &s.track_lists {
if let Some(le) = guard.graph.get(*id) {
if let Some(tl) = le
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
{
if tl.kind == kind {
total = tl.tracks.len();
}
}
}
}
}
unsafe { *count = total as c_int };
Ok(())
})
}
/// `oaknode_sequence_get_track_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_track_at(
sequence: CHandle,
type_: c_int,
index: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let kind = TrackType::from_c(type_).ok_or(Error::NotFound)?;
if index < 0 {
return Err(Error::NotFound);
}
let (project, track) = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
let mut found = None;
for id in &s.track_lists {
if let Some(le) = guard.graph.get(*id) {
if let Some(tl) = le
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
{
if tl.kind == kind {
found = tl.tracks.get(index as usize).copied();
}
}
}
}
(n.project.clone(), found.ok_or(Error::NotFound)?)
};
unsafe { *out = make_node_borrowed(project, track) };
Ok(())
})
}
/// `oaknode_sequence_get_all_track_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_all_track_count(
sequence: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
let mut total = 0usize;
for id in &s.track_lists {
if let Some(le) = guard.graph.get(*id) {
if let Some(tl) = le
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
{
total += tl.tracks.len();
}
}
}
unsafe { *count = total as c_int };
Ok(())
})
}
/// `oaknode_sequence_get_all_track_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_all_track_at(
sequence: CHandle,
index: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
if index < 0 {
return Err(Error::NotFound);
}
let (project, track) = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
let mut flat: Vec<NodeId> = Vec::new();
for id in &s.track_lists {
if let Some(le) = guard.graph.get(*id) {
if let Some(tl) = le
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
{
flat.extend(tl.tracks.iter().copied());
}
}
}
(
n.project.clone(),
flat.get(index as usize).copied().ok_or(Error::NotFound)?,
)
};
unsafe { *out = make_node_borrowed(project, track) };
Ok(())
})
}
/// `oaknode_sequence_get_playhead`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_playhead(
sequence: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
unsafe {
*numerator = s.playhead.numerator() as c_int;
*denominator = s.playhead.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_sequence_set_playhead`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_set_playhead(
sequence: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&sequence)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<SequenceBehavior>())
.ok_or(Error::State)?;
s.playhead = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
Ok(())
})
}
/// `oaknode_sequence_get_length` (cached overall length).
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_length(
sequence: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
unsafe {
*numerator = s.last_length.numerator() as c_int;
*denominator = s.last_length.denominator() as c_int;
}
Ok(())
})
}
/// Compute the length of the `kind` track lists (longest track).
fn list_length(n: &NodeRef, kind: TrackType) -> Result<oakcore_rs::Rational> {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
.ok_or(Error::State)?;
let mut longest = oakcore_rs::Rational::new(0, 1);
for id in &s.track_lists {
if let Some(le) = guard.graph.get(*id) {
if let Some(tl) = le
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
{
if tl.kind == kind {
let len = tl.total_length(&GraphTrackRange {
graph: &guard.graph,
});
if len > longest {
longest = len;
}
}
}
}
}
Ok(longest)
}
/// `oaknode_sequence_get_video_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_video_length(
sequence: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let len = list_length(&n, TrackType::Video)?;
unsafe {
*numerator = len.numerator() as c_int;
*denominator = len.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_sequence_get_audio_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_audio_length(
sequence: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let len = list_length(&n, TrackType::Audio)?;
unsafe {
*numerator = len.numerator() as c_int;
*denominator = len.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_sequence_verify_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_verify_length(sequence: CHandle) -> c_int {
guard(|| {
let n = unsafe { node_ref(&sequence)? };
let video = list_length(&n, TrackType::Video)?;
let audio = list_length(&n, TrackType::Audio)?;
let overall = if video > audio { video } else { audio };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<SequenceBehavior>())
.ok_or(Error::State)?;
s.verify_length((video, audio, overall));
Ok(())
})
}
/// `oaknode_sequence_get_video_stream_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_video_stream_count(
sequence: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let s = seq_of(&n)?;
unsafe { *count = s.video_stream_count() as c_int };
Ok(())
})
}
/// `oaknode_sequence_get_audio_stream_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_audio_stream_count(
sequence: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let s = seq_of(&n)?;
unsafe { *count = s.audio_stream_count() as c_int };
Ok(())
})
}
/// `oaknode_sequence_get_video_params`: the video params at `index` as
/// a NEW owned oakcommon handle (`// CPP-PARITY:
/// src/node/c_api/sequence.cpp:248`). The Rust model's `VideoParams`
/// carries width/height/frame-rate/format/channels; the handle is
/// built through the oakcommon videoparams C ABI (`OAKNODE_E_NOMEM`
/// when oakcommon is unavailable — the C++ null-handle path).
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_video_params(
sequence: CHandle,
index: c_int,
out: *mut std::ffi::c_void,
) -> c_int {
guard(|| {
if out.is_null() || index < 0 {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let s = seq_of(&n)?;
let params = s.video_params.get(index as usize).ok_or(Error::NotFound)?;
let handle = super::videoparams_handle_from(params).ok_or(Error::NoMem)?;
unsafe { *(out as *mut crate::handle::CHandle) = handle };
Ok(())
})
}
/// `oaknode_sequence_set_video_params`: replace the video params at
/// `index` with a copy of `params` (`// CPP-PARITY: sequence.cpp:270`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_set_video_params(
sequence: CHandle,
index: c_int,
params: crate::handle::CHandle,
) -> c_int {
guard(|| {
if index < 0 || params.ctx.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let read = super::videoparams_from_handle(params).ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<SequenceBehavior>())
.ok_or(Error::State)?;
if index as usize >= s.video_params.len() {
return Err(Error::NotFound);
}
s.video_params[index as usize] = read;
Ok(())
})
}
/// `oaknode_sequence_get_audio_params`: the audio params at `index` as
/// a NEW owned oakcore handle (`OakAudioParams **out`,
/// `// CPP-PARITY: sequence.cpp:293`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_get_audio_params(
sequence: CHandle,
index: c_int,
out: *mut *mut std::ffi::c_void,
) -> c_int {
guard(|| {
if out.is_null() || index < 0 {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let s = seq_of(&n)?;
let params = s.audio_params.get(index as usize).ok_or(Error::NotFound)?;
let handle = crate::bridge::core::audioparams_create(
params.sample_rate,
params.channel_layout,
params.format,
)
.ok_or(Error::NoMem)?;
unsafe { *out = handle };
Ok(())
})
}
/// `oaknode_sequence_set_audio_params`: replace the audio params at
/// `index` with a copy of `params` (`// CPP-PARITY: sequence.cpp:312`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_sequence_set_audio_params(
sequence: CHandle,
index: c_int,
params: *const std::ffi::c_void,
) -> c_int {
guard(|| {
if params.is_null() || index < 0 {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&sequence)? };
let read = crate::value::AudioParams {
sample_rate: crate::bridge::core::audioparams_sample_rate(params)
.ok_or(Error::Invalid)?,
channel_layout: crate::bridge::core::audioparams_channel_layout(params)
.ok_or(Error::Invalid)?,
format: crate::bridge::core::audioparams_format(params).ok_or(Error::Invalid)?,
};
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let s = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<SequenceBehavior>())
.ok_or(Error::State)?;
if index as usize >= s.audio_params.len() {
return Err(Error::NotFound);
}
s.audio_params[index as usize] = read;
Ok(())
})
}
}
// =====================================================================
// include/node/track.h
// =====================================================================
/// `include/node/track.h` exports. Track and track-list handles are node
/// handles whose nodes carry [`crate::track::TrackBehavior`] /
/// [`crate::track::TrackListBehavior`].
pub mod track {
use super::*;
use crate::node::NodeCore;
use crate::track::{TrackBehavior, TrackListBehavior, TrackType};
/// Borrowed track behavior copy.
fn track_of(n: &NodeRef) -> Result<crate::track::TrackBehavior> {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.map(|t| TrackBehavior {
kind: t.kind,
blocks: t.blocks.clone(),
muted: t.muted,
locked: t.locked,
height: t.height,
index: t.index,
track_list: t.track_list,
})
.ok_or(Error::State)
}
/// Borrowed track-list behavior copy.
fn tracklist_of(n: &NodeRef) -> Result<crate::track::TrackListBehavior> {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
.map(|t| TrackListBehavior {
kind: t.kind,
tracks: t.tracks.clone(),
sequence: t.sequence,
array_base: t.array_base,
})
.ok_or(Error::State)
}
/// `oaknode_track_as_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_as_node(track: CHandle) -> CHandle {
match unsafe { node_ref(&track) } {
Ok(n) => make_node_borrowed(n.project.clone(), n.id),
Err(_) => CHandle::null(),
}
}
/// `oaknode_track_create`: owned track in a scratch project.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_create(type_: c_int) -> CHandle {
let kind = match TrackType::from_c(type_) {
Some(k) => k,
None => return CHandle::null(),
};
let scratch = Project::new();
let id = {
let mut guard = lock(&scratch);
guard
.graph
.add_node(NodeCore::new(), Box::new(TrackBehavior::new(kind)))
};
make_node_owned(scratch, id)
}
/// `oaknode_track_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_free(track: *mut CHandle) {
if track.is_null() || unsafe { (*track).ctx.is_null() } {
return;
}
let h = unsafe { (*track).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*track).ctx = std::ptr::null_mut() };
}
/// `oaknode_track_get_type`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_type(track: CHandle, type_: *mut c_int) -> c_int {
guard(|| {
if type_.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *type_ = t.kind.to_c() };
Ok(())
})
}
/// `oaknode_track_set_type`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_set_type(track: CHandle, type_: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&track)? };
let kind = TrackType::from_c(type_).ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
t.kind = kind;
Ok(())
})
}
/// `oaknode_track_get_height`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_height(track: CHandle, height: *mut f64) -> c_int {
guard(|| {
if height.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *height = t.height };
Ok(())
})
}
/// `oaknode_track_set_height`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_set_height(track: CHandle, height: f64) -> c_int {
guard(|| {
let n = unsafe { node_ref(&track)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
t.height = height;
Ok(())
})
}
/// `oaknode_track_get_height_in_pixels`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_height_in_pixels(
track: CHandle,
height: *mut c_int,
) -> c_int {
guard(|| {
if height.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *height = crate::track::internal_height_to_pixel_height(t.height) };
Ok(())
})
}
/// `oaknode_track_set_height_in_pixels`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_set_height_in_pixels(
track: CHandle,
height: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&track)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
t.height = crate::track::pixel_height_to_internal_height(height);
Ok(())
})
}
/// `oaknode_track_get_default_height_in_pixels`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_default_height_in_pixels() -> c_int {
crate::track::internal_height_to_pixel_height(crate::track::DEFAULT_HEIGHT_INTERNAL)
}
/// `oaknode_track_get_minimum_height_in_pixels`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_minimum_height_in_pixels() -> c_int {
crate::track::internal_height_to_pixel_height(crate::track::MINIMUM_HEIGHT_INTERNAL)
}
/// `oaknode_track_get_index`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_index(track: CHandle, index: *mut c_int) -> c_int {
guard(|| {
if index.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *index = t.index };
Ok(())
})
}
/// `oaknode_track_set_index`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_set_index(track: CHandle, index: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&track)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
t.index = index;
Ok(())
})
}
/// `oaknode_track_get_muted`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_muted(track: CHandle, muted: *mut c_int) -> c_int {
guard(|| {
if muted.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *muted = t.muted as c_int };
Ok(())
})
}
/// `oaknode_track_set_muted`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_set_muted(track: CHandle, muted: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&track)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
t.muted = muted != 0;
Ok(())
})
}
/// `oaknode_track_get_locked`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_locked(track: CHandle, locked: *mut c_int) -> c_int {
guard(|| {
if locked.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *locked = t.locked as c_int };
Ok(())
})
}
/// `oaknode_track_set_locked`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_set_locked(track: CHandle, locked: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&track)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
t.locked = locked != 0;
Ok(())
})
}
/// `oaknode_track_get_reference`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_reference(
track: CHandle,
type_: *mut c_int,
index: *mut c_int,
) -> c_int {
guard(|| {
if type_.is_null() || index.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
let (ty, i) = t.reference();
unsafe {
*type_ = ty;
*index = i;
}
Ok(())
})
}
/// `oaknode_track_get_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_length(
track: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let len = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
t.length(&GraphBlockRange {
graph: &guard.graph,
})
};
unsafe {
*numerator = len.numerator() as c_int;
*denominator = len.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_track_get_sequence` (borrowed).
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_sequence(
track: CHandle,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let (project, seq) = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
let seq = t.track_list.and_then(|list| {
guard
.graph
.get(list)
.and_then(|le| le.behavior.as_any())
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
.and_then(|tl| tl.sequence)
});
(n.project.clone(), seq)
};
unsafe {
*out = match seq {
Some(s) => make_node_borrowed(project, s),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_track_get_block_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_block_count(
track: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
let t = track_of(&n)?;
unsafe { *count = t.blocks.len() as c_int };
Ok(())
})
}
/// `oaknode_track_get_block_at` (borrowed).
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_block_at(
track: CHandle,
index: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&track)? };
if index < 0 {
return Err(Error::NotFound);
}
let (project, block) = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
(
n.project.clone(),
t.blocks
.get(index as usize)
.copied()
.ok_or(Error::NotFound)?,
)
};
unsafe { *out = make_node_borrowed(project, block) };
Ok(())
})
}
/// Shared block-append/insert helper: adopt `block` into the track's
/// project and list. Returns the block's id in the track's project
/// (it may differ after the move).
fn adopt_block(
track_project: &ProjectArc,
track_id: NodeId,
block: &NodeRef,
block_handle: &CHandle,
) -> Result<NodeId> {
if !Arc::ptr_eq(&block.project, track_project) {
// Move the block into the track's project.
let new_id = move_node_between(&block.project, block.id, track_project)?;
let r = NodeRef {
project: track_project.clone(),
id: new_id,
owned: std::sync::Arc::new(AtomicBool::new(false)),
};
unsafe { write_node_ref(block_handle, r) };
// The block's core records the owning track.
let mut guard = lock(track_project);
if let Some(entry) = guard.graph.get_mut(new_id) {
if let Some(core) = block_core_mut(entry) {
core.track = Some(track_id);
}
}
Ok(new_id)
} else {
let mut guard = lock(track_project);
if let Some(entry) = guard.graph.get_mut(block.id) {
if let Some(core) = block_core_mut(entry) {
core.track = Some(track_id);
}
}
Ok(block.id)
}
}
/// `oaknode_track_append_block`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_append_block(track: CHandle, block: CHandle) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let block_id = adopt_block(&t.project, t.id, &b, &block)?;
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
tb.append_block(block_id);
Ok(())
})
}
/// `oaknode_track_prepend_block`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_prepend_block(track: CHandle, block: CHandle) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let block_id = adopt_block(&t.project, t.id, &b, &block)?;
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
tb.prepend_block(block_id);
Ok(())
})
}
/// `oaknode_track_insert_block_at_index`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_insert_block_at_index(
track: CHandle,
block: CHandle,
index: c_int,
) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let block_id = adopt_block(&t.project, t.id, &b, &block)?;
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
tb.insert_block_at_index(block_id, index.max(0) as usize);
Ok(())
})
}
/// `oaknode_track_insert_block_after`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_insert_block_after(
track: CHandle,
block: CHandle,
before: CHandle,
) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let ref_block = unsafe { node_ref(&before)? };
let block_id = adopt_block(&t.project, t.id, &b, &block)?;
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
if tb.insert_block_after(block_id, ref_block.id) {
Ok(())
} else {
Err(Error::NotFound)
}
})
}
/// `oaknode_track_insert_block_before`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_insert_block_before(
track: CHandle,
block: CHandle,
after: CHandle,
) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let ref_block = unsafe { node_ref(&after)? };
let block_id = adopt_block(&t.project, t.id, &b, &block)?;
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
if tb.insert_block_before(block_id, ref_block.id) {
Ok(())
} else {
Err(Error::NotFound)
}
})
}
/// `oaknode_track_ripple_remove_block`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_ripple_remove_block(
track: CHandle,
block: CHandle,
) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
if tb.ripple_remove_block(b.id) {
if let Some(entry) = guard.graph.get_mut(b.id) {
if let Some(core) = block_core_mut(entry) {
core.track = None;
}
}
Ok(())
} else {
Err(Error::NotFound)
}
})
}
/// `oaknode_track_replace_block`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_replace_block(
track: CHandle,
old_block: CHandle,
new_block: CHandle,
) -> c_int {
guard(|| {
let t = unsafe { node_ref(&track)? };
let old = unsafe { node_ref(&old_block)? };
let new = unsafe { node_ref(&new_block)? };
let new_id = adopt_block(&t.project, t.id, &new, &new_block)?;
let mut guard = lock(&t.project);
let entry = guard.graph.get_mut(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
.ok_or(Error::State)?;
if tb.replace_block(old.id, new_id) {
Ok(())
} else {
Err(Error::NotFound)
}
})
}
/// `oaknode_track_get_block_index`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_block_index(
track: CHandle,
block: CHandle,
index: *mut c_int,
) -> c_int {
guard(|| {
if index.is_null() {
return Err(Error::Invalid);
}
let t = unsafe { node_ref(&track)? };
let b = unsafe { node_ref(&block)? };
let tb = track_of(&t)?;
match tb.block_index(b.id) {
Some(i) => {
unsafe { *index = i as c_int };
Ok(())
}
None => Err(Error::NotFound),
}
})
}
/// `oaknode_track_get_block_containing_time`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_block_containing_time(
track: CHandle,
numerator: c_int,
denominator: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let t = unsafe { node_ref(&track)? };
let time = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
let (project, block) = {
let guard = lock(&t.project);
let entry = guard.graph.get(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
let found = tb.block_containing_time(
time,
&GraphBlockRange {
graph: &guard.graph,
},
);
(t.project.clone(), found)
};
unsafe {
*out = match block {
Some(b) => make_node_borrowed(project, b),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_track_get_visible_block_at_time`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_visible_block_at_time(
track: CHandle,
numerator: c_int,
denominator: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let t = unsafe { node_ref(&track)? };
let time = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
let (project, block) = {
let guard = lock(&t.project);
let entry = guard.graph.get(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
let found = tb.visible_block_at_time(
time,
&GraphBlockRange {
graph: &guard.graph,
},
);
(t.project.clone(), found)
};
unsafe {
*out = match block {
Some(b) => make_node_borrowed(project, b),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_track_is_range_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_is_range_free(
track: CHandle,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
is_free: *mut c_int,
) -> c_int {
guard(|| {
if is_free.is_null() {
return Err(Error::Invalid);
}
let t = unsafe { node_ref(&track)? };
let range = oakcore_rs::TimeRange::new(
oakcore_rs::Rational::new(in_num as i64, in_den as i64),
oakcore_rs::Rational::new(out_num as i64, out_den as i64),
);
let free = {
let guard = lock(&t.project);
let entry = guard.graph.get(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
tb.is_range_free(
range,
&GraphBlockRange {
graph: &guard.graph,
},
)
};
unsafe { *is_free = free as c_int };
Ok(())
})
}
/// `oaknode_track_get_nearest_block_before_or_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_nearest_block_before_or_at(
track: CHandle,
numerator: c_int,
denominator: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let t = unsafe { node_ref(&track)? };
let time = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
let (project, block) = {
let guard = lock(&t.project);
let entry = guard.graph.get(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
// The last block whose in-point is <= time.
let found = tb
.blocks
.iter()
.filter(|b| {
guard
.graph
.get(**b)
.and_then(block_core)
.map(|c| c.in_() <= time)
.unwrap_or(false)
})
.next_back()
.copied();
(t.project.clone(), found)
};
unsafe {
*out = match block {
Some(b) => make_node_borrowed(project, b),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_track_get_nearest_block_after_or_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_track_get_nearest_block_after_or_at(
track: CHandle,
numerator: c_int,
denominator: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let t = unsafe { node_ref(&track)? };
let time = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
let (project, block) = {
let guard = lock(&t.project);
let entry = guard.graph.get(t.id).ok_or(Error::NotFound)?;
let tb = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.ok_or(Error::State)?;
let found = tb
.blocks
.iter()
.filter(|b| {
guard
.graph
.get(**b)
.and_then(block_core)
.map(|c| c.out() >= time)
.unwrap_or(false)
})
.next()
.copied();
(t.project.clone(), found)
};
unsafe {
*out = match block {
Some(b) => make_node_borrowed(project, b),
None => CHandle::null(),
};
}
Ok(())
})
}
// ---- TrackList -----------------------------------------------------
/// `oaknode_tracklist_get_sequence`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_sequence(
list: CHandle,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
unsafe {
*out = match tl.sequence {
Some(s) => make_node_borrowed(n.project.clone(), s),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_tracklist_get_track_input_id` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_track_input_id(
list: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&list) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let tl = match tracklist_of(&n) {
Ok(tl) => tl,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let input_id =
crate::sequence::TRACK_INPUT_FORMAT.replace("%1", &tl.array_base.to_string());
copy_string_out(&input_id, buf, buf_size)
}
/// `oaknode_tracklist_array_append`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_array_append(list: CHandle) -> c_int {
guard(|| {
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
let input_id =
crate::sequence::TRACK_INPUT_FORMAT.replace("%1", &tl.array_base.to_string());
let mut guard = lock(&n.project);
let seq = tl.sequence.ok_or(Error::State)?;
let size = guard
.graph
.get(seq)
.map(|e| e.core.input_array_size(&input_id))
.unwrap_or(0);
guard.graph.input_array_insert(seq, &input_id, size as i32)
})
}
/// `oaknode_tracklist_array_remove_last`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_array_remove_last(list: CHandle) -> c_int {
guard(|| {
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
let input_id =
crate::sequence::TRACK_INPUT_FORMAT.replace("%1", &tl.array_base.to_string());
let mut guard = lock(&n.project);
let seq = tl.sequence.ok_or(Error::State)?;
let size = guard
.graph
.get(seq)
.map(|e| e.core.input_array_size(&input_id))
.unwrap_or(0);
if size == 0 {
return Ok(());
}
guard
.graph
.input_array_remove(seq, &input_id, (size - 1) as i32)
})
}
/// `oaknode_tracklist_get_array_index_from_cache_index` (identity for
/// compact lists; the cached index equals the array element when no
/// slots are disconnected).
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_array_index_from_cache_index(
list: CHandle,
cache_index: c_int,
out_index: *mut c_int,
) -> c_int {
guard(|| {
if out_index.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
if cache_index < 0 || cache_index as usize >= tl.tracks.len() {
return Err(Error::NotFound);
}
unsafe { *out_index = cache_index };
Ok(())
})
}
/// `oaknode_tracklist_get_type`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_type(list: CHandle, type_: *mut c_int) -> c_int {
guard(|| {
if type_.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
unsafe { *type_ = tl.kind.to_c() };
Ok(())
})
}
/// `oaknode_tracklist_get_track_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_track_count(
list: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
unsafe { *count = tl.tracks.len() as c_int };
Ok(())
})
}
/// `oaknode_tracklist_get_track_at` (borrowed).
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_track_at(
list: CHandle,
index: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
if index < 0 {
return Err(Error::NotFound);
}
let tl = tracklist_of(&n)?;
let track = tl
.tracks
.get(index as usize)
.copied()
.ok_or(Error::NotFound)?;
unsafe { *out = make_node_borrowed(n.project.clone(), track) };
Ok(())
})
}
/// `oaknode_tracklist_get_total_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_total_length(
list: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
let len = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let tl = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
.ok_or(Error::State)?;
tl.total_length(&GraphTrackRange {
graph: &guard.graph,
})
};
unsafe {
*numerator = len.numerator() as c_int;
*denominator = len.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_tracklist_get_array_size` (>= track count).
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_array_size(
list: CHandle,
size: *mut c_int,
) -> c_int {
guard(|| {
if size.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&list)? };
let tl = tracklist_of(&n)?;
let input_id =
crate::sequence::TRACK_INPUT_FORMAT.replace("%1", &tl.array_base.to_string());
let guard = lock(&n.project);
let arr = tl
.sequence
.and_then(|seq| guard.graph.get(seq))
.map(|e| e.core.input_array_size(&input_id))
.unwrap_or(0);
unsafe { *size = arr as c_int };
Ok(())
})
}
/// `oaknode_tracklist_add_track`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_add_track(list: CHandle, track: CHandle) -> c_int {
guard(|| {
let l = unsafe { node_ref(&list)? };
let t = unsafe { node_ref(&track)? };
let tl = tracklist_of(&l)?;
// Move the track into the list's project; the id may change.
let track_id = if !Arc::ptr_eq(&t.project, &l.project) {
let new_id = move_node_between(&t.project, t.id, &l.project)?;
let r = NodeRef {
project: l.project.clone(),
id: new_id,
owned: std::sync::Arc::new(AtomicBool::new(false)),
};
unsafe { write_node_ref(&track, r) };
new_id
} else {
t.id
};
let mut guard = lock(&l.project);
// Inherit the previous track's height.
let prev_height = {
let entry = guard.graph.get(l.id).ok_or(Error::NotFound)?;
let tl = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
.ok_or(Error::State)?;
tl.tracks
.last()
.and_then(|last| guard.graph.get(*last))
.and_then(|le| le.behavior.as_any())
.and_then(|a| a.downcast_ref::<TrackBehavior>())
.map(|tb| tb.height)
.unwrap_or(crate::track::DEFAULT_HEIGHT_INTERNAL)
};
{
let entry = guard.graph.get_mut(l.id).ok_or(Error::NotFound)?;
let tl = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackListBehavior>())
.ok_or(Error::State)?;
let index = tl.tracks.len() as i32;
tl.tracks.push(track_id);
if let Some(entry) = guard.graph.get_mut(track_id) {
if let Some(tb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
{
tb.track_list = Some(l.id);
tb.index = index;
tb.height = prev_height;
}
}
}
Ok(())
})
}
/// `oaknode_tracklist_remove_track`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_remove_track(
list: CHandle,
track: CHandle,
) -> c_int {
guard(|| {
let l = unsafe { node_ref(&list)? };
let t = unsafe { node_ref(&track)? };
let mut guard = lock(&l.project);
let removed = {
let entry = guard.graph.get_mut(l.id).ok_or(Error::NotFound)?;
let tl = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackListBehavior>())
.ok_or(Error::State)?;
let before = tl.tracks.len();
tl.tracks.retain(|x| *x != t.id);
tl.tracks.len() != before
};
if removed {
// Renumber the remaining tracks' indices.
let ids: Vec<NodeId> = {
let entry = guard.graph.get(l.id).ok_or(Error::NotFound)?;
let tl = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
.ok_or(Error::State)?;
tl.tracks.clone()
};
for (i, id) in ids.iter().enumerate() {
if let Some(entry) = guard.graph.get_mut(*id) {
if let Some(tb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
{
tb.index = i as i32;
}
}
}
}
if removed {
if let Some(entry) = guard.graph.get_mut(t.id) {
if let Some(tb) = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TrackBehavior>())
{
tb.track_list = None;
}
}
Ok(())
} else {
Err(Error::NotFound)
}
})
}
}
// =====================================================================
// include/node/block.h
// =====================================================================
/// `include/node/block.h` exports. Block handles are node handles whose
/// nodes carry a block behavior ([`crate::block::BlockCore`]).
pub mod block {
use super::*;
use crate::block::{BlockCore, ClipBlockBehavior, GapBlockBehavior, TransitionBlockBehavior};
/// True when the node carries a block behavior.
fn is_block(n: &NodeRef) -> bool {
let guard = lock(&n.project);
guard.graph.get(n.id).and_then(block_core).is_some()
}
/// `oaknode_block_clip_create`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_clip_create() -> CHandle {
let scratch = Project::new();
let (core, behavior) = crate::block::clip_create();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
make_node_owned(scratch, id)
}
/// `oaknode_block_gap_create`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_gap_create() -> CHandle {
let scratch = Project::new();
let (core, behavior) = crate::block::gap_create();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
make_node_owned(scratch, id)
}
/// `oaknode_block_transition_create`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_transition_create(kind: c_int) -> CHandle {
if !(0..=1).contains(&kind) {
return CHandle::null();
}
let scratch = Project::new();
let (core, behavior) = crate::block::transition_create();
let id = {
let mut guard = lock(&scratch);
guard.graph.add_node(core, behavior)
};
make_node_owned(scratch, id)
}
/// `oaknode_block_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_free(block: *mut CHandle) {
if block.is_null() || unsafe { (*block).ctx.is_null() } {
return;
}
let h = unsafe { (*block).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*block).ctx = std::ptr::null_mut() };
}
/// `oaknode_block_get_kind`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_kind(block: CHandle, out_kind: *mut c_int) -> c_int {
guard(|| {
if out_kind.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let kind = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
entry
.behavior
.as_any()
.map(|a| {
if a.downcast_ref::<ClipBlockBehavior>().is_some() {
1
} else if a.downcast_ref::<GapBlockBehavior>().is_some() {
2
} else if a.downcast_ref::<TransitionBlockBehavior>().is_some() {
3
} else {
0
}
})
.ok_or(Error::State)?
};
unsafe { *out_kind = kind };
Ok(())
})
}
/// `oaknode_block_as_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_as_node(block: CHandle) -> CHandle {
match unsafe { node_ref(&block) } {
Ok(n) => make_node_borrowed(n.project.clone(), n.id),
Err(_) => CHandle::null(),
}
}
/// `oaknode_block_from_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_from_node(node: CHandle) -> CHandle {
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return CHandle::null(),
};
if is_block(&n) {
make_node_borrowed(n.project.clone(), n.id)
} else {
CHandle::null()
}
}
/// `oaknode_block_get_in`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_in(
block: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let core = {
let guard = lock(&n.project);
guard
.graph
.get(n.id)
.and_then(block_core)
.map(|c| BlockCore::default())
.ok_or(Error::State)?;
guard
.graph
.get(n.id)
.and_then(block_core)
.map(|c| BlockCore {
range: c.range,
media_in: c.media_in,
speed: c.speed,
reversed: c.reversed,
links: c.links.clone(),
enabled: c.enabled,
maintain_audio_pitch: c.maintain_audio_pitch,
loop_mode: c.loop_mode,
track: c.track,
})
};
let core = core.ok_or(Error::State)?;
unsafe {
*numerator = core.in_().numerator() as c_int;
*denominator = core.in_().denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_block_set_in`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_set_in(
block: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&block)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = block_core_mut(entry).ok_or(Error::State)?;
core.set_in(oakcore_rs::Rational::new(
numerator as i64,
denominator as i64,
));
Ok(())
})
}
/// `oaknode_block_get_out`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_out(
block: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let guard = lock(&n.project);
let core = guard
.graph
.get(n.id)
.and_then(block_core)
.ok_or(Error::State)?;
unsafe {
*numerator = core.out().numerator() as c_int;
*denominator = core.out().denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_block_set_out`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_set_out(
block: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&block)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = block_core_mut(entry).ok_or(Error::State)?;
core.set_out(oakcore_rs::Rational::new(
numerator as i64,
denominator as i64,
));
Ok(())
})
}
/// `oaknode_block_get_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_length(
block: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let guard = lock(&n.project);
let core = guard
.graph
.get(n.id)
.and_then(block_core)
.ok_or(Error::State)?;
let len = core.length();
unsafe {
*numerator = len.numerator() as c_int;
*denominator = len.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_block_set_length_and_media_out`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_set_length_and_media_out(
block: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&block)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = block_core_mut(entry).ok_or(Error::State)?;
core.set_length_and_media_out(oakcore_rs::Rational::new(
numerator as i64,
denominator as i64,
));
Ok(())
})
}
/// `oaknode_block_set_length_and_media_in`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_set_length_and_media_in(
block: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&block)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = block_core_mut(entry).ok_or(Error::State)?;
core.set_length_and_media_in(oakcore_rs::Rational::new(
numerator as i64,
denominator as i64,
));
Ok(())
})
}
/// `oaknode_block_get_enabled`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_enabled(
block: CHandle,
enabled: *mut c_int,
) -> c_int {
guard(|| {
if enabled.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let guard = lock(&n.project);
let core = guard
.graph
.get(n.id)
.and_then(block_core)
.ok_or(Error::State)?;
unsafe { *enabled = core.enabled as c_int };
Ok(())
})
}
/// `oaknode_block_set_enabled`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_set_enabled(block: CHandle, enabled: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&block)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = block_core_mut(entry).ok_or(Error::State)?;
core.enabled = enabled != 0;
Ok(())
})
}
/// `oaknode_block_get_previous`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_previous(
block: CHandle,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let (project, prev) = {
let guard = lock(&n.project);
let core = guard
.graph
.get(n.id)
.and_then(block_core)
.ok_or(Error::State)?;
let track = core.track.ok_or(Error::State)?;
let tb = guard
.graph
.get(track)
.and_then(|e| e.behavior.as_any())
.and_then(|a| a.downcast_ref::<crate::track::TrackBehavior>())
.ok_or(Error::State)?;
let idx = tb.block_index(n.id).ok_or(Error::NotFound)?;
(
n.project.clone(),
idx.checked_sub(1).and_then(|i| tb.block_at(i)),
)
};
unsafe {
*out = match prev {
Some(p) => make_node_borrowed(project, p),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_block_get_next`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_next(block: CHandle, out: *mut CHandle) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let (project, next) = {
let guard = lock(&n.project);
let core = guard
.graph
.get(n.id)
.and_then(block_core)
.ok_or(Error::State)?;
let track = core.track.ok_or(Error::State)?;
let tb = guard
.graph
.get(track)
.and_then(|e| e.behavior.as_any())
.and_then(|a| a.downcast_ref::<crate::track::TrackBehavior>())
.ok_or(Error::State)?;
let idx = tb.block_index(n.id).ok_or(Error::NotFound)?;
(n.project.clone(), tb.block_at(idx + 1))
};
unsafe {
*out = match next {
Some(p) => make_node_borrowed(project, p),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_block_get_track` (borrowed).
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_track(block: CHandle, out: *mut CHandle) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let (project, track) = {
let guard = lock(&n.project);
let core = guard
.graph
.get(n.id)
.and_then(block_core)
.ok_or(Error::State)?;
(n.project.clone(), core.track)
};
unsafe {
*out = match track {
Some(t) => make_node_borrowed(project, t),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_block_link`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_link(a: CHandle, b: CHandle) -> c_int {
guard(|| {
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
if !Arc::ptr_eq(&a.project, &b.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&a.project);
if guard.graph.link(a.id, b.id) {
Ok(())
} else {
Err(Error::Failed("already linked".to_string()))
}
})
}
/// `oaknode_block_unlink`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_unlink(a: CHandle, b: CHandle) -> c_int {
guard(|| {
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
if !Arc::ptr_eq(&a.project, &b.project) {
return Err(Error::Invalid);
}
let mut guard = lock(&a.project);
if guard.graph.unlink(a.id, b.id) {
Ok(())
} else {
Err(Error::Failed("not linked".to_string()))
}
})
}
/// `oaknode_block_are_linked`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_are_linked(
a: CHandle,
b: CHandle,
linked: *mut c_int,
) -> c_int {
guard(|| {
if linked.is_null() {
return Err(Error::Invalid);
}
let a = unsafe { node_ref(&a)? };
let b = unsafe { node_ref(&b)? };
let guard = lock(&a.project);
let linked_val = guard.graph.are_linked(a.id, b.id);
unsafe { *linked = linked_val as c_int };
Ok(())
})
}
/// `oaknode_block_get_link_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_link_count(
block: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
let guard = lock(&n.project);
let links = guard.graph.links_of(n.id);
unsafe { *count = links.len() as c_int };
Ok(())
})
}
/// `oaknode_block_get_link_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_block_get_link_at(
block: CHandle,
index: c_int,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&block)? };
if index < 0 {
return Err(Error::NotFound);
}
let guard = lock(&n.project);
let links = guard.graph.links_of(n.id);
let target = links.get(index as usize).copied().ok_or(Error::NotFound)?;
unsafe { *out = make_node_borrowed(n.project.clone(), target) };
Ok(())
})
}
// ---- Clip ---------------------------------------------------------
/// `oaknode_clip_get_media_in`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_get_media_in(
clip: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&clip)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<ClipBlockBehavior>())
.map(|c| &c.core)
.ok_or(Error::State)?;
unsafe {
*numerator = core.media_in.numerator() as c_int;
*denominator = core.media_in.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_clip_set_media_in`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_set_media_in(
clip: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&clip)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<ClipBlockBehavior>())
.map(|c| &mut c.core)
.ok_or(Error::State)?;
core.media_in = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
Ok(())
})
}
/// `oaknode_clip_get_speed`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_get_speed(clip: CHandle, speed: *mut f64) -> c_int {
guard(|| {
if speed.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&clip)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<ClipBlockBehavior>())
.map(|c| &c.core)
.ok_or(Error::State)?;
unsafe { *speed = core.speed };
Ok(())
})
}
/// `oaknode_clip_set_speed`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_set_speed(clip: CHandle, speed: f64) -> c_int {
guard(|| {
let n = unsafe { node_ref(&clip)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<ClipBlockBehavior>())
.map(|c| &mut c.core)
.ok_or(Error::State)?;
core.speed = speed;
Ok(())
})
}
/// `oaknode_clip_get_reverse`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_get_reverse(clip: CHandle, reverse: *mut c_int) -> c_int {
guard(|| {
if reverse.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&clip)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<ClipBlockBehavior>())
.map(|c| &c.core)
.ok_or(Error::State)?;
unsafe { *reverse = core.reversed as c_int };
Ok(())
})
}
/// `oaknode_clip_set_reverse`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_set_reverse(clip: CHandle, reverse: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&clip)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<ClipBlockBehavior>())
.map(|c| &mut c.core)
.ok_or(Error::State)?;
core.reversed = reverse != 0;
Ok(())
})
}
/// `oaknode_clip_get_maintain_audio_pitch`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_get_maintain_audio_pitch(
clip: CHandle,
maintain: *mut c_int,
) -> c_int {
guard(|| {
if maintain.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&clip)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<ClipBlockBehavior>())
.map(|c| &c.core)
.ok_or(Error::State)?;
unsafe { *maintain = core.maintain_audio_pitch as c_int };
Ok(())
})
}
/// `oaknode_clip_set_maintain_audio_pitch`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_set_maintain_audio_pitch(
clip: CHandle,
maintain: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&clip)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<ClipBlockBehavior>())
.map(|c| &mut c.core)
.ok_or(Error::State)?;
core.maintain_audio_pitch = maintain != 0;
Ok(())
})
}
/// `oaknode_clip_get_loop_mode`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_get_loop_mode(
clip: CHandle,
loop_mode: *mut c_int,
) -> c_int {
guard(|| {
if loop_mode.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&clip)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<ClipBlockBehavior>())
.map(|c| &c.core)
.ok_or(Error::State)?;
unsafe { *loop_mode = core.loop_mode };
Ok(())
})
}
/// `oaknode_clip_set_loop_mode`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_set_loop_mode(clip: CHandle, loop_mode: c_int) -> c_int {
guard(|| {
let n = unsafe { node_ref(&clip)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<ClipBlockBehavior>())
.map(|c| &mut c.core)
.ok_or(Error::State)?;
core.loop_mode = loop_mode;
Ok(())
})
}
/// `oaknode_clip_get_track_type`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_get_track_type(
clip: CHandle,
type_: *mut c_int,
) -> c_int {
guard(|| {
if type_.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&clip)? };
let kind = {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let core = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<ClipBlockBehavior>())
.map(|c| &c.core)
.ok_or(Error::State)?;
let track = core.track;
track
.and_then(|t| guard.graph.get(t))
.and_then(|te| te.behavior.as_any())
.and_then(|a| a.downcast_ref::<crate::track::TrackBehavior>())
.map(|tb| tb.kind.to_c())
.unwrap_or(-1)
};
unsafe { *type_ = kind };
Ok(())
})
}
// ---- Transition ----------------------------------------------------
/// `oaknode_transition_get_in_offset`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_get_in_offset(
transition: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&transition)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TransitionBlockBehavior>())
.ok_or(Error::State)?;
unsafe {
*numerator = t.in_offset.numerator() as c_int;
*denominator = t.in_offset.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_transition_get_out_offset`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_get_out_offset(
transition: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&transition)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TransitionBlockBehavior>())
.ok_or(Error::State)?;
unsafe {
*numerator = t.out_offset.numerator() as c_int;
*denominator = t.out_offset.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_transition_get_offset_center`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_get_offset_center(
transition: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
guard(|| {
if numerator.is_null() || denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&transition)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TransitionBlockBehavior>())
.ok_or(Error::State)?;
// Center = (length - in - out) / 2 from the in edge.
let length = t.core.length();
let center = (length - t.in_offset - t.out_offset) / oakcore_rs::Rational::new(2, 1);
unsafe {
*numerator = center.numerator() as c_int;
*denominator = center.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_transition_set_offset_center`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_set_offset_center(
transition: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&transition)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TransitionBlockBehavior>())
.ok_or(Error::State)?;
let center = oakcore_rs::Rational::new(numerator as i64, denominator as i64);
let length = t.core.length();
let remaining = (length - center * oakcore_rs::Rational::new(2, 1))
/ oakcore_rs::Rational::new(2, 1);
t.in_offset = remaining;
t.out_offset = remaining;
Ok(())
})
}
/// `oaknode_transition_set_offsets_and_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_set_offsets_and_length(
transition: CHandle,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&transition)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let t = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<TransitionBlockBehavior>())
.ok_or(Error::State)?;
t.in_offset = oakcore_rs::Rational::new(in_num as i64, in_den as i64);
t.out_offset = oakcore_rs::Rational::new(out_num as i64, out_den as i64);
t.core.set_length_and_media_out(t.in_offset + t.out_offset);
Ok(())
})
}
/// `oaknode_transition_is_dual`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_is_dual(
transition: CHandle,
dual: *mut c_int,
) -> c_int {
guard(|| {
if dual.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&transition)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
if entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TransitionBlockBehavior>())
.is_none()
{
return Err(Error::State);
}
let both =
guard
.graph
.is_input_connected(n.id, crate::block::transition_input::OUT_BLOCK, -1)
&& guard.graph.is_input_connected(
n.id,
crate::block::transition_input::IN_BLOCK,
-1,
);
unsafe { *dual = both as c_int };
Ok(())
})
}
/// `oaknode_transition_get_connected_out_block`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_get_connected_out_block(
transition: CHandle,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&transition)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
if entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TransitionBlockBehavior>())
.is_none()
{
return Err(Error::State);
}
let connected =
guard
.graph
.connected_output(n.id, crate::block::transition_input::OUT_BLOCK, -1);
unsafe {
*out = match connected {
Some(b) => make_node_borrowed(n.project.clone(), b),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_transition_get_connected_in_block`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_transition_get_connected_in_block(
transition: CHandle,
out: *mut CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&transition)? };
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
if entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<TransitionBlockBehavior>())
.is_none()
{
return Err(Error::State);
}
let connected =
guard
.graph
.connected_output(n.id, crate::block::transition_input::IN_BLOCK, -1);
unsafe {
*out = match connected {
Some(b) => make_node_borrowed(n.project.clone(), b),
None => CHandle::null(),
};
}
Ok(())
})
}
/// `oaknode_clip_add_cache_passthrough_from` — the render cache
/// handles are created per node at cache-init time (Phase 2
/// follow-up with bridge::render); the passthrough copies UUIDs,
/// which is a no-op for uncreated caches.
#[no_mangle]
pub unsafe extern "C" fn oaknode_clip_add_cache_passthrough_from(
clip: CHandle,
other: CHandle,
) -> c_int {
guard(|| {
let _c = unsafe { node_ref(&clip)? };
let _o = unsafe { node_ref(&other)? };
// Cache UUID passthrough is inert until the oakrender bridge
// creates per-node caches; the call is accepted as a no-op
// (matching the C++ flow where caches may be null).
Ok(())
})
}
}
// =====================================================================
// include/node/footage.h
// =====================================================================
/// `include/node/footage.h` exports. Footage handles are node handles
/// whose nodes carry [`crate::footage::FootageBehavior`].
pub mod footage {
use super::*;
use crate::footage::FootageBehavior;
use crate::node::NodeCore;
fn footage_of(n: &NodeRef) -> Result<crate::footage::FootageBehavior> {
let guard = lock(&n.project);
let entry = guard.graph.get(n.id).ok_or(Error::NotFound)?;
entry
.behavior
.as_any()
.and_then(|a| a.downcast_ref::<FootageBehavior>())
.map(|f| FootageBehavior {
filename: f.filename.clone(),
streams: f.streams.clone(),
proxy: f.proxy.clone(),
proxy_enabled: f.proxy_enabled,
proxy_state: f.proxy_state,
proxy_video_stream_index: f.proxy_video_stream_index,
proxy_preset_version: f.proxy_preset_version,
timestamp: f.timestamp,
decoder: f.decoder.clone(),
valid: f.valid,
cancel: f.cancel.clone(),
})
.ok_or(Error::State)
}
/// `oaknode_footage_create` (borrowed; node added to the project).
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_create(
project: CHandle,
filename: *const c_char,
) -> CHandle {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return CHandle::null(),
};
let filename = unsafe { cstr(filename) }.unwrap_or("").to_string();
let id = {
let mut guard = lock(&p);
guard
.graph
.add_node(NodeCore::new(), Box::new(FootageBehavior::new(&filename)))
};
make_node_borrowed(p, id)
}
/// `oaknode_footage_as_node`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_as_node(footage: CHandle) -> CHandle {
match unsafe { node_ref(&footage) } {
Ok(n) => make_node_borrowed(n.project.clone(), n.id),
Err(_) => CHandle::null(),
}
}
/// `oaknode_footage_filename` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_filename(
footage: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&footage) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
match footage_of(&n) {
Ok(f) => copy_string_out(&f.filename, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_footage_set_filename`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_set_filename(
footage: CHandle,
filename: *const c_char,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&footage)? };
let filename = unsafe { cstr(filename) }.ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
f.filename = filename.to_string();
Ok(())
})
}
/// `oaknode_footage_is_valid`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_is_valid(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.valid as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_timestamp`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_timestamp(
footage: CHandle,
out_timestamp: *mut i64,
) -> c_int {
guard(|| {
if out_timestamp.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&footage)? };
let f = footage_of(&n)?;
unsafe { *out_timestamp = f.timestamp };
Ok(())
})
}
/// `oaknode_footage_set_timestamp`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_set_timestamp(
footage: CHandle,
timestamp: i64,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&footage)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
f.timestamp = timestamp;
Ok(())
})
}
/// `oaknode_footage_decoder` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_decoder(
footage: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&footage) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
match footage_of(&n) {
Ok(f) => copy_string_out(&f.decoder, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_footage_total_stream_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_total_stream_count(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.total_stream_count() as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_video_stream_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_video_stream_count(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.video_stream_count() as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_audio_stream_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_audio_stream_count(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.audio_stream_count() as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_subtitle_stream_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_subtitle_stream_count(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.subtitle_stream_count() as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_duration`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_duration(
footage: CHandle,
out_numerator: *mut c_int,
out_denominator: *mut c_int,
) -> c_int {
guard(|| {
if out_numerator.is_null() || out_denominator.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&footage)? };
let f = footage_of(&n)?;
let d = f.duration();
unsafe {
*out_numerator = d.numerator() as c_int;
*out_denominator = d.denominator() as c_int;
}
Ok(())
})
}
/// `oaknode_footage_proxy_enabled`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_proxy_enabled(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.proxy_enabled as c_int,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_set_proxy_enabled`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_set_proxy_enabled(
footage: CHandle,
enabled: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&footage)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
f.proxy_enabled = enabled != 0;
Ok(())
})
}
/// `oaknode_footage_proxy_path` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_proxy_path(
footage: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let n = match unsafe { node_ref(&footage) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
match footage_of(&n) {
Ok(f) => copy_string_out(&f.proxy, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_footage_proxy_state`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_proxy_state(footage: CHandle) -> c_int {
match unsafe { node_ref(&footage) } {
Ok(n) => match footage_of(&n) {
Ok(f) => f.proxy_state,
Err(e) => e.code(),
},
Err(_) => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_footage_set_proxy`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_set_proxy(
footage: CHandle,
path: *const c_char,
state: c_int,
video_stream_index: c_int,
preset_version: c_int,
enabled: c_int,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&footage)? };
let path = if path.is_null() {
""
} else {
unsafe { cstr(path) }.ok_or(Error::Invalid)?
};
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
f.set_proxy(
path,
state,
video_stream_index,
preset_version,
enabled != 0,
);
Ok(())
})
}
/// `oaknode_footage_clear_proxy`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_clear_proxy(footage: CHandle) -> c_int {
guard(|| {
let n = unsafe { node_ref(&footage)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
f.clear_proxy();
Ok(())
})
}
/// `oaknode_footage_get_video_params`: the `index`th video stream's
/// params as a NEW owned oakcommon handle
/// (`// CPP-PARITY: src/node/c_api/footage.cpp:327`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_get_video_params(
footage: CHandle,
index: c_int,
out: *mut std::ffi::c_void,
) -> c_int {
guard(|| {
if out.is_null() || index < 0 {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&footage)? };
let params = with_graph_read(&n.project, |g| {
g.get(n.id).and_then(|e| {
e.behavior
.as_any()
.and_then(|a| a.downcast_ref::<FootageBehavior>())
.and_then(|f| f.video_params(index as usize))
})
})
.ok_or(Error::NotFound)?;
let handle = super::videoparams_handle_from(&params).ok_or(Error::NoMem)?;
unsafe { *(out as *mut crate::handle::CHandle) = handle };
Ok(())
})
}
/// `oaknode_footage_set_video_params`: replace the `index`th video
/// stream's params from an oakcommon handle
/// (`// CPP-PARITY: footage.cpp:348`). `OAKNODE_E_NOT_FOUND` when the
/// footage has no such video stream (the C++ writes into the params
/// input array regardless; documented divergence).
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_set_video_params(
footage: CHandle,
index: c_int,
params: *const std::ffi::c_void,
) -> c_int {
guard(|| {
if params.is_null() {
return Err(Error::Invalid);
}
let handle = unsafe { (*(params as *const crate::handle::CHandle)).clone() };
if handle.ctx.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&footage)? };
let read = super::videoparams_from_handle(handle).ok_or(Error::Invalid)?;
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
let mut seen = 0usize;
for stream in f.streams.iter_mut().filter(|s| s.is_video) {
if seen == index as usize {
stream.video = Some(read);
return Ok(());
}
seen += 1;
}
Err(Error::NotFound)
})
}
/// `oaknode_footage_get_video_length`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_get_video_length(
footage: CHandle,
out_num: *mut i64,
out_den: *mut i64,
) -> c_int {
guard(|| {
if out_num.is_null() || out_den.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&footage)? };
let f = footage_of(&n)?;
let len = f.video_length();
unsafe {
*out_num = len.numerator();
*out_den = len.denominator();
}
Ok(())
})
}
/// `oaknode_footage_set_cancel_atom`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_set_cancel_atom(
footage: CHandle,
atom: CHandle,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&footage)? };
let mut guard = lock(&n.project);
let entry = guard.graph.get_mut(n.id).ok_or(Error::NotFound)?;
let f = entry
.behavior
.as_any_mut()
.and_then(|a| a.downcast_mut::<FootageBehavior>())
.ok_or(Error::State)?;
// The cancel atom is an opaque oakrender handle; the shared
// cancellation flag is set when it is present.
f.set_cancel(!atom.ctx.is_null());
Ok(())
})
}
}
// =====================================================================
// include/node/colormanager.h
// =====================================================================
/// `include/node/colormanager.h` exports. The manager handle boxes a
/// [`crate::colormanager::ColorManager`] directly (not a graph node).
/// Config-dependent queries report `E_STATE` until a config is loaded;
/// the listings reflect the manager's state (without liboakrender the
/// lists hold the built-in defaults).
pub mod colormanager {
use super::*;
use crate::colormanager::ColorManager;
/// `oaknode_colormanager_init` (owned manager bound to a project).
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_init(project: CHandle) -> CHandle {
let _ = unsafe { project_arc(&project) };
// The manager is process/project state; the project binding is
// recorded but the manager object is standalone and shared
// through a mutex.
crate::handle::make_owned(std::sync::Mutex::new(ColorManager::new()))
}
/// `oaknode_colormanager_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_free(manager: *mut CHandle) {
if manager.is_null() || unsafe { (*manager).ctx.is_null() } {
return;
}
let h = unsafe { (*manager).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*manager).ctx = std::ptr::null_mut() };
}
/// `oaknode_colormanager_wrap_borrowed` — the native manager pointer
/// is not a Rust object; the borrowed box wraps a placeholder.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_wrap_borrowed(
native_manager: *mut std::ffi::c_void,
) -> CHandle {
if native_manager.is_null() {
return CHandle::null();
}
crate::handle::make_owned(ColorManager::new())
}
/// `oaknode_colormanager_initialize`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_initialize(manager: CHandle) -> c_int {
guard(|| {
let mut guard = manager_guard(&manager)?;
guard.initialize()
})
}
/// `oaknode_colormanager_set_up_default_config`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_set_up_default_config() -> c_int {
guard(|| {
// The process-wide default config state lives with the
// managers; nothing global to do without oakrender.
Ok(())
})
}
/// `oaknode_colormanager_get_config_filename`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_config_filename(
manager: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| m.config_filename.clone()) {
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_set_config_filename`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_set_config_filename(
manager: CHandle,
filename: *const c_char,
) -> c_int {
guard(|| {
let filename = unsafe { cstr(filename) }.ok_or(Error::Invalid)?.to_string();
manager_mut(&manager, |m| m.config_filename = filename)
})
}
/// `oaknode_colormanager_update_config_from_filename`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_update_config_from_filename(
manager: CHandle,
) -> c_int {
guard(|| {
let mut guard = manager_guard(&manager)?;
guard.update_config_from_filename()
})
}
/// `oaknode_colormanager_get_default_input_color_space`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_default_input_color_space(
manager: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| m.default_input_space.clone()) {
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_set_default_input_color_space`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_set_default_input_color_space(
manager: CHandle,
colorspace: *const c_char,
) -> c_int {
guard(|| {
let colorspace = unsafe { cstr(colorspace) }
.ok_or(Error::Invalid)?
.to_string();
manager_mut(&manager, |m| m.default_input_space = colorspace)
})
}
/// `oaknode_colormanager_get_reference_color_space`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_reference_color_space(
manager: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| m.reference_space.clone()) {
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_compliant_color_space` — requires a
/// loaded config.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_compliant_color_space(
manager: CHandle,
colorspace: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if !m.config_loaded {
return String::new();
}
let colorspace = unsafe { cstr(colorspace) }.unwrap_or("");
if m.list_colorspaces().iter().any(|c| c == colorspace) {
colorspace.to_string()
} else {
m.default_input_space.clone()
}
}) {
Ok(s) => {
if s.is_empty() {
crate::error::OAKNODE_E_STATE
} else {
copy_string_out(&s, buf, buf_size)
}
}
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_colorspace_for_ffmpeg_tags` — the tag
/// mapping requires OCIO (E_STATE without a loaded config).
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_colorspace_for_ffmpeg_tags(
manager: CHandle,
_primaries: c_int,
_trc: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let loaded = manager_string(&manager, |m| m.config_loaded.to_string());
match loaded {
Ok(s) if s == "true" => {
// Unknown tags -> default input (empty result size 1).
match manager_string(&manager, |m| m.default_input_space.clone()) {
Ok(space) => copy_string_out(&space, buf, buf_size),
Err(e) => e.code(),
}
}
_ => crate::error::OAKNODE_E_STATE,
}
}
/// `oaknode_colormanager_get_display_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_display_count(
manager: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let guard = manager_guard(&manager)?;
if !guard.config_loaded {
return Err(Error::State);
}
unsafe { *count = guard.list_displays().len() as c_int };
Ok(())
})
}
/// `oaknode_colormanager_get_display_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_display_at(
manager: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if !m.config_loaded {
return String::new();
}
m.list_displays()
.get(index as usize)
.cloned()
.unwrap_or_default()
}) {
Ok(s) if s.is_empty() => crate::error::OAKNODE_E_NOT_FOUND,
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_default_display`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_default_display(
manager: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if m.config_loaded {
m.default_display.clone()
} else {
String::new()
}
}) {
Ok(s) if s.is_empty() => crate::error::OAKNODE_E_STATE,
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_view_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_view_count(
manager: CHandle,
_display: *const c_char,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let guard = manager_guard(&manager)?;
if !guard.config_loaded {
return Err(Error::State);
}
unsafe { *count = guard.list_views("").len() as c_int };
Ok(())
})
}
/// `oaknode_colormanager_get_view_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_view_at(
manager: CHandle,
_display: *const c_char,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if !m.config_loaded {
return String::new();
}
m.list_views("")
.get(index as usize)
.cloned()
.unwrap_or_default()
}) {
Ok(s) if s.is_empty() => crate::error::OAKNODE_E_NOT_FOUND,
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_default_view`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_default_view(
manager: CHandle,
_display: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if m.config_loaded {
m.default_view.clone()
} else {
String::new()
}
}) {
Ok(s) if s.is_empty() => crate::error::OAKNODE_E_STATE,
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_look_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_look_count(
manager: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let guard = manager_guard(&manager)?;
if !guard.config_loaded {
return Err(Error::State);
}
unsafe { *count = guard.list_looks().len() as c_int };
Ok(())
})
}
/// `oaknode_colormanager_get_look_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_look_at(
manager: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if !m.config_loaded {
return String::new();
}
m.list_looks()
.get(index as usize)
.cloned()
.unwrap_or_default()
}) {
Ok(s) if s.is_empty() => crate::error::OAKNODE_E_NOT_FOUND,
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_colorspace_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_colorspace_count(
manager: CHandle,
count: *mut c_int,
) -> c_int {
guard(|| {
if count.is_null() {
return Err(Error::Invalid);
}
let guard = manager_guard(&manager)?;
if !guard.config_loaded {
return Err(Error::State);
}
unsafe { *count = guard.list_colorspaces().len() as c_int };
Ok(())
})
}
/// `oaknode_colormanager_get_colorspace_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_colorspace_at(
manager: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
match manager_string(&manager, |m| {
if !m.config_loaded {
return String::new();
}
m.list_colorspaces()
.get(index as usize)
.cloned()
.unwrap_or_default()
}) {
Ok(s) if s.is_empty() => crate::error::OAKNODE_E_NOT_FOUND,
Ok(s) => copy_string_out(&s, buf, buf_size),
Err(e) => e.code(),
}
}
/// `oaknode_colormanager_get_default_luma_coefs` — requires a loaded
/// config; without OCIO the Rec.709 coefficients are returned.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_default_luma_coefs(
manager: CHandle,
rgb: *mut f64,
) -> c_int {
guard(|| {
if rgb.is_null() {
return Err(Error::Invalid);
}
let guard = manager_guard(&manager)?;
if !guard.config_loaded {
return Err(Error::State);
}
unsafe {
*rgb.add(0) = 0.2126;
*rgb.add(1) = 0.7152;
*rgb.add(2) = 0.0722;
}
Ok(())
})
}
/// `oaknode_colormanager_get_compliant_color_transform`: a copy of
/// `transform` whose display/view/look (or output colorspace) is
/// clamped to what the active config offers
/// (`// CPP-PARITY: src/node/c_api/colormanager.cpp:383`,
/// `src/node/src/color/colormanager/colormanager.cpp:241`). Without
/// OCIO the config lists hold the built-in defaults, so the clamp is
/// identity unless a field names something the defaults lack.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_compliant_color_transform(
manager: CHandle,
transform: crate::handle::CHandle,
force_display: c_int,
out: *mut crate::handle::CHandle,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let mut guard = manager_guard(&manager)?;
if !guard.is_loaded() {
return Err(Error::State);
}
// Read the transform's fields (E_INVALID when oakcommon is
// unavailable — the C++ `get_native` NULL path).
let is_display = crate::bridge::common::colortransform_is_display(transform.clone())
.ok_or(Error::Invalid)?;
let (display, view, look, output) = if is_display || force_display != 0 {
(
crate::bridge::common::colortransform_get_display(transform.clone())
.ok_or(Error::Invalid)?,
crate::bridge::common::colortransform_get_view(transform.clone())
.ok_or(Error::Invalid)?,
crate::bridge::common::colortransform_get_look(transform.clone())
.ok_or(Error::Invalid)?,
String::new(),
)
} else {
(
String::new(),
String::new(),
String::new(),
crate::bridge::common::colortransform_get_output(transform.clone())
.ok_or(Error::Invalid)?,
)
};
drop(guard);
// Clamp against the active config (C++
// `get_compliant_color_space`).
let handle = if is_display || force_display != 0 {
let guard = manager_guard(&manager)?;
let displays = guard.list_displays();
let display = if displays.contains(&display) {
display
} else {
guard.default_display.clone()
};
let views = guard.list_views(&display);
let view = if views.contains(&view) {
view
} else {
guard.default_view.clone()
};
let looks = guard.list_looks();
let look = if looks.contains(&look) {
look
} else {
String::new()
};
crate::bridge::common::colortransform_init_display(&display, &view, &look)
} else {
let guard = manager_guard(&manager)?;
let spaces = guard.list_colorspaces();
let output = if spaces.contains(&output) {
output
} else {
guard.default_input_space.clone()
};
crate::bridge::common::colortransform_init_output(&output)
};
let handle = handle.ok_or(Error::NoMem)?;
unsafe { *out = handle };
Ok(())
})
}
/// Read a manager field through the shared lock.
fn manager_string(
manager: &CHandle,
f: impl FnOnce(&ColorManager) -> String,
) -> Result<String> {
let guard = manager_guard(manager)?;
Ok(f(&guard))
}
/// Mutate a manager field through the shared lock.
fn manager_mut(manager: &CHandle, f: impl FnOnce(&mut ColorManager)) -> Result<()> {
let mut guard = manager_guard(manager)?;
f(&mut guard);
Ok(())
}
/// Lock the shared manager box (the handle boxes `Mutex<ColorManager>`).
fn manager_guard(manager: &CHandle) -> Result<std::sync::MutexGuard<'_, ColorManager>> {
let m = unsafe { crate::handle::get::<std::sync::Mutex<ColorManager>>(manager) }
.ok_or(Error::Invalid)?;
Ok(m.lock().unwrap_or_else(|e| e.into_inner()))
}
/// `oaknode_colormanager_get_native`: borrowed access to the
/// underlying C++ manager — a C++-only bridge export with no Rust
/// counterpart. Returns NULL (the contract is NULL-safe); the Rust
/// manager object is reachable only through the handle.
#[no_mangle]
pub unsafe extern "C" fn oaknode_colormanager_get_native(
_manager: CHandle,
) -> *mut std::ffi::c_void {
std::ptr::null_mut()
}
}
// =====================================================================
// include/node/traverser.h
// =====================================================================
/// `include/node/traverser.h` exports: database generation over a time
/// range and database row enumeration. The traverser handle boxes a
/// [`crate::traverser::Traverser`]; the database handle boxes a
/// [`crate::traverser::ValueDatabase`].
pub mod traverser {
use super::*;
use crate::traverser::{EvalRequest, Traverser, ValueDatabase};
/// `oaknode_traverser_init`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_init() -> CHandle {
crate::handle::make_owned(Traverser::new())
}
/// `oaknode_traverser_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_free(traverser: *mut CHandle) {
if traverser.is_null() || unsafe { (*traverser).ctx.is_null() } {
return;
}
let h = unsafe { (*traverser).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*traverser).ctx = std::ptr::null_mut() };
}
/// `oaknode_traverser_generate_database`: evaluate `node` at the
/// range's in-time and collect its input row + output table into a
/// database keyed by input id (a single-frame database; the C++
/// walks the whole range — the frame iterator lands with the
/// traverser's range stepping, `// CPP-PARITY: traverser.cpp`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_generate_database(
traverser: CHandle,
node: CHandle,
in_num: i64,
in_den: i64,
out_num: i64,
out_den: i64,
out_db: *mut CHandle,
) -> c_int {
guard(|| {
if out_db.is_null() {
return Err(Error::Invalid);
}
let n = unsafe { node_ref(&node)? };
let _ = (in_num, in_den, out_num, out_den);
let time = oakcore_rs::Rational::new(in_num, in_den);
let mut hooks = NoopHooks;
let table = {
// The handle is an opaque engine token; each pass runs
// on a fresh engine (the pass is stateless).
let _ =
unsafe { crate::handle::get::<Traverser>(&traverser) }.ok_or(Error::Invalid)?;
let guard = lock(&n.project);
let request = EvalRequest::new(n.id, time);
let mut engine = Traverser::new();
engine.evaluate(&guard.graph, &request, &mut hooks)?
};
// Build the database: one row per evaluated input value.
let mut rows: Vec<(String, Vec<(crate::value::ValueType, NodeValue)>)> = Vec::new();
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
for (from, input_id, _element) in
with_graph_read(&n.project, |g| g.output_connections(n.id))
{
let _ = from;
if seen.insert(input_id.clone()) {
rows.push((input_id, Vec::new()));
}
}
// The root's output table also feeds the database under the
// "" key (the C++ `NodeValueDatabase` rows are the evaluated
// values per input of the root).
let root_row: Vec<(crate::value::ValueType, NodeValue)> = table
.rows()
.iter()
.map(|(t, v, _)| (*t, v.clone()))
.collect();
let mut db_rows = rows;
db_rows.push(("".to_string(), root_row));
let db = ValueDatabase { rows: db_rows };
unsafe { *out_db = crate::handle::make_owned(db) };
Ok(())
})
}
/// No-op hooks for offline database generation.
struct NoopHooks;
impl crate::traverser::RenderHooks for NoopHooks {}
/// `oaknode_traverser_database_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_database_free(db: *mut CHandle) {
if db.is_null() || unsafe { (*db).ctx.is_null() } {
return;
}
let h = unsafe { (*db).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*db).ctx = std::ptr::null_mut() };
}
/// `oaknode_traverser_database_row_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_database_row_count(
db: CHandle,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let d = unsafe { crate::handle::get::<ValueDatabase>(&db) }.ok_or(Error::Invalid)?;
unsafe { *out_count = d.rows.len() as c_int };
Ok(())
})
}
/// `oaknode_traverser_database_row_key_at` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_database_row_key_at(
db: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let d = match unsafe { crate::handle::get::<ValueDatabase>(&db) } {
Some(d) => d,
None => return crate::error::OAKNODE_E_INVALID,
};
match d.rows.get(index as usize) {
Some((key, _)) => copy_string_out(key, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_traverser_database_row_value_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_database_row_value_count(
db: CHandle,
key: *const c_char,
out_count: *mut c_int,
) -> c_int {
guard(|| {
if out_count.is_null() {
return Err(Error::Invalid);
}
let d = unsafe { crate::handle::get::<ValueDatabase>(&db) }.ok_or(Error::Invalid)?;
let key = unsafe { cstr(key) }.ok_or(Error::Invalid)?;
let row = d
.rows
.iter()
.find(|(k, _)| k == key)
.ok_or(Error::NotFound)?;
unsafe { *out_count = row.1.len() as c_int };
Ok(())
})
}
/// `oaknode_traverser_database_value_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_database_value_at(
db: CHandle,
key: *const c_char,
index: c_int,
out: *mut OakNodeValue,
) -> c_int {
guard(|| {
if out.is_null() {
return Err(Error::Invalid);
}
let d = unsafe { crate::handle::get::<ValueDatabase>(&db) }.ok_or(Error::Invalid)?;
let key = unsafe { cstr(key) }.ok_or(Error::Invalid)?;
let row = d
.rows
.iter()
.find(|(k, _)| k == key)
.ok_or(Error::NotFound)?;
let (ty, value) = row.1.get(index as usize).ok_or(Error::NotFound)?;
let pod = OakNodeValue::from_node_value(*ty, value)?;
unsafe { *out = pod };
Ok(())
})
}
/// `oaknode_traverser_database_value_string_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_traverser_database_value_string_at(
db: CHandle,
key: *const c_char,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let d = match unsafe { crate::handle::get::<ValueDatabase>(&db) } {
Some(d) => d,
None => return crate::error::OAKNODE_E_INVALID,
};
let key = match unsafe { cstr(key) } {
Some(k) => k,
None => return crate::error::OAKNODE_E_INVALID,
};
let row = match d.rows.iter().find(|(k, _)| k == key) {
Some(r) => r,
None => return crate::error::OAKNODE_E_NOT_FOUND,
};
let (ty, value) = match row.1.get(index as usize) {
Some(v) => v,
None => return crate::error::OAKNODE_E_NOT_FOUND,
};
let s = crate::serializer::value_to_string(*ty, value, false);
copy_string_out(&s, buf, buf_size)
}
}
// =====================================================================
// include/node/serializer.h
// =====================================================================
/// `include/node/serializer.h` exports: in-memory node-graph copy/paste
/// via XML. SaveData holds the nodes to serialize + per-node properties;
/// LoadData holds the created nodes + promised connections + properties.
pub mod serializer {
use super::*;
use crate::serializer::{XmlRead, XmlWrite};
/// Serializer result codes (include/node/serializer.h).
const SERIALIZER_OK: c_int = 0;
const SERIALIZER_FILE_ERROR: c_int = 4;
const SERIALIZER_XML_ERROR: c_int = 5;
/// Save descriptor (C++ `ProjectSerializer::SaveData`).
pub struct SaveData {
/// Load type (`OAKNODE_SERIALIZER_LOAD_*`).
pub load_type: c_int,
/// Context project (borrowed).
pub project: Option<ProjectArc>,
/// Restrict to these nodes (None = all).
pub only_serialize_nodes: Option<Vec<NodeId>>,
/// Per-node (key, value) properties.
pub properties: std::collections::HashMap<(NodeId, String), String>,
}
/// Load result (C++ `ProjectSerializer::LoadData`).
pub struct LoadData {
/// Created nodes (owned by the caller) as (project, id).
pub nodes: Vec<(ProjectArc, NodeId)>,
/// Promised connections (output, input, input_id, element).
pub connections: Vec<(NodeId, NodeId, String, i32)>,
/// Per-node properties.
pub properties: std::collections::HashMap<(NodeId, String), String>,
}
/// `oaknode_serializer_initialize`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_initialize() -> c_int {
// The factory registry is lazily built; initializing is a no-op
// that guarantees it exists.
guard(|| {
let _ = crate::factory::Factory::global();
Ok(())
})
}
/// `oaknode_serializer_shutdown` — no-op (nothing global to release).
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_shutdown() {}
/// `oaknode_serializer_savedata_create`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_savedata_create(
load_type: c_int,
project: CHandle,
) -> CHandle {
let project = unsafe { project_arc(&project) }.ok();
crate::handle::make_owned(SaveData {
load_type,
project,
only_serialize_nodes: None,
properties: std::collections::HashMap::new(),
})
}
/// `oaknode_serializer_savedata_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_savedata_free(save_data: *mut CHandle) {
if save_data.is_null() || unsafe { (*save_data).ctx.is_null() } {
return;
}
let h = unsafe { (*save_data).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*save_data).ctx = std::ptr::null_mut() };
}
/// `oaknode_serializer_savedata_set_nodes`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_savedata_set_nodes(
save_data: CHandle,
nodes: *const CHandle,
count: c_int,
) -> c_int {
guard(|| {
if nodes.is_null() || count < 0 {
return Err(Error::Invalid);
}
let sd = unsafe { crate::handle::get::<SaveData>(&save_data) }.ok_or(Error::Invalid)?;
let mut ids = Vec::new();
for i in 0..count as usize {
let child = unsafe { (*nodes.add(i)).clone() };
let n = unsafe { node_ref(&child)? };
ids.push(n.id);
}
// The box is shared; mutate through the raw pointer.
unsafe {
let rb = save_data.ctx as *mut crate::handle::RefBox<SaveData>;
(*rb).value.only_serialize_nodes = Some(ids);
}
Ok(())
})
}
/// `oaknode_serializer_savedata_set_property`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_savedata_set_property(
save_data: CHandle,
node: CHandle,
key: *const c_char,
value: *const c_char,
) -> c_int {
guard(|| {
let n = unsafe { node_ref(&node)? };
let key = unsafe { cstr(key) }.ok_or(Error::Invalid)?;
let value = unsafe { cstr(value) }.ok_or(Error::Invalid)?;
unsafe {
let rb = save_data.ctx as *mut crate::handle::RefBox<SaveData>;
(*rb)
.value
.properties
.insert((n.id, key.to_string()), value.to_string());
}
Ok(())
})
}
/// `oaknode_serializer_save_to_xml` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_save_to_xml(
save_data: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let sd = match unsafe { crate::handle::get::<SaveData>(&save_data) } {
Some(sd) => sd,
None => return crate::error::OAKNODE_E_INVALID,
};
let xml = match save_to_xml_inner(sd) {
Ok(x) => x,
Err(e) => return e.code(),
};
copy_string_out(&xml, buf, buf_size)
}
/// Serialize the save data to an XML string.
fn save_to_xml_inner(sd: &SaveData) -> Result<String> {
let project = sd.project.as_ref().ok_or(Error::State)?;
let mut writer = crate::serializer::XmlWriterBridge::new().ok_or(Error::Failed(
"oakcommon XML writer unavailable".to_string(),
))?;
writer.start_element("project");
writer.attribute("version", "1");
let guard = lock(project);
writer.start_element("nodes");
let ids: Vec<NodeId> = match &sd.only_serialize_nodes {
Some(list) => list.clone(),
None => guard.graph.node_ids(),
};
for id in ids {
let entry = guard.graph.get(id).ok_or(Error::NotFound)?;
writer.start_element("node");
let type_id = entry.behavior.type_id().to_string();
let connections: Vec<(NodeId, String, i32)> = guard
.graph
.output_connections_all()
.into_iter()
.filter(|(_, to, _, _)| *to == id)
.map(|(from, _, input, element)| (from, input, element))
.collect();
crate::serializer::save_node(&mut writer, &entry.core, id, &type_id, &connections)?;
// Serialize per-node properties as text elements.
let mut props: Vec<(&String, &String)> = sd
.properties
.iter()
.filter(|((n, _), _)| *n == id)
.map(|((_, k), v)| (k, v))
.collect();
props.sort_by(|a, b| a.0.cmp(b.0));
if !props.is_empty() {
writer.start_element("properties");
for (k, v) in props {
writer.text_element(k, v);
}
writer.end_element(); // properties
}
writer.end_element(); // node
}
writer.end_element(); // nodes
writer.end_element(); // project
Ok(writer.output())
}
/// `oaknode_serializer_load_from_xml`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_load_from_xml(
project: CHandle,
xml: *const c_char,
_load_type: c_int,
out_result: *mut c_int,
out_load_data: *mut CHandle,
details_buf: *mut c_char,
details_buf_size: c_int,
) -> c_int {
guard(|| {
if out_result.is_null() {
return Err(Error::Invalid);
}
let xml = unsafe { cstr(xml) }.ok_or(Error::Invalid)?;
let load_data = match load_from_xml_inner(xml) {
Ok(ld) => ld,
Err(_) => {
unsafe { *out_result = SERIALIZER_XML_ERROR };
if !out_load_data.is_null() {
unsafe { *out_load_data = CHandle::null() };
}
if !details_buf.is_null() && details_buf_size > 0 {
unsafe { *details_buf = 0 };
}
return Ok(());
}
};
unsafe { *out_result = SERIALIZER_OK };
if !out_load_data.is_null() {
unsafe { *out_load_data = crate::handle::make_owned(load_data) };
}
Ok(())
})
}
/// Parse XML into a [`LoadData`] (nodes created in fresh scratch
/// projects, owned by the caller).
fn load_from_xml_inner(xml: &str) -> Result<LoadData> {
let project = crate::serializer::load(xml)?;
let guard = lock(&project);
let mut nodes = Vec::new();
for id in guard.graph.node_ids() {
nodes.push((project.clone(), id));
}
Ok(LoadData {
nodes,
connections: Vec::new(),
properties: std::collections::HashMap::new(),
})
}
/// `oaknode_serializer_loaddata_free`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_loaddata_free(load_data: *mut CHandle) {
if load_data.is_null() || unsafe { (*load_data).ctx.is_null() } {
return;
}
let h = unsafe { (*load_data).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*load_data).ctx = std::ptr::null_mut() };
}
/// `oaknode_serializer_loaddata_node_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_loaddata_node_count(load_data: CHandle) -> c_int {
match unsafe { crate::handle::get::<LoadData>(&load_data) } {
Some(ld) => ld.nodes.len() as c_int,
None => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_serializer_loaddata_node_at` (borrowed).
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_loaddata_node_at(
load_data: CHandle,
index: c_int,
) -> CHandle {
let ld = match unsafe { crate::handle::get::<LoadData>(&load_data) } {
Some(ld) => ld,
None => return CHandle::null(),
};
match ld.nodes.get(index as usize) {
Some((project, id)) => make_node_borrowed(project.clone(), *id),
None => CHandle::null(),
}
}
/// `oaknode_serializer_loaddata_get_property` (two-stage).
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_loaddata_get_property(
load_data: CHandle,
node: CHandle,
key: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
let ld = match unsafe { crate::handle::get::<LoadData>(&load_data) } {
Some(ld) => ld,
None => return crate::error::OAKNODE_E_INVALID,
};
let n = match unsafe { node_ref(&node) } {
Ok(n) => n,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let key = match unsafe { cstr(key) } {
Some(k) => k,
None => return crate::error::OAKNODE_E_INVALID,
};
match ld.properties.get(&(n.id, key.to_string())) {
Some(v) => copy_string_out(v, buf, buf_size),
None => crate::error::OAKNODE_E_NOT_FOUND,
}
}
/// `oaknode_serializer_loaddata_connection_count`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_loaddata_connection_count(
load_data: CHandle,
) -> c_int {
match unsafe { crate::handle::get::<LoadData>(&load_data) } {
Some(ld) => ld.connections.len() as c_int,
None => crate::error::OAKNODE_E_INVALID,
}
}
/// `oaknode_serializer_loaddata_connection_at`.
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_loaddata_connection_at(
load_data: CHandle,
index: c_int,
out_output_node: *mut CHandle,
out_input_node: *mut CHandle,
input_id_buf: *mut c_char,
input_id_buf_size: c_int,
out_element: *mut c_int,
) -> c_int {
guard(|| {
if out_output_node.is_null() || out_input_node.is_null() || out_element.is_null() {
return Err(Error::Invalid);
}
let ld = unsafe { crate::handle::get::<LoadData>(&load_data) }.ok_or(Error::Invalid)?;
let (out_id, in_id, input_id, element) =
ld.connections.get(index as usize).ok_or(Error::NotFound)?;
// Resolve node handles from the load-data's nodes.
let out = ld
.nodes
.iter()
.find(|(_, id)| id == out_id)
.map(|(p, id)| make_node_borrowed(p.clone(), *id))
.unwrap_or_else(CHandle::null);
let inn = ld
.nodes
.iter()
.find(|(_, id)| id == in_id)
.map(|(p, id)| make_node_borrowed(p.clone(), *id))
.unwrap_or_else(CHandle::null);
unsafe {
*out_output_node = out;
*out_input_node = inn;
*out_element = *element;
}
if !input_id_buf.is_null() {
copy_string_out(input_id, input_id_buf, input_id_buf_size);
}
Ok(())
})
}
/// Report a serializer failure the C++ `report_serializer_result` way:
/// write the details string (two-stage) and return the required size
/// when a buffer was provided, else `OAKNODE_E_FAILED`
/// (`// CPP-PARITY: src/node/c_api/serializer.cpp`).
fn report_serializer_failure(msg: &str, details: *mut c_char, details_size: c_int) -> c_int {
let needed = msg.len() + 1;
if !details.is_null() && details_size > 0 {
copy_string_out(msg, details, details_size);
needed as c_int
} else {
crate::error::OAKNODE_E_FAILED
}
}
/// `oaknode_serializer_save_to_file`: serialize `project` to XML and
/// write it to `filename`. OVEC compression (`use_compression`) is
/// not supported — always plain XML. Result code semantics per
/// include/node/serializer.h (`// CPP-PARITY: serializer.cpp:394`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_save_to_file(
project: CHandle,
filename: *const c_char,
_use_compression: c_int,
out_code: *mut c_int,
details: *mut c_char,
details_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let filename = match unsafe { cstr(filename) } {
Some(s) => s,
None => return crate::error::OAKNODE_E_INVALID,
};
let xml = match save_to_xml_inner(&SaveData {
load_type: 0,
project: Some(p),
only_serialize_nodes: None,
properties: std::collections::HashMap::new(),
}) {
Ok(x) => x,
Err(_) => {
if !out_code.is_null() {
unsafe { *out_code = SERIALIZER_XML_ERROR };
}
return report_serializer_failure("serialization failed", details, details_size);
}
};
match std::fs::write(filename, xml) {
Ok(()) => {
if !out_code.is_null() {
unsafe { *out_code = SERIALIZER_OK };
}
crate::error::OAKNODE_OK
}
Err(e) => {
if !out_code.is_null() {
unsafe { *out_code = SERIALIZER_FILE_ERROR };
}
report_serializer_failure(&e.to_string(), details, details_size)
}
}
}
/// `oaknode_serializer_load_from_file`: read `filename` and load it
/// into `project` (the C++ `ProjectSerializer::load` populates the
/// given project; `// CPP-PARITY: serializer.cpp:419`).
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_load_from_file(
project: CHandle,
filename: *const c_char,
out_code: *mut c_int,
details: *mut c_char,
details_size: c_int,
) -> c_int {
let p = match unsafe { project_arc(&project) } {
Ok(p) => p,
Err(_) => return crate::error::OAKNODE_E_INVALID,
};
let filename = match unsafe { cstr(filename) } {
Some(s) => s,
None => return crate::error::OAKNODE_E_INVALID,
};
let xml = match std::fs::read_to_string(filename) {
Ok(x) => x,
Err(e) => {
if !out_code.is_null() {
unsafe { *out_code = SERIALIZER_FILE_ERROR };
}
return report_serializer_failure(&e.to_string(), details, details_size);
}
};
match crate::serializer::load(&xml) {
Ok(loaded) => {
// Move the loaded graph into the given project.
let mut dst = lock(&p);
let mut src = lock(&loaded);
dst.graph.transfer_all(&mut src.graph);
if !out_code.is_null() {
unsafe { *out_code = SERIALIZER_OK };
}
crate::error::OAKNODE_OK
}
Err(_) => {
if !out_code.is_null() {
unsafe { *out_code = SERIALIZER_XML_ERROR };
}
report_serializer_failure("XML parse error", details, details_size)
}
}
}
}