refactor: drop internal bridge/ffi layers; exporter family lands

Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are
gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/
oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain
Rust, CHandle marshalling shrinks to the oakengine boundary, and tests
call the Rust APIs directly (pure C-ABI wrapper tests removed where
the domain layer already covers the behavior).

exporter.h family implemented: oakengine_export_render (CLI contract),
oakengine_export_render_with_params (was a stub), last_error and
progress callback; synchronous path reuses task_create_export +
start_sync. Fixes on the way: oaktask video ticket self-deadlock,
audio params dropped on the export path, codec encoder AAC slicing and
H.264 time base. Real-mp4 tests cover both entry points, progress and
the illegal-argument matrix.

Also: oakstorage session maps null project handles to None (version-
info path), configstore test double literal 3.14 -> 3.15 (clippy PI
lint), oakaudio output callback scratch buffer + env-aware P1 test,
cli media round-trip test uses a generated 16-frame clip (no more
minute-long debug runs).
This commit is contained in:
2026-08-16 00:33:45 +08:00
parent 2248be8567
commit ab1a2e9c7b
293 changed files with 27826 additions and 90128 deletions
-32
View File
@@ -1,32 +0,0 @@
// 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/>.
//! oakcodec C ABI calls (footage probing) — now direct Rust calls into
//! the oakcodec crate (single-lib unification, see
//! `docs/zh/plans/riir/single-lib.md`).
use std::ffi::c_char;
use crate::handle::CHandle;
/// `oakcodec_decoder_probe` — probe a media file, returning the
/// stream-list handle (`oakcodec_decoder_probe(filename)`). The caller
/// owns the returned handle.
pub fn decoder_probe(path: &str) -> Option<CHandle> {
use std::ffi::CString;
let c = CString::new(path).ok()?;
Some(unsafe { oakcodec::ffi::decoder::oakcodec_decoder_probe(c.as_ptr()) })
}
-424
View File
@@ -1,424 +0,0 @@
// 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/>.
//! oakcommon C ABI calls — now direct Rust calls into the oakcommon crate
//! (single-lib unification, see `docs/zh/plans/riir/single-lib.md`).
//! The XML surface mirrors `include/common/xmlutils.h` and backs the
//! serializer's reader/writer traits. Function names and signatures are
//! unchanged (callers in `src/` and `tests/` are untouched); the
//! `test-stubs` feature and its in-crate mocks were removed because the
//! real oakcommon rlib is now always linked (the mocks would collide with
//! its `#[no_mangle]` exports).
use std::ffi::{c_char, c_int};
use crate::handle::CHandle;
/// `oakcommon_xml_reader_init`.
pub fn xml_reader_init(data: *const c_char) -> Option<CHandle> {
Some(unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_init(data) })
}
/// `oakcommon_xml_reader_free`.
pub fn xml_reader_free(reader: *mut CHandle) {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_free(reader) }
}
/// `oakcommon_xml_reader_read_next_start_element` (advance to the next
/// start element; `found` receives 1/0).
pub fn xml_reader_next_start_element(reader: CHandle) -> Option<bool> {
let mut found = 0;
unsafe {
oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_next_start_element(reader, &mut found);
}
Some(found != 0)
}
/// `oakcommon_xml_reader_name` (two-stage).
pub fn xml_reader_name(reader: CHandle) -> Option<String> {
two_stage_string("oakcommon_xml_reader_name", |buf, size| unsafe {
Some(oakcommon::ffi::xmlutils::oakcommon_xml_reader_name(
reader.clone(),
buf,
size,
))
})
}
/// `oakcommon_xml_reader_read_element_text` (two-stage).
pub fn xml_reader_read_element_text(reader: CHandle) -> Option<String> {
two_stage_string(
"oakcommon_xml_reader_read_element_text",
|buf, size| unsafe {
Some(
oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_element_text(
reader.clone(),
buf,
size,
),
)
},
)
}
/// `oakcommon_xml_reader_skip_current_element`.
pub fn xml_reader_skip_current_element(reader: CHandle) -> Option<c_int> {
Some(unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_skip_current_element(reader) })
}
/// `oakcommon_xml_reader_attribute_count`.
pub fn xml_reader_attribute_count(reader: CHandle) -> Option<c_int> {
let mut count = 0;
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_count(reader, &mut count) };
Some(count)
}
/// `oakcommon_xml_reader_attribute_name` (two-stage).
pub fn xml_reader_attribute_name(reader: CHandle, index: c_int) -> Option<String> {
two_stage_string("oakcommon_xml_reader_attribute_name", |buf, size| unsafe {
Some(
oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_name(
reader.clone(),
index,
buf,
size,
),
)
})
}
/// `oakcommon_xml_reader_attribute_value` (two-stage).
pub fn xml_reader_attribute_value(reader: CHandle, index: c_int) -> Option<String> {
two_stage_string("oakcommon_xml_reader_attribute_value", |buf, size| unsafe {
Some(
oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_value(
reader.clone(),
index,
buf,
size,
),
)
})
}
/// `oakcommon_xml_reader_has_error`.
pub fn xml_reader_has_error(reader: CHandle) -> Option<bool> {
let mut err = 0;
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_has_error(reader, &mut err) };
Some(err != 0)
}
/// `oakcommon_xml_writer_init`.
pub fn xml_writer_init() -> Option<CHandle> {
Some(unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_init() })
}
/// `oakcommon_xml_writer_free`.
pub fn xml_writer_free(writer: *mut CHandle) {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_free(writer) }
}
/// `oakcommon_xml_writer_write_start_element`.
pub fn xml_writer_start_element(writer: CHandle, name: &str) -> Option<c_int> {
use std::ffi::CString;
let n = CString::new(name).ok()?;
Some(unsafe {
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_start_element(writer, n.as_ptr())
})
}
/// `oakcommon_xml_writer_write_attribute`.
pub fn xml_writer_attribute(writer: CHandle, name: &str, value: &str) -> Option<c_int> {
use std::ffi::CString;
let n = CString::new(name).ok()?;
let v = CString::new(value).ok()?;
Some(unsafe {
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_attribute(
writer,
n.as_ptr(),
v.as_ptr(),
)
})
}
/// `oakcommon_xml_writer_write_characters`.
pub fn xml_writer_characters(writer: CHandle, text: &str) -> Option<c_int> {
use std::ffi::CString;
let t = CString::new(text).ok()?;
Some(unsafe {
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_characters(writer, t.as_ptr())
})
}
/// `oakcommon_xml_writer_write_text_element`.
pub fn xml_writer_text_element(writer: CHandle, name: &str, text: &str) -> Option<c_int> {
use std::ffi::CString;
let n = CString::new(name).ok()?;
let t = CString::new(text).ok()?;
Some(unsafe {
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_text_element(
writer,
n.as_ptr(),
t.as_ptr(),
)
})
}
/// `oakcommon_xml_writer_write_end_element`.
pub fn xml_writer_end_element(writer: CHandle) -> Option<c_int> {
Some(unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_element(writer) })
}
/// `oakcommon_xml_writer_write_end_document`.
pub fn xml_writer_end_document(writer: CHandle) -> Option<c_int> {
Some(unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_document(writer) })
}
/// `oakcommon_xml_writer_output` (two-stage).
pub fn xml_writer_output(writer: CHandle) -> Option<String> {
two_stage_string("oakcommon_xml_writer_output", |buf, size| unsafe {
Some(oakcommon::ffi::xmlutils::oakcommon_xml_writer_output(
writer.clone(),
buf,
size,
))
})
}
/// `oakcommon_config_get_int` (config access for node defaults).
pub fn config_get_int(group: &str, key: &str, default: c_int) -> Option<c_int> {
use std::ffi::CString;
let g = CString::new(group).ok()?;
let k = CString::new(key).ok()?;
Some(unsafe {
oakcommon::ffi::config::oakcommon_config_get_int(g.as_ptr(), k.as_ptr(), default)
})
}
// ---------------------------------------------------------------------
// oakcommon videoparams C ABI (sequence/footage stream params)
// ---------------------------------------------------------------------
/// `oakcommon_videoparams_init_basic`: new owned handle (count 1).
pub fn videoparams_init_basic(
width: c_int,
height: c_int,
pixel_format: c_int,
channels: c_int,
par_num: c_int,
par_den: c_int,
interlacing: c_int,
divider: c_int,
) -> Option<CHandle> {
Some(unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_init_basic(
width,
height,
pixel_format,
channels,
par_num,
par_den,
interlacing,
divider,
)
})
}
/// `oakcommon_videoparams_set_frame_rate`.
pub fn videoparams_set_frame_rate(params: CHandle, num: c_int, den: c_int) -> Option<c_int> {
Some(unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_set_frame_rate(params, num, den)
})
}
/// `oakcommon_videoparams_free` — releases the handle locally (the
/// handle's `release` fn points into the oakcommon box machinery).
pub fn videoparams_free(params: *mut CHandle) {
if params.is_null() || unsafe { (*params).ctx.is_null() } {
return;
}
let h = unsafe { (*params).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*params).ctx = std::ptr::null_mut() };
}
/// `oakcommon_videoparams_get_width` — the value, or the default on
/// error (the caller decides whether the handle is real).
pub fn videoparams_get_width(params: CHandle) -> Option<c_int> {
let mut v = 0;
let rc = unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_get_width(params.clone(), &mut v)
};
Some(if rc < 0 { 0 } else { v })
}
/// `oakcommon_videoparams_get_height`.
pub fn videoparams_get_height(params: CHandle) -> Option<c_int> {
let mut v = 0;
let rc = unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_get_height(params.clone(), &mut v)
};
Some(if rc < 0 { 0 } else { v })
}
/// `oakcommon_videoparams_get_format`.
pub fn videoparams_get_format(params: CHandle) -> Option<c_int> {
let mut v = 0;
let rc = unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_get_format(params.clone(), &mut v)
};
Some(if rc < 0 { 0 } else { v })
}
/// `oakcommon_videoparams_get_channel_count`.
pub fn videoparams_get_channel_count(params: CHandle) -> Option<c_int> {
let mut v = 0;
let rc = unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_get_channel_count(params.clone(), &mut v)
};
Some(if rc < 0 { 0 } else { v })
}
/// `oakcommon_videoparams_get_frame_rate` — (num, den).
pub fn videoparams_get_frame_rate(params: CHandle) -> Option<(c_int, c_int)> {
let mut n = 0;
let mut d = 0;
let rc = unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_get_frame_rate(
params.clone(),
&mut n,
&mut d,
)
};
Some(if rc < 0 { (0, 0) } else { (n, d) })
}
// ---------------------------------------------------------------------
// oakcommon colortransform C ABI (color manager compliance)
// ---------------------------------------------------------------------
/// `oakcommon_colortransform_init_display`: new owned display transform.
pub fn colortransform_init_display(display: &str, view: &str, look: &str) -> Option<CHandle> {
use std::ffi::CString;
let d = CString::new(display).ok()?;
let v = CString::new(view).ok()?;
let l = CString::new(look).ok()?;
Some(unsafe {
oakcommon::ffi::colortransform::oakcommon_colortransform_init_display(
d.as_ptr(),
v.as_ptr(),
l.as_ptr(),
)
})
}
/// `oakcommon_colortransform_init_output`: new owned output transform.
pub fn colortransform_init_output(output: &str) -> Option<CHandle> {
use std::ffi::CString;
let o = CString::new(output).ok()?;
Some(unsafe {
oakcommon::ffi::colortransform::oakcommon_colortransform_init_output(o.as_ptr())
})
}
/// `oakcommon_colortransform_free` — releases the handle locally.
pub fn colortransform_free(transform: *mut CHandle) {
if transform.is_null() || unsafe { (*transform).ctx.is_null() } {
return;
}
let h = unsafe { (*transform).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*transform).ctx = std::ptr::null_mut() };
}
/// `oakcommon_colortransform_is_display`.
pub fn colortransform_is_display(transform: CHandle) -> Option<bool> {
Some(unsafe {
oakcommon::ffi::colortransform::oakcommon_colortransform_is_display(transform) != 0
})
}
/// `oakcommon_colortransform_get_display` (two-stage).
pub fn colortransform_get_display(transform: CHandle) -> Option<String> {
two_stage_string("oakcommon_colortransform_get_display", |buf, size| unsafe {
Some(
oakcommon::ffi::colortransform::oakcommon_colortransform_get_display(
transform.clone(),
buf,
size,
),
)
})
}
/// `oakcommon_colortransform_get_output` (two-stage).
pub fn colortransform_get_output(transform: CHandle) -> Option<String> {
two_stage_string("oakcommon_colortransform_get_output", |buf, size| unsafe {
Some(
oakcommon::ffi::colortransform::oakcommon_colortransform_get_output(
transform.clone(),
buf,
size,
),
)
})
}
/// `oakcommon_colortransform_get_view` (two-stage).
pub fn colortransform_get_view(transform: CHandle) -> Option<String> {
two_stage_string("oakcommon_colortransform_get_view", |buf, size| unsafe {
Some(
oakcommon::ffi::colortransform::oakcommon_colortransform_get_view(
transform.clone(),
buf,
size,
),
)
})
}
/// `oakcommon_colortransform_get_look` (two-stage).
pub fn colortransform_get_look(transform: CHandle) -> Option<String> {
two_stage_string("oakcommon_colortransform_get_look", |buf, size| unsafe {
Some(
oakcommon::ffi::colortransform::oakcommon_colortransform_get_look(
transform.clone(),
buf,
size,
),
)
})
}
/// Shared two-stage string fetch: query the required size, then read
/// into an owned buffer. `None` when the query returns an error.
fn two_stage_string<F: Fn(*mut c_char, c_int) -> Option<c_int>>(
_sym: &str,
call: F,
) -> Option<String> {
let needed = call(std::ptr::null_mut(), 0)?;
if needed <= 0 {
return Some(String::new());
}
let mut buf = vec![0u8; needed as usize];
call(buf.as_mut_ptr() as *mut c_char, needed)?;
buf.pop(); // trailing NUL
String::from_utf8(buf).ok()
}
-171
View File
@@ -1,171 +0,0 @@
// 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/>.
//! oakcore C ABI imports (audio stream parameters). dlsym-resolved (see
//! [`super`]). The `OakAudioParams` object is an opaque raw pointer owned
//! by the caller (`oakcore_audioparams_free`), not a [`crate::handle::CHandle`].
use std::ffi::{c_int, c_void};
#[cfg(not(feature = "test-stubs"))]
/// `oakcore_audioparams_create` — new owned params (release with
/// [`audioparams_free`]).
pub fn audioparams_create(
sample_rate: c_int,
channel_layout: u64,
format: c_int,
) -> Option<*mut c_void> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(c_int, u64, c_int) -> *mut c_void;
dlsym::call::<F, *mut c_void>("oakcore_audioparams_create", |f| unsafe {
f(sample_rate, channel_layout, format)
})
}
/// Test-stub path.
#[cfg(feature = "test-stubs")]
pub fn audioparams_create(
sample_rate: c_int,
channel_layout: u64,
format: c_int,
) -> Option<*mut c_void> {
Some(unsafe { stub::oakcore_audioparams_create(sample_rate, channel_layout, format) })
}
/// `oakcore_audioparams_free`.
pub fn audioparams_free(params: *mut c_void) {
if params.is_null() {
return;
}
#[cfg(feature = "test-stubs")]
unsafe {
stub::oakcore_audioparams_free(params);
}
#[cfg(not(feature = "test-stubs"))]
{
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*mut c_void);
let _ = dlsym::call::<F, ()>("oakcore_audioparams_free", |f| unsafe { f(params) });
}
}
#[cfg(not(feature = "test-stubs"))]
/// `oakcore_audioparams_sample_rate`.
pub fn audioparams_sample_rate(params: *const c_void) -> Option<c_int> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*const c_void) -> c_int;
dlsym::call::<F, c_int>("oakcore_audioparams_sample_rate", |f| unsafe { f(params) })
}
/// Test-stub path.
#[cfg(feature = "test-stubs")]
pub fn audioparams_sample_rate(params: *const c_void) -> Option<c_int> {
Some(unsafe { stub::oakcore_audioparams_sample_rate(params) })
}
#[cfg(not(feature = "test-stubs"))]
/// `oakcore_audioparams_channel_layout`.
pub fn audioparams_channel_layout(params: *const c_void) -> Option<u64> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*const c_void) -> u64;
dlsym::call::<F, u64>("oakcore_audioparams_channel_layout", |f| unsafe {
f(params)
})
}
/// Test-stub path.
#[cfg(feature = "test-stubs")]
pub fn audioparams_channel_layout(params: *const c_void) -> Option<u64> {
Some(unsafe { stub::oakcore_audioparams_channel_layout(params) })
}
#[cfg(not(feature = "test-stubs"))]
/// `oakcore_audioparams_format`.
pub fn audioparams_format(params: *const c_void) -> Option<c_int> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*const c_void) -> c_int;
dlsym::call::<F, c_int>("oakcore_audioparams_format", |f| unsafe { f(params) })
}
/// Test-stub path.
#[cfg(feature = "test-stubs")]
pub fn audioparams_format(params: *const c_void) -> Option<c_int> {
Some(unsafe { stub::oakcore_audioparams_format(params) })
}
/// In-crate implementations of the oakcore audioparams C ABI for
/// `cargo test` (`--features test-stubs`). Mirrors the real object: a
/// plain struct behind the caller-owned pointer.
#[cfg(feature = "test-stubs")]
pub(crate) mod stub {
use super::*;
/// `oakcore_audioparams_create`.
#[no_mangle]
pub unsafe extern "C" fn oakcore_audioparams_create(
sample_rate: c_int,
channel_layout: u64,
format: c_int,
) -> *mut c_void {
Box::into_raw(Box::new(StubAudioParams {
sample_rate,
channel_layout,
format,
})) as *mut c_void
}
/// `oakcore_audioparams_free`.
#[no_mangle]
pub unsafe extern "C" fn oakcore_audioparams_free(params: *mut c_void) {
if !params.is_null() {
unsafe { drop(Box::from_raw(params as *mut StubAudioParams)) };
}
}
/// `oakcore_audioparams_sample_rate`.
#[no_mangle]
pub unsafe extern "C" fn oakcore_audioparams_sample_rate(params: *const c_void) -> c_int {
if params.is_null() {
return 0;
}
unsafe { (*(params as *const StubAudioParams)).sample_rate }
}
/// `oakcore_audioparams_channel_layout`.
#[no_mangle]
pub unsafe extern "C" fn oakcore_audioparams_channel_layout(params: *const c_void) -> u64 {
if params.is_null() {
return 0;
}
unsafe { (*(params as *const StubAudioParams)).channel_layout }
}
/// `oakcore_audioparams_format`.
#[no_mangle]
pub unsafe extern "C" fn oakcore_audioparams_format(params: *const c_void) -> c_int {
if params.is_null() {
return 0;
}
unsafe { (*(params as *const StubAudioParams)).format }
}
/// Boxed audioparams stub payload.
pub(crate) struct StubAudioParams {
pub sample_rate: c_int,
pub channel_layout: u64,
pub format: c_int,
}
}
-76
View File
@@ -1,76 +0,0 @@
// 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 imports from other oak modules.
//!
//! ## Resolution model
//!
//! Symbols are resolved at runtime with `dlsym(RTLD_DEFAULT)` (the
//! module is force-loaded into the host process, so the real module
//! libraries' symbols are in the global scope). `cargo test` builds
//! without those libraries: a missing symbol surfaces as `None` from
//! the wrapper and the caller maps it to a graceful error. This follows
//! the oakplugin crate template (`src/plugin/rust/src/bridge/mod.rs`).
//!
//! Real linkage for the module dylib is provided by the C++ side's
//! force_load of liboaknode (the staticlib); nothing here is linked
//! directly at compile time.
pub mod codec;
pub mod common;
pub mod core;
pub mod render;
pub mod timeline;
pub mod undo;
/// Shared dlsym runtime resolution (pub for crate tests).
pub mod dlsym {
use std::ffi::{c_char, c_void};
/// RTLD_DEFAULT (macOS: -2; Linux: 0).
#[cfg(target_os = "macos")]
pub(crate) const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;
#[cfg(target_os = "linux")]
pub(crate) const RTLD_DEFAULT: *mut c_void = 0isize as *mut c_void;
extern "C" {
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
}
/// Resolve a global-scope symbol; `None` when missing.
pub fn resolve(name: &str) -> Option<*mut c_void> {
let c = std::ffi::CString::new(name).ok()?;
let p = unsafe { dlsym(RTLD_DEFAULT, c.as_ptr()) };
if p.is_null() {
None
} else {
Some(p)
}
}
/// Resolve and call by signature; `None` when the symbol is missing.
///
/// # Safety
/// The caller guarantees `T` matches the symbol's real function type.
pub(crate) fn call<T, R>(name: &str, f: impl FnOnce(T) -> R) -> Option<R>
where
T: Copy,
{
let p = resolve(name)?;
let f_ptr: T = unsafe { std::mem::transmute_copy(&p) };
Some(f(f_ptr))
}
}
-159
View File
@@ -1,159 +0,0 @@
// 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/>.
//! oakrender C ABI imports (caches, textures, color processors).
//!
//! Symbols resolved via `dlsym(RTLD_DEFAULT)` (see [`super::dlsym`]);
//! every wrapper returns `None`/a neutral value when the symbol is
//! absent (cargo test without liboakrender).
use std::ffi::c_int;
use crate::handle::CHandle;
/// oakrender cache handle (value type).
pub type CacheHandle = CHandle;
/// oakrender texture handle (value type).
pub type TextureHandle = CHandle;
/// oakrender color processor handle (value type).
pub type ColorProcessorHandle = CHandle;
/// Cache kind constants (oakrender `OAKRENDER_CACHE_*`).
pub mod cache_kind {
/// `OAKRENDER_CACHE_VIDEO_FRAME`.
pub const VIDEO_FRAME: i32 = 0;
/// `OAKRENDER_CACHE_THUMBNAIL`.
pub const THUMBNAIL: i32 = 1;
/// `OAKRENDER_CACHE_AUDIO_PLAYBACK`.
pub const AUDIO_PLAYBACK: i32 = 2;
/// `OAKRENDER_CACHE_AUDIO_WAVEFORM`.
pub const AUDIO_WAVEFORM: i32 = 3;
}
/// `oakrender_cache_create_for_node`.
pub fn cache_create_for_node(parent: CHandle, kind: i32) -> Option<CHandle> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(CHandle, i32) -> CHandle;
dlsym::call::<F, CHandle>("oakrender_cache_create_for_node", |f| unsafe {
f(parent, kind)
})
}
/// `oakrender_cache_free`.
pub fn cache_free(cache: *mut CHandle) {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*mut CHandle);
if let Some(f) = dlsym::call::<F, ()>("oakrender_cache_free", |f| unsafe { f(cache) }) {
let _ = f;
}
}
/// `oakrender_cache_invalidate_range`.
pub fn cache_invalidate_range(
cache: CHandle,
in_num: i64,
in_den: i64,
out_num: i64,
out_den: i64,
) {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(CHandle, i64, i64, i64, i64);
if let Some(f) = dlsym::call::<F, ()>("oakrender_cache_invalidate_range", |f| unsafe {
f(cache, in_num, in_den, out_num, out_den)
}) {
let _ = f;
}
}
/// `oakrender_cache_set_uuid`.
pub fn cache_set_uuid(cache: CHandle, uuid: &str) -> Option<i32> {
use crate::bridge::dlsym;
use std::ffi::CString;
type F = unsafe extern "C" fn(CHandle, *const std::ffi::c_char) -> i32;
let c = CString::new(uuid).ok()?;
dlsym::call::<F, i32>("oakrender_cache_set_uuid", |f| unsafe {
f(cache, c.as_ptr())
})
}
/// `oakrender_cache_get_uuid` (two-stage).
pub fn cache_get_uuid(cache: CHandle) -> Option<String> {
use crate::bridge::dlsym;
use std::ffi::c_char;
type F = unsafe extern "C" fn(CHandle, *mut c_char, i32) -> i32;
let needed = dlsym::call::<F, i32>("oakrender_cache_get_uuid", |f| unsafe {
f(cache.clone(), std::ptr::null_mut(), 0)
})?;
if needed <= 0 {
return None;
}
let mut buf = vec![0u8; needed as usize];
dlsym::call::<F, i32>("oakrender_cache_get_uuid", |f| unsafe {
f(cache.clone(), buf.as_mut_ptr() as *mut c_char, needed)
})?;
buf.pop(); // trailing NUL
String::from_utf8(buf).ok()
}
/// `oakrender_disk_cache_path` (two-stage): the default cache directory.
pub fn disk_cache_path() -> Option<String> {
use crate::bridge::dlsym;
use std::ffi::c_char;
type F = unsafe extern "C" fn(*mut c_char, i32) -> i32;
let needed = dlsym::call::<F, i32>("oakrender_disk_cache_path", |f| unsafe {
f(std::ptr::null_mut(), 0)
})?;
if needed <= 0 {
return None;
}
let mut buf = vec![0u8; needed as usize];
dlsym::call::<F, i32>("oakrender_disk_cache_path", |f| unsafe {
f(buf.as_mut_ptr() as *mut c_char, needed)
})?;
buf.pop(); // trailing NUL
String::from_utf8(buf).ok()
}
/// `oakrender_color_config_create_default`: load the bundled OCIO
/// config. `None` = symbol absent (cargo test); `Some(Ok(())` =
/// success; `Some(Err(()))` = OCIO error.
pub fn color_config_create_default() -> Option<Result<(), ()>> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn() -> i32;
let rc = dlsym::call::<F, i32>("oakrender_color_config_create_default", |f| unsafe { f() })?;
if rc == 0 {
Some(Ok(()))
} else {
Some(Err(()))
}
}
/// `oakrender_color_config_load_from_filename`: load a config file.
/// Same tri-state as [`color_config_create_default`].
pub fn color_config_load(filename: &str) -> Option<Result<(), ()>> {
use crate::bridge::dlsym;
use std::ffi::CString;
type F = unsafe extern "C" fn(*const std::ffi::c_char) -> i32;
let c = CString::new(filename).ok()?;
let rc = dlsym::call::<F, i32>("oakrender_color_config_load_from_filename", |f| unsafe {
f(c.as_ptr())
})?;
if rc == 0 {
Some(Ok(()))
} else {
Some(Err(()))
}
}
-59
View File
@@ -1,59 +0,0 @@
// 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/>.
//! oaktimeline C ABI imports (sequence markers/work area, edit
//! commands used by sequence setup). dlsym-resolved (see [`super`]).
use crate::handle::CHandle;
/// `oaktimeline_marker_list_create`.
pub fn marker_list_create() -> Option<CHandle> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn() -> CHandle;
dlsym::call::<F, CHandle>("oaktimeline_marker_list_create", |f| unsafe { f() })
}
/// `oaktimeline_marker_list_free`.
pub fn marker_list_free(list: *mut CHandle) {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*mut CHandle);
if let Some(f) = dlsym::call::<F, ()>("oaktimeline_marker_list_free", |f| unsafe { f(list) }) {
let _ = f;
}
}
/// `oaktimeline_workarea_create`.
pub fn workarea_create() -> Option<CHandle> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn() -> CHandle;
dlsym::call::<F, CHandle>("oaktimeline_workarea_create", |f| unsafe { f() })
}
/// `oaktimeline_workarea_free`.
pub fn workarea_free(w: *mut CHandle) {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(*mut CHandle);
if let Some(f) = dlsym::call::<F, ()>("oaktimeline_workarea_free", |f| unsafe { f(w) }) {
let _ = f;
}
}
/// `oaktimeline_add_track_command`.
pub fn add_track_command(list: CHandle) -> Option<CHandle> {
use crate::bridge::dlsym;
type F = unsafe extern "C" fn(CHandle) -> CHandle;
dlsym::call::<F, CHandle>("oaktimeline_add_track_command", |f| unsafe { f(list) })
}
-408
View File
@@ -1,408 +0,0 @@
// 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/>.
//! oakundo C ABI calls — now direct Rust calls into the oakundo crate
//! (single-lib unification, see `docs/zh/plans/riir/single-lib.md`).
//! Undo commands are created through the C ABI vtable
//! (`oakundo_command_init` with Rust closures as userdata) — no C++
//! UndoCommand subclassing exists on this side.
//!
//! ## Test stubs (`--features test-stubs`)
//!
//! With the direct dependency, the real oakundo rlib is linked into
//! every build and test; the `test-stubs` feature still compiles
//! in-crate `#[no_mangle]` implementations of the undo C ABI (see
//! [`stub`]) for environments that link without oakundo. Do not enable
//! the feature in a binary that also links oakundo (duplicate symbols).
use std::ffi::c_int;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::handle::CHandle;
/// `OakUndoCommandVtable` (include/undo/undocommand.h) — the callback
/// table backing a caller-defined undo command. Single-lib unification:
/// aliases the oakundo crate's vtable POD (identical layout).
pub type Vtable = oakundo::undocommand::OakUndoCommandVtable;
/// Rust closure state behind a vtable command's `userdata` pointer.
///
/// The box is handed to [`command_init`] (which takes ownership); the
/// vtable trampolines below route `redo`/`undo`/destruction back into
/// the closures.
pub struct CommandState {
/// Whether the command has been executed (redo_now no-ops when done).
pub done: AtomicBool,
/// Redo closure.
pub redo: Box<dyn FnMut() + Send>,
/// Undo closure.
pub undo: Box<dyn FnMut() + Send>,
}
impl CommandState {
/// New state with both directions.
pub fn new(
redo: impl FnMut() + Send + 'static,
undo: impl FnMut() + Send + 'static,
) -> CommandState {
CommandState {
done: AtomicBool::new(false),
redo: Box::new(redo),
undo: Box::new(undo),
}
}
}
/// Trampoline: run the redo closure behind `userdata`. Panics are
/// swallowed at the C boundary (`// CPP-PARITY: undocommand.cpp` — the
/// C++ side has no panic concept; a panic here must never unwind across
/// the extern "C" frame).
unsafe extern "C" fn redo_trampoline(userdata: *mut std::ffi::c_void) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if !userdata.is_null() {
let state = unsafe { &mut *(userdata as *mut CommandState) };
(state.redo)();
}
}));
}
/// Trampoline: run the undo closure behind `userdata`.
unsafe extern "C" fn undo_trampoline(userdata: *mut std::ffi::c_void) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if !userdata.is_null() {
let state = unsafe { &mut *(userdata as *mut CommandState) };
(state.undo)();
}
}));
}
/// Trampoline: free the `CommandState` box.
unsafe extern "C" fn free_trampoline(userdata: *mut std::ffi::c_void) {
if !userdata.is_null() {
unsafe { drop(Box::from_raw(userdata as *mut CommandState)) };
}
}
/// Create a vtable-backed undo command whose redo/undo run the given
/// closures (`oakundo_command_init`). The returned handle is owned by
/// the caller; `None` when oakundo is unavailable (or the stub returns
/// an empty handle).
pub fn command_from_closures(
redo: impl FnMut() + Send + 'static,
undo: impl FnMut() + Send + 'static,
) -> Option<CHandle> {
let state = Box::new(CommandState::new(redo, undo));
let vtable = Vtable {
redo: Some(redo_trampoline),
undo: Some(undo_trampoline),
free_fn: Some(free_trampoline),
};
command_init(&vtable, Box::into_raw(state) as *mut std::ffi::c_void)
}
/// `oakundo_command_init` (vtable command).
#[cfg(feature = "test-stubs")]
pub fn command_init(vtable: &Vtable, userdata: *mut std::ffi::c_void) -> Option<CHandle> {
Some(unsafe { stub::oakundo_command_init(vtable as *const Vtable, userdata) })
}
/// `oakundo_command_init` (vtable command).
#[cfg(not(feature = "test-stubs"))]
pub fn command_init(vtable: &Vtable, userdata: *mut std::ffi::c_void) -> Option<CHandle> {
// Direct call into the oakundo crate (single-lib unification).
let h = unsafe { oakundo::ffi::command::oakundo_command_init(vtable, userdata) };
if h.is_null() {
None
} else {
Some(h)
}
}
/// `oakundo_command_init_multi`.
#[cfg(feature = "test-stubs")]
pub fn command_init_multi() -> Option<CHandle> {
Some(unsafe { stub::oakundo_command_init_multi() })
}
/// `oakundo_command_init_multi`.
#[cfg(not(feature = "test-stubs"))]
pub fn command_init_multi() -> Option<CHandle> {
// Direct call into the oakundo crate (single-lib unification).
let h = unsafe { oakundo::ffi::command::oakundo_command_init_multi() };
if h.is_null() {
None
} else {
Some(h)
}
}
/// `oakundo_command_multi_add_child`.
#[cfg(feature = "test-stubs")]
pub fn command_multi_add_child(multi: CHandle, child: CHandle) -> Option<c_int> {
Some(unsafe { stub::oakundo_command_multi_add_child(multi, child) })
}
/// `oakundo_command_multi_add_child`.
#[cfg(not(feature = "test-stubs"))]
pub fn command_multi_add_child(multi: CHandle, child: CHandle) -> Option<c_int> {
// Direct call into the oakundo crate (single-lib unification).
Some(unsafe { oakundo::ffi::command::oakundo_command_multi_add_child(multi, child) })
}
/// `oakundo_command_redo_now`.
#[cfg(feature = "test-stubs")]
pub fn command_redo_now(command: CHandle) -> Option<c_int> {
Some(unsafe { stub::oakundo_command_redo_now(command) })
}
/// `oakundo_command_redo_now`.
#[cfg(not(feature = "test-stubs"))]
pub fn command_redo_now(command: CHandle) -> Option<c_int> {
// Direct call into the oakundo crate (single-lib unification).
Some(unsafe { oakundo::ffi::command::oakundo_command_redo_now(command) })
}
/// `oakundo_command_undo_now`.
#[cfg(feature = "test-stubs")]
pub fn command_undo_now(command: CHandle) -> Option<c_int> {
Some(unsafe { stub::oakundo_command_undo_now(command) })
}
/// `oakundo_command_undo_now`.
#[cfg(not(feature = "test-stubs"))]
pub fn command_undo_now(command: CHandle) -> Option<c_int> {
// Direct call into the oakundo crate (single-lib unification).
Some(unsafe { oakundo::ffi::command::oakundo_command_undo_now(command) })
}
/// `oakundo_command_free`.
#[cfg(feature = "test-stubs")]
pub fn command_free(command: *mut CHandle) {
unsafe { stub::oakundo_command_free(command) };
}
/// `oakundo_command_free`.
#[cfg(not(feature = "test-stubs"))]
pub fn command_free(command: *mut CHandle) {
// Direct call into the oakundo crate (single-lib unification).
unsafe { oakundo::ffi::command::oakundo_command_free(command) }
}
/// `oakundo_stack_push` (facade-owned stack).
pub fn stack_push(
stack: CHandle,
command: CHandle,
text: *const std::ffi::c_char,
) -> Option<c_int> {
// Direct call into the oakundo crate (single-lib unification).
Some(unsafe { oakundo::ffi::undostack::oakundo_undostack_push(stack, command, text) })
}
/// In-crate implementations of the undo C ABI for `cargo test`
/// (`--features test-stubs`). Mirrors the C++ `CallbackUndoCommand`
/// (`src/undo/c_api/undocommand.cpp`) semantics: the command holds the
/// vtable + userdata, calls `free_fn` on destruction, and `redo_now`/
/// `undo_now` are no-ops when already executed. Multi commands hold one
/// reference per child.
#[cfg(feature = "test-stubs")]
pub(crate) mod stub {
use super::*;
/// A command box behind an OakUndoCommand handle's `ctx`.
pub(crate) enum StubCommand {
/// Vtable command.
Callback {
/// Executed state (redo_now no-ops when true).
done: AtomicBool,
/// Redo callback.
redo: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
/// Undo callback.
undo: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
/// userdata release.
free_fn: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
/// Opaque userdata (owned by the command).
userdata: *mut std::ffi::c_void,
},
/// Multi command.
Multi {
/// Executed state.
done: AtomicBool,
/// Child commands (each holds one reference).
children: Vec<CHandle>,
},
}
impl Drop for StubCommand {
fn drop(&mut self) {
match self {
StubCommand::Callback {
free_fn, userdata, ..
} => {
if let Some(f) = free_fn {
if !userdata.is_null() {
unsafe { f(*userdata) };
}
}
}
StubCommand::Multi { children, .. } => {
for child in children {
if let Some(f) = child.release {
unsafe { f(child.ctx) };
}
}
}
}
}
}
/// `oakundo_command_init`.
#[no_mangle]
pub unsafe extern "C" fn oakundo_command_init(
vtable: *const Vtable,
userdata: *mut std::ffi::c_void,
) -> CHandle {
if vtable.is_null() {
return CHandle::null();
}
let vt = unsafe { &*vtable };
let cmd = StubCommand::Callback {
done: AtomicBool::new(false),
redo: vt.redo,
undo: vt.undo,
free_fn: vt.free_fn,
userdata,
};
crate::handle::make_owned(SendStub(cmd))
}
/// `oakundo_command_init_multi`.
#[no_mangle]
pub unsafe extern "C" fn oakundo_command_init_multi() -> CHandle {
crate::handle::make_owned(SendStub(StubCommand::Multi {
done: AtomicBool::new(false),
children: Vec::new(),
}))
}
/// `oakundo_command_multi_add_child`.
#[no_mangle]
pub unsafe extern "C" fn oakundo_command_multi_add_child(
multi: CHandle,
child: CHandle,
) -> c_int {
if multi.ctx.is_null() || child.ctx.is_null() {
return crate::error::OAKNODE_E_INVALID;
}
let boxed = multi.ctx as *mut crate::handle::RefBox<SendStub>;
// Take one reference for the multi.
if let Some(f) = child.addref {
unsafe { f(child.ctx) };
}
let state = unsafe { &mut (*boxed).value };
match &mut state.0 {
StubCommand::Multi { children, .. } => {
children.push(child);
crate::error::OAKNODE_OK
}
_ => crate::error::OAKNODE_E_INVALID,
}
}
/// `oakundo_command_redo_now`.
#[no_mangle]
pub unsafe extern "C" fn oakundo_command_redo_now(command: CHandle) -> c_int {
if command.ctx.is_null() {
return crate::error::OAKNODE_E_INVALID;
}
let boxed = command.ctx as *mut crate::handle::RefBox<SendStub>;
let state = unsafe { &mut (*boxed).value };
match &mut state.0 {
StubCommand::Callback {
done,
redo,
userdata,
..
} => {
if !done.swap(true, Ordering::AcqRel) {
if let Some(f) = redo {
unsafe { f(*userdata) };
}
}
crate::error::OAKNODE_OK
}
StubCommand::Multi { done, children } => {
if !done.swap(true, Ordering::AcqRel) {
for child in children.iter() {
let _ = unsafe { oakundo_command_redo_now(child.clone()) };
}
}
crate::error::OAKNODE_OK
}
}
}
/// `oakundo_command_undo_now`.
#[no_mangle]
pub unsafe extern "C" fn oakundo_command_undo_now(command: CHandle) -> c_int {
if command.ctx.is_null() {
return crate::error::OAKNODE_E_INVALID;
}
let boxed = command.ctx as *mut crate::handle::RefBox<SendStub>;
let state = unsafe { &mut (*boxed).value };
match &mut state.0 {
StubCommand::Callback {
done,
undo,
userdata,
..
} => {
if done.swap(false, Ordering::AcqRel) {
if let Some(f) = undo {
unsafe { f(*userdata) };
}
}
crate::error::OAKNODE_OK
}
StubCommand::Multi { done, children } => {
if done.swap(false, Ordering::AcqRel) {
for child in children.iter().rev() {
let _ = unsafe { oakundo_command_undo_now(child.clone()) };
}
}
crate::error::OAKNODE_OK
}
}
}
/// `oakundo_command_free`.
#[no_mangle]
pub unsafe extern "C" fn oakundo_command_free(command: *mut CHandle) {
if command.is_null() || unsafe { (*command).ctx.is_null() } {
return;
}
let h = unsafe { (*command).clone() };
if let Some(f) = h.release {
unsafe { f(h.ctx) };
}
unsafe { (*command).ctx = std::ptr::null_mut() };
}
/// Send-marker for the command box (the raw `userdata` pointer is
/// only dereferenced on the thread that created the command — the
/// test thread — so the box never actually crosses threads).
struct SendStub(StubCommand);
unsafe impl Send for SendStub {}
}
File diff suppressed because it is too large Load Diff
+29 -27
View File
@@ -15,12 +15,14 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Footage nodes (C++ `olive::Footage`): media file references.
//! Probing goes through the oakcodec C ABI (`bridge::codec`) — the C++
//! transition-stub probe path does not exist here.
//! Probing goes through the oakcodec decoder registry (direct Rust calls,
//! single-lib unification).
//! `// CPP-PARITY: src/node/src/project/footage/footage.{h,cpp}`.
use std::sync::atomic::{AtomicBool, Ordering};
use oakcodec::decoder::Decoder as _;
use crate::input::Input;
use crate::node::{Category, NodeBehavior, NodeCore};
use crate::value::{AudioParams, NodeValue, ValueType, VideoParams};
@@ -84,33 +86,33 @@ impl FootageBehavior {
}
}
/// Probe the file through oakcodec (`oakcodec_decoder_probe`),
/// filling `streams`. Error on unreadable/corrupt media or when the
/// codec module is unavailable; the prior `streams`/`valid` state is
/// preserved on failure (no partial state).
/// Probe the file through oakcodec's decoder registry, recording the
/// recognized decoder id. Error on unreadable/corrupt media; the
/// prior `streams`/`valid` state is preserved on failure (no partial
/// state).
pub fn probe(&mut self) -> crate::error::Result<()> {
use crate::error::Error;
// Direct call into the oakcodec crate (single-lib unification):
// `oakcodec_decoder_probe(filename)` returns the stream-list
// handle (owned by the caller).
let out = match crate::bridge::codec::decoder_probe(&self.filename) {
Some(out) => out,
None => {
return Err(Error::Failed(
"oakcodec unavailable (not linked)".to_string(),
));
}
};
if out.is_null() {
return Err(Error::Failed(
"oakcodec probe returned no streams".to_string(),
));
}
// The probe result handle is an oakcodec stream-list. Reading
// stream entries into `streams` is a Phase-2 follow-up (the
// exact accessor symbols are pinned when the codec module C ABI
// is finalized).
let _ = out;
// Direct probe through the oakcodec decoder registry (single-lib
// unification; replaces the former `oakcodec_decoder_probe` C ABI
// call).
let desc = oakcodec::decoder::receive_list_of_all_decoders()
.into_iter()
.find_map(|d| {
let desc = d.probe(&self.filename, None)?;
if desc.decoder().is_empty()
|| (desc.video_stream_count() == 0
&& desc.audio_stream_count() == 0
&& desc.subtitle_stream_count() == 0)
{
return None;
}
Some(desc)
})
.ok_or_else(|| Error::Failed("oakcodec probe returned no streams".to_string()))?;
self.decoder = desc.decoder().to_string();
// Reading stream entries into `streams` is a follow-up (the
// stream-access surface is pinned when the codec module is
// finalized); the probe result itself is dropped here.
self.valid = true;
Ok(())
}
-2
View File
@@ -30,11 +30,9 @@
#![warn(missing_docs)]
pub mod block;
pub mod bridge;
pub mod colormanager;
pub mod error;
pub mod factory;
pub mod ffi;
pub mod folder;
pub mod footage;
pub mod graph;
+8 -8
View File
@@ -73,7 +73,7 @@ pub struct NodeCore {
pub inputs: Vec<Input>,
/// Keyframe tracks per (input, element).
pub keyframes: Vec<(String, i32, KeyframeTrack)>,
/// Caches as oakrender handles (created via bridge::render).
/// Caches as opaque oakrender handles.
pub caches: NodeCaches,
/// Node flags bitmask (hidden, dont-show-in-param-view, ...).
pub flags: u64,
@@ -429,18 +429,18 @@ impl NodeCore {
#[derive(Clone)]
pub struct NodeCaches {
/// Video frame hash cache.
pub video: crate::bridge::render::CacheHandle,
pub video: crate::handle::CHandle,
/// Thumbnail cache.
pub thumbnail: crate::bridge::render::CacheHandle,
pub thumbnail: crate::handle::CHandle,
/// Audio playback cache.
pub audio: crate::bridge::render::CacheHandle,
pub audio: crate::handle::CHandle,
/// Waveform cache.
pub waveform: crate::bridge::render::CacheHandle,
pub waveform: crate::handle::CHandle,
}
impl Default for NodeCaches {
/// All empty handles (caches are created lazily through bridge::render
/// when a node enters a project; `// CPP-PARITY: node.cpp:102`).
/// All empty handles (caches are created lazily when a node
/// enters a project; `// CPP-PARITY: node.cpp:102`).
fn default() -> Self {
NodeCaches {
video: crate::handle::CHandle::null(),
@@ -586,7 +586,7 @@ pub trait NodeBehavior: Send {
fn generate_frame(
&self,
core: &NodeCore,
frame: &mut crate::bridge::render::TextureHandle,
frame: &mut crate::handle::CHandle,
time: Rational,
) {
let _ = (core, frame, time);
+1 -1
View File
@@ -20,7 +20,7 @@
//!
//! Note: OpenColorIO itself is never linked here; it is reached through
//! the color manager (`crate::colormanager`) and the oakrender bridge
//! (`crate::bridge::render`), like the C++ node's
//! (oakrender, opaque handles), like the C++ node's
//! `oaknode_colormanager_*` / `oakrender_color_processor_*` calls.
use crate::factory::NodeMeta;
@@ -93,7 +93,7 @@ impl GeneratorWithMerge {
/// and the un-merged case pushes `job` itself.
pub fn push_mergable_job(
inputs: &crate::value::NodeValueRow,
job: crate::bridge::render::TextureHandle,
job: crate::handle::CHandle,
table: &mut crate::value::NodeValueTable,
) {
match inputs.get(BASE_INPUT) {
+1 -1
View File
@@ -22,7 +22,7 @@
//! undo commands `NodeGroupAddInputPassthrough` /
//! `NodeGroupSetOutputPassthrough`; the C ABI's undoable variants
//! (`include/node/group.h`) build equivalent vtable commands through
//! `bridge::undo` in the ffi layer.
//! oakundo commands in the ops layer.
use crate::factory::NodeMeta;
use crate::graph::Graph;
+5 -5
View File
@@ -25,7 +25,7 @@
//!
//! Note: OpenColorIO itself is never linked here. All OCIO work goes
//! through the color manager (`crate::colormanager`) and the oakrender
//! bridge (`crate::bridge::render`), mirroring how the C++ node calls
//! bridge (opaque oakrender handles), mirroring how the C++ node calls
//! `oakrender_color_processor_*` / `oaknode_colormanager_*` C functions
//! instead of using OCIO directly.
@@ -47,13 +47,13 @@ pub const TEXTURE_INPUT: &str = "tex_in";
/// bridge), so the field is omitted here: `added_to_graph` /
/// `removed_from_graph` document the capture/clear, and the processor
/// generation helpers reach the manager through
/// `crate::colormanager`/`crate::bridge::render` at call time.
/// `crate::colormanager`/oakrender at call time.
pub struct OcioBase {
/// Owned color processor handle (C++ `processor_`, an
/// `OakColorProcessor`); `None`/empty while no valid processor has
/// been generated. Released with the node (C++ destructor calls
/// `oakrender_color_processor_free`).
processor: Option<crate::bridge::render::ColorProcessorHandle>,
processor: Option<crate::handle::CHandle>,
}
// The processor handle wraps a refcounted C object that is only
@@ -74,7 +74,7 @@ impl OcioBase {
/// Borrowed view of the owned processor handle (C++
/// `OCIOBaseNode::processor()`; callers must NOT free it).
pub fn processor(&self) -> Option<&crate::bridge::render::ColorProcessorHandle> {
pub fn processor(&self) -> Option<&crate::handle::CHandle> {
self.processor.as_ref()
}
@@ -83,7 +83,7 @@ impl OcioBase {
/// `OakColorProcessor` before storing the new one).
pub fn set_processor(
&mut self,
processor: Option<crate::bridge::render::ColorProcessorHandle>,
processor: Option<crate::handle::CHandle>,
) {
// The C++ frees the previous processor via
// `oakrender_color_processor_free`; the Rust handle is a
@@ -20,7 +20,7 @@
//!
//! Note: OpenColorIO itself is never linked here; it is reached through
//! the color manager (`crate::colormanager`) and the oakrender bridge
//! (`crate::bridge::render`), like the C++ node's
//! (oakrender, opaque handles), like the C++ node's
//! `oakrender_color_processor_create_grading_primary` call.
use crate::factory::NodeMeta;
@@ -25,7 +25,7 @@
//!
//! Note: OpenColorIO itself is never linked here; it is reached through
//! the color manager (`crate::colormanager`) and the oakrender bridge
//! (`crate::bridge::render`), like the C++ node's
//! (oakrender, opaque handles), like the C++ node's
//! `oakrender_color_processor_create_grading_primary` call.
use crate::factory::NodeMeta;
+2 -2
View File
@@ -19,7 +19,7 @@
//!
//! Note: OpenColorIO itself is never linked here; it is reached through
//! the color manager (`crate::colormanager`) and the oakrender bridge
//! (`crate::bridge::render`), like the C++ node's
//! (oakrender, opaque handles), like the C++ node's
//! `oakrender_color_processor_create_lut` / `oakrender_lut_*` calls.
use std::sync::Mutex;
@@ -57,7 +57,7 @@ struct ProcessorState {
last_direction: i64,
/// Cached processor for change detection (C++ `last_processor_`);
/// released with the node.
last_processor: Option<crate::bridge::render::ColorProcessorHandle>,
last_processor: Option<crate::handle::CHandle>,
/// Human-readable reason no LUT processor is active (C++
/// `last_error_`); empty when a valid processor is in use or no LUT
/// file has been selected yet.
+3 -3
View File
@@ -19,7 +19,7 @@
//!
//! DECLARATION ONLY. This node is a thin wrapper over an OFX plugin
//! instance that lives behind the `oakplugin` crate's C ABI bridge
//! (`crate::bridge`); no OFX types (`OFX::Host::ImageEffect::Instance`,
//! (opaque oakrender handles); no OFX types (`OFX::Host::ImageEffect::Instance`,
//! `kOfxParam*`, ...) are declared here. The plugin instance is
//! represented as the opaque [`PluginInstanceHandle`] below — the real
//! definition belongs to the oakplugin bridge module and this draft
@@ -242,14 +242,14 @@ impl NodeBehavior for PluginNode {
/// the destination frame if needed and zero-fills it (plugins do
/// their real image work in the plugin job, not here).
///
/// The Rust frame is an opaque [`crate::bridge::render::TextureHandle`]
/// The Rust frame is an opaque [`crate::handle::CHandle`]
/// whose pixels cannot be read or written from this crate, so the
/// body is a documented no-op (`// CPP-PARITY: plugin.cpp`
/// `generate_frame`).
fn generate_frame(
&self,
core: &NodeCore,
frame: &mut crate::bridge::render::TextureHandle,
frame: &mut crate::handle::CHandle,
time: Rational,
) {
let _ = (core, frame, time);
+2 -2
View File
@@ -136,7 +136,7 @@ impl NodeBehavior for PolygonGenerator {
/// scaling and center translation; without a backend the frame is
/// left empty (warned once).
///
/// The Rust `frame` is an opaque [`crate::bridge::render::TextureHandle`]
/// The Rust `frame` is an opaque [`crate::handle::CHandle`]
/// whose bytes cannot be touched, and this crate has no path-fill
/// backend — so neither the clear nor the fill is representable here
/// (`// CPP-PARITY: polygon.cpp` `generate_frame`). The path building
@@ -145,7 +145,7 @@ impl NodeBehavior for PolygonGenerator {
fn generate_frame(
&self,
core: &NodeCore,
frame: &mut crate::bridge::render::TextureHandle,
frame: &mut crate::handle::CHandle,
time: oakcore_rs::Rational,
) {
let _ = (core, frame, time);
+2 -2
View File
@@ -241,7 +241,7 @@ impl NodeBehavior for TextGeneratorV1 {
/// into the float frame multiplied by the color input. With no
/// backend installed, warns once and leaves the frame empty.
///
/// The Rust frame is an opaque [`crate::bridge::render::TextureHandle`]
/// The Rust frame is an opaque [`crate::handle::CHandle`]
/// whose pixels cannot be read or written from this crate, so the
/// body is a documented no-op; the layout/measure/offset control flow
/// is ported in [`Self::layout_request`], [`Self::draw_offsets`] and
@@ -249,7 +249,7 @@ impl NodeBehavior for TextGeneratorV1 {
fn generate_frame(
&self,
core: &NodeCore,
frame: &mut crate::bridge::render::TextureHandle,
frame: &mut crate::handle::CHandle,
time: Rational,
) {
let _ = (core, frame, time);
+2 -2
View File
@@ -301,7 +301,7 @@ impl NodeBehavior for TextGeneratorV2 {
/// identical). With no backend installed, warns once and leaves
/// the frame empty.
///
/// The Rust frame is an opaque [`crate::bridge::render::TextureHandle`]
/// The Rust frame is an opaque [`crate::handle::CHandle`]
/// whose pixels cannot be read or written from this crate, so the
/// body is a documented no-op; the layout/measure/offset control flow
/// is ported in [`Self::layout_request`], [`Self::base_offset`],
@@ -310,7 +310,7 @@ impl NodeBehavior for TextGeneratorV2 {
fn generate_frame(
&self,
core: &NodeCore,
frame: &mut crate::bridge::render::TextureHandle,
frame: &mut crate::handle::CHandle,
time: Rational,
) {
let _ = (core, frame, time);
+2 -2
View File
@@ -342,7 +342,7 @@ impl NodeBehavior for TextGeneratorV3 {
/// backend installed, warns once and leaves the cleared frame
/// untouched.
///
/// The Rust frame is an opaque [`crate::bridge::render::TextureHandle`]
/// The Rust frame is an opaque [`crate::handle::CHandle`]
/// whose pixels cannot be read or written from this crate, so the
/// body is a documented no-op; the layout/measure/offset control flow
/// is ported in [`Self::layout_request`], [`Self::base_offset`] and
@@ -350,7 +350,7 @@ impl NodeBehavior for TextGeneratorV3 {
fn generate_frame(
&self,
core: &NodeCore,
frame: &mut crate::bridge::render::TextureHandle,
frame: &mut crate::handle::CHandle,
time: Rational,
) {
let _ = (core, frame, time);
+67 -13
View File
@@ -17,7 +17,10 @@
//! Free functions replacing the C++ `Node` static methods
//! (COVERAGE.md §6/§9).
use std::ffi::c_void;
use oakcore_rs::TimeRange;
use oakundo::undocommand::{OakUndoCommandVtable, UndoCommand};
use crate::graph::Graph;
use crate::id::NodeId;
@@ -111,7 +114,7 @@ pub fn copy_inputs(
/// (C++ `copy_dependency_graph` / `copy_node_in_graph` /
/// `copy_node_and_dependency_graph_minus_items`). Returns the new
/// node ids (source order). Undo packaging happens at the caller via
/// bridge::undo.
/// oakundo's `UndoCommand`.
pub fn copy_subgraph(
graph: &mut Graph,
nodes: &[NodeId],
@@ -147,10 +150,64 @@ fn lock_any<T>(m: &std::sync::Mutex<T>) -> std::sync::MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
/// Userdata payload behind a closure-backed undo command: the boxed
/// redo/undo closures.
struct ClosureCommand {
/// The redo closure.
redo: Box<dyn FnMut() + Send>,
/// The undo closure.
undo: Box<dyn FnMut() + Send>,
}
/// `OakUndoCommandVtable` redo thunk for [`ClosureCommand`] userdata.
unsafe extern "C" fn closure_redo(ud: *mut c_void) {
// SAFETY: `ud` is the `ClosureCommand` box created by
// `command_from_closures` and still owned by the command.
let c = unsafe { &mut *(ud as *mut ClosureCommand) };
(c.redo)();
}
/// `OakUndoCommandVtable` undo thunk for [`ClosureCommand`] userdata.
unsafe extern "C" fn closure_undo(ud: *mut c_void) {
// SAFETY: see `closure_redo`.
let c = unsafe { &mut *(ud as *mut ClosureCommand) };
(c.undo)();
}
/// `OakUndoCommandVtable` free thunk for [`ClosureCommand`] userdata.
unsafe extern "C" fn closure_free(ud: *mut c_void) {
if !ud.is_null() {
// SAFETY: the box is destroyed exactly once, by the command that
// owns it.
unsafe { drop(Box::from_raw(ud as *mut ClosureCommand)) };
}
}
/// Build an un-executed [`UndoCommand`] from redo/undo closures (the
/// direct-Rust replacement of the former oakundo bridge
/// `command_from_closures`).
fn command_from_closures(
redo: impl FnMut() + Send + 'static,
undo: impl FnMut() + Send + 'static,
) -> UndoCommand {
let ud = Box::into_raw(Box::new(ClosureCommand {
redo: Box::new(redo),
undo: Box::new(undo),
}));
UndoCommand::from_vtable(
OakUndoCommandVtable {
redo: Some(closure_redo),
undo: Some(closure_undo),
free_fn: Some(closure_free),
},
ud as *mut c_void,
)
}
/// Set a keyframed/standard value at a time, returning an un-executed
/// undo command (C++ `Node::set_value_at_time` static; command creation
/// via bridge::undo). The mutation is chosen from the current state at
/// creation time, like the C++ (`// CPP-PARITY: node.cpp:1782`):
/// via oakundo's `UndoCommand`). The mutation is chosen from the current
/// state at creation time, like the C++ (`// CPP-PARITY: node.cpp:1782`):
/// - keyframing input with a key at `time` -> replace the key's value;
/// - keyframing input without a key -> insert a key (best type =
/// closest key's type, default Linear);
@@ -166,7 +223,7 @@ pub fn set_value_at_time_command(
element: i32,
time: oakcore_rs::Rational,
value: &crate::value::NodeValue,
) -> crate::error::Result<crate::handle::CHandle> {
) -> crate::error::Result<UndoCommand> {
use crate::error::Error;
use crate::keyframe::{Interpolation, Keyframe};
@@ -202,7 +259,7 @@ pub fn set_value_at_time_command(
// Replace the key's value (preserving type/handles).
let project_redo = project.clone();
let project_undo = project.clone();
Ok(crate::bridge::undo::command_from_closures(
Ok(command_from_closures(
{
let value = value.clone();
let input = input.clone();
@@ -229,8 +286,7 @@ pub fn set_value_at_time_command(
}
}
},
)
.ok_or(Error::NoMem)?)
))
}
None => {
let input_redo = input.clone();
@@ -250,7 +306,7 @@ pub fn set_value_at_time_command(
let project_redo = project.clone();
let project_undo = project.clone();
let value_redo = value.clone();
Ok(crate::bridge::undo::command_from_closures(
Ok(command_from_closures(
move || {
let mut g = lock_any(&project_redo);
if let Some(e) = g.graph.get_mut(node) {
@@ -272,8 +328,7 @@ pub fn set_value_at_time_command(
.remove_key(time);
}
},
)
.ok_or(Error::NoMem)?)
))
}
}
} else {
@@ -287,7 +342,7 @@ pub fn set_value_at_time_command(
let input_redo = input.clone();
let input_undo = input.clone();
let value_redo = value.clone();
Ok(crate::bridge::undo::command_from_closures(
Ok(command_from_closures(
move || {
let mut g = lock_any(&project_redo);
if let Some(e) = g.graph.get_mut(node) {
@@ -301,7 +356,6 @@ pub fn set_value_at_time_command(
e.core.set_standard_value(&input_undo, element, old.clone());
}
},
)
.ok_or(Error::NoMem)?)
))
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ pub struct NodeRef {
impl NodeRef {
/// New reference. `owned` selects whether releasing the last handle
/// reference accounts the node in [`crate::ffi::debug_alive_count`].
/// reference accounts the node in the crate's debug alive count.
pub fn new(project: Arc<Mutex<Project>>, id: NodeId, owned: bool) -> NodeRef {
NodeRef {
project,
+8 -13
View File
@@ -117,20 +117,15 @@ impl SequenceBehavior {
}
/// Apply the default video/audio parameters (C++
/// `ViewerOutput::set_default_parameters()`; the config lookups use
/// oakcommon's defaults when the config module is absent).
/// `ViewerOutput::set_default_parameters()`; the config lookups read
/// the oakcommon config store directly).
pub fn set_default_parameters(&mut self) {
let width =
crate::bridge::common::config_get_int("DefaultSequenceWidth", "", 1920).unwrap_or(1920);
let height = crate::bridge::common::config_get_int("DefaultSequenceHeight", "", 1080)
.unwrap_or(1080);
let sample_rate =
crate::bridge::common::config_get_int("DefaultSequenceAudioFrequency", "", 48000)
.unwrap_or(48000);
let fps_num = crate::bridge::common::config_get_int("DefaultSequenceFrameRateNum", "", 30)
.unwrap_or(30);
let fps_den = crate::bridge::common::config_get_int("DefaultSequenceFrameRateDen", "", 1)
.unwrap_or(1);
let config = oakcommon::configstore::ConfigStore::instance();
let width = config.get_int(None, "DefaultSequenceWidth", 1920);
let height = config.get_int(None, "DefaultSequenceHeight", 1080);
let sample_rate = config.get_int(None, "DefaultSequenceAudioFrequency", 48000);
let fps_num = config.get_int(None, "DefaultSequenceFrameRateNum", 30);
let fps_den = config.get_int(None, "DefaultSequenceFrameRateDen", 1);
self.video_params = vec![VideoParams {
width,
+33 -48
View File
@@ -16,8 +16,8 @@
//! Project (de)serialization: the C++ `ProjectSerializer` family.
//!
//! XML I/O goes through [`crate::bridge::common`] (oakcommon C ABI;
//! in-crate stubs under `--features test-stubs`). The XML shape mirrors
//! XML I/O goes through oakcommon's [`XmlReader`]/[`XmlWriter`] (direct
//! Rust calls, single-lib unification). The XML shape mirrors
//! the C++ `Node::save`/`Project::save` writers (`// CPP-PARITY:
//! src/node/src/node.cpp:node::save`, `// CPP-PARITY:
//! src/node/src/project.cpp:save`). Byte-exact output parity with the C++
@@ -32,9 +32,9 @@
use std::sync::{Arc, Mutex};
use oakcommon::xmlutils::{XmlReader, XmlWriter};
use oakcore_rs::Rational;
use crate::bridge::common;
use crate::graph::Graph;
use crate::id::NodeId;
use crate::keyframe::{Interpolation, Keyframe};
@@ -43,7 +43,7 @@ use crate::project::{NodeRef, Project};
use crate::value::{NodeValue, ValueType};
/// Minimal XML reader surface the serializer needs (implemented over
/// the oakcommon xml C ABI in `bridge::common`).
/// oakcommon's `xmlutils`).
pub trait XmlRead {
/// Advance to the next start element; false at end/close.
fn next_start_element(&mut self) -> bool;
@@ -73,47 +73,38 @@ pub trait XmlWrite {
fn characters(&mut self, _text: &str) {}
}
/// Reader over the oakcommon XML C ABI.
/// Reader over oakcommon's [`XmlReader`].
pub struct XmlReaderBridge {
/// The oakcommon reader handle.
pub handle: crate::handle::CHandle,
/// The oakcommon reader.
reader: XmlReader,
/// Current element name (cached).
name: String,
}
/// Writer over the oakcommon XML C ABI.
/// Writer over oakcommon's [`XmlWriter`].
pub struct XmlWriterBridge {
/// The oakcommon writer handle.
pub handle: crate::handle::CHandle,
/// The oakcommon writer.
writer: XmlWriter,
}
impl XmlReaderBridge {
/// Create from XML text; `None` when oakcommon is unavailable.
/// Create from XML text; `None` on a parse error.
pub fn new(xml: &str) -> Option<XmlReaderBridge> {
use std::ffi::CString;
let c = CString::new(xml).ok()?;
let handle = common::xml_reader_init(c.as_ptr())?;
let reader = XmlReader::new(xml).ok()?;
Some(XmlReaderBridge {
handle,
reader,
name: String::new(),
})
}
}
impl Drop for XmlReaderBridge {
fn drop(&mut self) {
common::xml_reader_free(&mut self.handle);
}
}
impl XmlRead for XmlReaderBridge {
fn next_start_element(&mut self) -> bool {
match common::xml_reader_next_start_element(self.handle.clone()) {
Some(true) => {
self.name = common::xml_reader_name(self.handle.clone()).unwrap_or_default();
true
}
_ => false,
if self.reader.read_next_start_element().unwrap_or(false) {
self.name = self.reader.name().unwrap_or_default();
true
} else {
false
}
}
@@ -122,64 +113,58 @@ impl XmlRead for XmlReaderBridge {
}
fn attribute(&self, name: &str) -> Option<String> {
let count = common::xml_reader_attribute_count(self.handle.clone()).unwrap_or(0);
let count = self.reader.attribute_count().unwrap_or(0);
for i in 0..count {
let attr_name =
common::xml_reader_attribute_name(self.handle.clone(), i).unwrap_or_default();
let attr_name = self.reader.attribute_name(i).unwrap_or_default();
if attr_name == name {
return common::xml_reader_attribute_value(self.handle.clone(), i);
return self.reader.attribute_value(i).ok();
}
}
None
}
fn read_element_text(&mut self) -> String {
common::xml_reader_read_element_text(self.handle.clone()).unwrap_or_default()
self.reader.read_element_text().unwrap_or_default()
}
fn skip_current_element(&mut self) {
let _ = common::xml_reader_skip_current_element(self.handle.clone());
let _ = self.reader.skip_current_element();
}
}
impl XmlWriterBridge {
/// Create a writer; `None` when oakcommon is unavailable.
/// Create a writer.
pub fn new() -> Option<XmlWriterBridge> {
let handle = common::xml_writer_init()?;
Some(XmlWriterBridge { handle })
Some(XmlWriterBridge {
writer: XmlWriter::new(),
})
}
/// The serialized output.
pub fn output(&self) -> String {
common::xml_writer_output(self.handle.clone()).unwrap_or_default()
}
}
impl Drop for XmlWriterBridge {
fn drop(&mut self) {
common::xml_writer_free(&mut self.handle);
self.writer.output().unwrap_or_default()
}
}
impl XmlWrite for XmlWriterBridge {
fn start_element(&mut self, name: &str) {
let _ = common::xml_writer_start_element(self.handle.clone(), name);
let _ = self.writer.write_start_element(name);
}
fn end_element(&mut self) {
let _ = common::xml_writer_end_element(self.handle.clone());
let _ = self.writer.write_end_element();
}
fn attribute(&mut self, name: &str, value: &str) {
let _ = common::xml_writer_attribute(self.handle.clone(), name, value);
let _ = self.writer.write_attribute(name, value);
}
fn text_element(&mut self, name: &str, text: &str) {
let _ = common::xml_writer_text_element(self.handle.clone(), name, text);
let _ = self.writer.write_text_element(name, text);
}
fn characters(&mut self, text: &str) {
let _ = common::xml_writer_characters(self.handle.clone(), text);
let _ = self.writer.write_characters(text);
}
}
+1 -1
View File
@@ -122,7 +122,7 @@ pub enum NodeValue {
/// Boolean.
Boolean(bool),
/// Texture handle (owned reference).
Texture(crate::bridge::render::TextureHandle),
Texture(crate::handle::CHandle),
/// Interleaved/planar sample payload + format.
Samples(SampleBuffer),
/// Rational.