feat(engine): rename facade to oakengine, build liboakengine.dylib
- src/facade/rust -> src/engine/rust; package oakfacade -> oakengine - crate-type += cdylib; module crates are real deps; linkage anchors force-link module C ABIs into the dylib - build.rs: -undefined dynamic_lookup for host-provided oakcore_*/fb_* - nm: 749 oakengine_* + 687 module oak*_* exports; undefined set is only the intended host-provided symbols - worker/cli updated to the new path/name; undo test race fix
This commit is contained in:
+2
-1
@@ -784,13 +784,14 @@ name = "oakcore-rs"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "oakfacade"
|
||||
name = "oakengine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"oakaudio",
|
||||
"oakcodec",
|
||||
"oakcommon",
|
||||
"oakcore-rs",
|
||||
"oaknode",
|
||||
"oakplugin",
|
||||
"oakrender",
|
||||
@@ -0,0 +1,70 @@
|
||||
[package]
|
||||
name = "oakengine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Oak Video Editor facade: re-exports the frozen oakengine_* C ABI over the module C ABIs (Rust)"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
# FFI discipline: panics must be catchable at every exported entry.
|
||||
panic = "unwind"
|
||||
|
||||
[dependencies]
|
||||
# NDJSON control-plane protocol for the worker session (src/worker.rs) and
|
||||
# the shm error formatting in src/ipc.rs.
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
# POSIX shm_open/mmap/munmap/shm_unlink constants + syscalls for the
|
||||
# shared-memory frame-slot transport (src/ipc.rs).
|
||||
libc = "0.2"
|
||||
|
||||
# Every module call crosses the module C ABI as an `extern "C"` import
|
||||
# (src/bridge/). The module crates below are REAL dependencies so their
|
||||
# `#[no_mangle]` exports are linked into the final `liboakengine` cdylib:
|
||||
# the dylib then carries the module C ABIs itself (oakundo_*, oakcommon_*,
|
||||
# oaktimeline_*, oakcodec_*, oakaudio_*, oakrender_*, oaktask_*,
|
||||
# oakplugin_*, oaknode_*) alongside the facade's oakengine_* exports.
|
||||
#
|
||||
# The crates are linked WITHOUT their `test-stubs` features so the real
|
||||
# exports ship. Cross-module calls that the modules resolve with
|
||||
# dlsym(RTLD_DEFAULT) (oaknode, oakplugin, oakrender) now resolve against
|
||||
# the sibling modules inside the same dylib; the remaining undefined
|
||||
# imports are the C++ host-provided symbols (`oakcore_audioparams_*`,
|
||||
# `oakcore_rational_*` from liboakcore, `fb_*` from ffmpeg_bridge), which
|
||||
# build.rs leaves as runtime lookups for the host app.
|
||||
#
|
||||
# Tests link the same crates; the dev-dependencies below re-declare
|
||||
# oakcommon/oakplugin WITH `test-stubs` so their in-crate C ABI mocks
|
||||
# (the ffmpeg_bridge replacement and the oakrender dlsym stubs) are
|
||||
# compiled into the test binaries (features union with the normal
|
||||
# dependencies for test builds).
|
||||
#
|
||||
# NOTE (oaktimeline/oaktask): their `test-stubs` features are never used
|
||||
# here — the in-crate mocks define `oakundo_command_init` etc., which
|
||||
# would collide with the real oakundo rlib in one binary. Without
|
||||
# test-stubs their real exports reference the oaknode/oakundo/oakcommon
|
||||
# C ABI symbols as link-time externs, provided by the sibling crates in
|
||||
# the same dylib (or by the dev-dependency rlibs in a test binary).
|
||||
#
|
||||
# rustc would normally prune rlibs that are only touched through
|
||||
# `extern "C"` imports from the link; src/linkage.rs anchors every crate
|
||||
# (and oakcore-rs) so the linker pulls their object files.
|
||||
oakcore-rs = { path = "../../oakcore-rs" }
|
||||
oakundo = { path = "../../undo/rust" }
|
||||
oakcommon = { path = "../../common/rust" }
|
||||
oaktimeline = { path = "../../timeline/rust" }
|
||||
oakcodec = { path = "../../codec/rust" }
|
||||
oakaudio = { path = "../../audio/rust" }
|
||||
oakrender = { path = "../../render/rust" }
|
||||
oaktask = { path = "../../task/rust" }
|
||||
oakplugin = { path = "../../plugin/rust" }
|
||||
oaknode = { path = "../../node/rust" }
|
||||
|
||||
[dev-dependencies]
|
||||
# Test-only feature union (see the comment above): tests keep the
|
||||
# in-crate mocks these features compile.
|
||||
oakcommon = { path = "../../common/rust", features = ["test-stubs"] }
|
||||
oakplugin = { path = "../../plugin/rust", features = ["test-stubs"] }
|
||||
@@ -42,9 +42,16 @@ tests/
|
||||
The facade's regular dependencies are `serde`/`serde_json` (the worker's
|
||||
NDJSON control-plane protocol, `src/worker.rs`) and `libc` (POSIX
|
||||
`shm_open`/`mmap`/`munmap`/`shm_unlink` for `src/ipc.rs`). Every module
|
||||
call still crosses the module C ABI as an `extern "C"` import
|
||||
(`src/bridge/`), resolved at the final app link against the module shared
|
||||
libraries.
|
||||
call crosses the module C ABI as an `extern "C"` import (`src/bridge/`),
|
||||
and the module crates themselves are real dependencies: [`linkage`](src/linkage.rs)
|
||||
anchors them so their `#[no_mangle]` exports are linked into the
|
||||
`liboakengine` cdylib — the dylib carries the module C ABIs (oakundo_*,
|
||||
oakcommon_*, oaktimeline_*, oakcodec_*, oakaudio_*, oakrender_*,
|
||||
oaktask_*, oakplugin_*, oaknode_*) next to the facade's oakengine_*
|
||||
exports. The only remaining imports are the C++ host symbols
|
||||
(`oakcore_*` from liboakcore, `fb_*` from ffmpeg_bridge), which
|
||||
`build.rs` leaves as runtime lookups (macOS `-undefined dynamic_lookup`)
|
||||
resolved from the host Oak process.
|
||||
|
||||
### Handle mapping
|
||||
|
||||
@@ -89,16 +96,16 @@ control-plane message serializers remain unwrapped.
|
||||
|
||||
## Testing
|
||||
|
||||
`cargo test` links the module crates' rlibs (dev-dependencies) so the
|
||||
facade's bridge imports resolve:
|
||||
The module crates are real dependencies, so `cargo test` links the same
|
||||
rlibs the cdylib embeds; the dev-dependencies re-declare
|
||||
`oakcommon`/`oakplugin` with their `test-stubs` features so the test
|
||||
binaries keep the in-crate mocks (ffmpeg_bridge stub / render mocks):
|
||||
|
||||
- `oakcommon`/`oakplugin` use their `test-stubs` features (ffmpeg_bridge
|
||||
stub / in-crate render mocks).
|
||||
- `oaknode`/`oaktimeline`/`oaktask` are linked WITHOUT their `test-stubs`
|
||||
features: their in-crate mocks would collide with the real oakundo rlib
|
||||
in one test binary. Without test-stubs their real exports reference the
|
||||
oaknode/oakundo/oakcommon C ABI symbols as link-time externs, which the
|
||||
dev-dependency rlibs provide; oaknode itself resolves cross-module
|
||||
sibling crate rlibs provide; oaknode itself resolves cross-module
|
||||
symbols at runtime with `dlsym(RTLD_DEFAULT)`.
|
||||
- `tests/common/mod.rs` defines the `oakcore_*` (liboakcore) and `fb_*`
|
||||
(libffmpeg_bridge) symbols the oakcodec/oakaudio rlibs reference, and
|
||||
@@ -106,7 +113,9 @@ facade's bridge imports resolve:
|
||||
factory so the oaknode serializer's dlsym lookups resolve in every test
|
||||
binary.
|
||||
- `src/lib.rs`'s test-only `test_link` forces the oakrender/oaknode/
|
||||
oaktimeline/oaktask rlibs into the lib unit-test binary.
|
||||
oaktimeline/oaktask rlibs into the lib unit-test binary (the always-on
|
||||
`src/linkage.rs` anchors are `#[cfg(not(test))]`; they are what embeds
|
||||
the module C ABIs in the cdylib for `cargo build`).
|
||||
|
||||
Families whose wrapped behavior requires the real module dylibs carry
|
||||
`#[ignore]` tests with a documented reason; the smoke tests here exercise
|
||||
@@ -118,7 +127,8 @@ payloads in both directions, including wraparound and full/empty edges.
|
||||
cargo test # 71 tests green + 1 ignored (lib 35: ipc 17 + worker 18;
|
||||
# integration: undo 3, common 4, audio 4, plugin 3, codec 5,
|
||||
# render 6, linkage 1, node 3, timeline 2 + 1 ignored, task 5)
|
||||
cargo build # staticlib + rlib; module symbols resolve at the final app link
|
||||
cargo build # cdylib embeds the module C ABIs; oakcore_*/fb_* stay
|
||||
# runtime lookups (see build.rs)
|
||||
```
|
||||
|
||||
## FFI discipline
|
||||
@@ -0,0 +1,33 @@
|
||||
// Oak Video Editor - Non-Linear Video Editor
|
||||
// Copyright (C) 2026 Oak Team
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Build-time link configuration for the `liboakengine` cdylib.
|
||||
//!
|
||||
//! The dylib now carries the module C ABIs itself (oakundo_*, oakcommon_*,
|
||||
//! ... — see Cargo.toml), so the only remaining undefined imports are the
|
||||
//! C++ host-provided symbols the modules call directly: `oakcore_*`
|
||||
//! (liboakcore's `oakcore_audioparams_*` / `oakcore_rational_*`, called by
|
||||
//! oakcodec) and `fb_find_best_pix_fmt_of_list` (ffmpeg_bridge, called by
|
||||
//! oakcommon's pixel-format helper). Those live in the host Oak process,
|
||||
//! which loads this dylib, so macOS `ld` must accept them as runtime
|
||||
//! lookups instead of link-time errors. Only the cdylib gets this flag —
|
||||
//! the rlib/staticlib (and the worker/cli consumers) are unaffected.
|
||||
|
||||
fn main() {
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
|
||||
println!("cargo:rustc-cdylib-link-arg=-Wl,-undefined,dynamic_lookup");
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! # oakfacade — the `liboakengine` facade (Rust)
|
||||
//! # oakengine — the `liboakengine` facade (Rust)
|
||||
//!
|
||||
//! Re-exports the frozen `oakengine_*` C ABI
|
||||
//! (`engine/include/oakengine/*.h`) verbatim on top of the module C ABIs
|
||||
@@ -42,12 +42,17 @@
|
||||
//!
|
||||
//! ## Testing
|
||||
//!
|
||||
//! `cargo test` links the module crates' rlibs (dev-dependencies) so the
|
||||
//! bridge imports resolve; `tests/linkage.rs` references every crate to
|
||||
//! force rustc to pull the rlibs into the link. Where a wrapped family
|
||||
//! needs module behavior the crates do not implement yet, the engine
|
||||
//! function is a documented stub and its test carries `#[ignore]` with a
|
||||
//! reason (see README.md).
|
||||
//! The module crates are real dependencies (see Cargo.toml) and
|
||||
//! [`linkage`] anchors them into every link of this crate, so the module
|
||||
//! C ABIs are embedded in the `liboakengine` cdylib next to the facade's
|
||||
//! own exports. `cargo test` links the same crates' rlibs (plus the
|
||||
//! `test-stubs` feature union declared in the dev-dependencies, which
|
||||
//! compiles the oakcommon/oakplugin in-crate mocks); `tests/linkage.rs`
|
||||
//! additionally references every crate for the integration-test binaries
|
||||
//! and `test_link` (below) covers the unit-test binary. Where a wrapped
|
||||
//! family needs module behavior the crates do not implement yet, the
|
||||
//! engine function is a documented stub and its test carries `#[ignore]`
|
||||
//! with a reason (see README.md).
|
||||
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![warn(missing_docs)]
|
||||
@@ -60,6 +65,8 @@ pub mod deferred;
|
||||
pub mod error;
|
||||
pub mod handle;
|
||||
pub mod ipc;
|
||||
#[cfg(not(test))]
|
||||
pub mod linkage;
|
||||
pub mod node;
|
||||
pub mod plugin;
|
||||
pub mod render;
|
||||
@@ -0,0 +1,66 @@
|
||||
// Oak Video Editor - Non-Linear Video Editor
|
||||
// Copyright (C) 2026 Oak Team
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Linkage anchors — force the module crates' rlibs into every link.
|
||||
//!
|
||||
//! The facade talks to the modules exclusively through `extern "C"`
|
||||
//! imports (src/bridge/), so rustc would otherwise consider the module
|
||||
//! crates unused and prune their rlibs from the link. This module
|
||||
//! references one `#[no_mangle]` export of every module crate (and
|
||||
//! `oakcore-rs`) from a `#[used]` static, which (a) marks each crate as
|
||||
//! used so its rlib reaches the linker and (b) keeps the anchor alive so
|
||||
//! the referenced object files are pulled. For the `liboakengine` cdylib
|
||||
//! this is what actually embeds the module C ABIs (oakundo_*,
|
||||
//! oakcommon_*, ...) into the dylib next to the facade's own oakengine_*
|
||||
//! exports.
|
||||
//!
|
||||
//! The per-crate symbol mirrors the test-force-link in
|
||||
//! tests/common/mod.rs (same paths, same `as usize` cast idiom), so the
|
||||
//! crate/module paths are proven against the current module layouts.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
/// Pull every module crate into the link. Mirrors
|
||||
/// `tests/common/mod.rs::force_link`; the oakcommon XML/undo anchors are
|
||||
/// repeated because oaknode's serializer resolves those C ABI symbols at
|
||||
/// runtime via dlsym(RTLD_DEFAULT) and they must be present in the dylib
|
||||
/// for that lookup to succeed.
|
||||
fn force_link() -> usize {
|
||||
let fns: [usize; 13] = [
|
||||
// oakcore-rs (pure value types; referenced so its rlib is linked).
|
||||
oakcore_rs::Rational::new(1, 2).numerator() as usize,
|
||||
// One exported C ABI symbol per module crate.
|
||||
oakundo::ffi::undostack::oakundo_undostack_init as usize,
|
||||
oakcommon::ffi::config::oakcommon_config_get_int as usize,
|
||||
oaktimeline::ffi::marker::oaktimeline_marker_list_create as usize,
|
||||
oakcodec::ffi::format::oakcodec_encoding_format_count as usize,
|
||||
oakaudio::ffi::waveform::oakaudio_waveform_length as usize,
|
||||
oakrender::ffi::cache::oakrender_cache_indicator_height as usize,
|
||||
oaktask::ffi::manager::oaktask_manager_init as usize,
|
||||
oakplugin::ffi::oakplugin_host_plugin_count as usize,
|
||||
oaknode::ffi::project::oaknode_project_init as usize,
|
||||
// oaknode's dlsym(RTLD_DEFAULT) targets (see tests/common/mod.rs).
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_init as usize,
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_init as usize,
|
||||
oakundo::ffi::command::oakundo_command_init as usize,
|
||||
];
|
||||
fns.iter().sum()
|
||||
}
|
||||
|
||||
/// Keeps [`force_link`] (and through it every referenced export) alive in
|
||||
/// the cdylib/staticlib even though nothing calls it directly.
|
||||
#[used]
|
||||
static FORCE_LINK_ANCHOR: fn() -> usize = force_link;
|
||||
@@ -22,7 +22,7 @@
|
||||
#[path = "common/mod.rs"]
|
||||
mod common;
|
||||
|
||||
use oakfacade::audio::{
|
||||
use oakengine::audio::{
|
||||
oakengine_audio_clear_buffered_output, oakengine_audio_create_instance,
|
||||
oakengine_audio_destroy_instance, oakengine_audio_estimate_envelope_offset,
|
||||
oakengine_audio_get_output_device, oakengine_audio_hard_reset,
|
||||
@@ -22,7 +22,7 @@ mod common;
|
||||
|
||||
use std::ffi::{c_char, c_int};
|
||||
|
||||
use oakfacade::codec::{
|
||||
use oakengine::codec::{
|
||||
oakengine_encoding_codec_is_lossless, oakengine_encoding_codec_is_still_image,
|
||||
oakengine_encoding_codec_name, oakengine_encoding_filename_contains_digit_placeholder,
|
||||
oakengine_encoding_filename_remove_digit_placeholder, oakengine_encoding_format_audio_codec_count,
|
||||
@@ -45,7 +45,7 @@ use oakfacade::codec::{
|
||||
oakengine_encoding_params_video_pix_fmt, oakengine_encoding_params_set_video_pix_fmt,
|
||||
oakengine_encoding_pix_fmt_index,
|
||||
};
|
||||
use oakfacade::common::OakVideoParamsPod;
|
||||
use oakengine::common::OakVideoParamsPod;
|
||||
|
||||
/// Container format / codec metadata queries.
|
||||
#[test]
|
||||
@@ -142,7 +142,7 @@ fn params_handle_round_trip() {
|
||||
let mut vp: OakVideoParamsPod = unsafe { std::mem::zeroed() };
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
oakfacade::common::oakengine_video_params_make(&mut vp, 1920, 1080, 1001, 30000, 4, 1, 1, 0, 1, 1)
|
||||
oakengine::common::oakengine_video_params_make(&mut vp, 1920, 1080, 1001, 30000, 4, 1, 1, 0, 1, 1)
|
||||
},
|
||||
0
|
||||
);
|
||||
@@ -25,7 +25,7 @@ mod common;
|
||||
|
||||
use std::ffi::{c_char, c_int};
|
||||
|
||||
use oakfacade::common::{
|
||||
use oakengine::common::{
|
||||
oakengine_config_get_int, oakengine_config_get_string, oakengine_config_load,
|
||||
oakengine_config_save, oakengine_config_set_error_handler, oakengine_config_set_int,
|
||||
oakengine_config_set_string, oakengine_video_params_bytes_per_pixel,
|
||||
@@ -87,12 +87,12 @@ fn config_error_handler() {
|
||||
assert_eq!(unsafe { oakengine_config_set_error_handler(Some(handler), std::ptr::null_mut()) }, 0);
|
||||
// Report an error through the handler.
|
||||
assert_eq!(unsafe {
|
||||
oakfacade::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr())
|
||||
oakengine::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr())
|
||||
}, 0);
|
||||
assert_eq!(CALLED.load(std::sync::atomic::Ordering::SeqCst), 1);
|
||||
// NULL handler clears; reporting then does not invoke.
|
||||
assert_eq!(unsafe { oakengine_config_set_error_handler(None, std::ptr::null_mut()) }, 0);
|
||||
unsafe { oakfacade::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) };
|
||||
unsafe { oakengine::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) };
|
||||
assert_eq!(CALLED.load(std::sync::atomic::Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ mod common;
|
||||
|
||||
use std::ffi::{c_char, c_int};
|
||||
|
||||
use oakfacade::node::{
|
||||
use oakengine::node::{
|
||||
oakengine_footage_borrow, oakengine_footage_last_error, oakengine_footage_probe,
|
||||
oakengine_node_connect, oakengine_node_disconnect, oakengine_node_factory_create_from_id,
|
||||
oakengine_node_factory_id_count, oakengine_node_factory_name_from_id,
|
||||
@@ -83,7 +83,7 @@ fn float_value(x: f64) -> OakNodeValue {
|
||||
}
|
||||
|
||||
/// The index of the first project node whose type id matches `id`, or -1.
|
||||
unsafe fn find_node(project: *mut oakfacade::handle::OakEngineProject, id: &str) -> c_int {
|
||||
unsafe fn find_node(project: *mut oakengine::handle::OakEngineProject, id: &str) -> c_int {
|
||||
let count = unsafe { oakengine_project_node_count(project) };
|
||||
for i in 0..count {
|
||||
let node = unsafe { oakengine_project_node_at(project, i) };
|
||||
@@ -137,15 +137,15 @@ fn project_node_keyframe_lifecycle() {
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { oakengine_project_set_filename(project, c"/tmp/oakfacade_node_test.ovexml".as_ptr()) },
|
||||
unsafe { oakengine_project_set_filename(project, c"/tmp/oakengine_node_test.ovexml".as_ptr()) },
|
||||
0
|
||||
);
|
||||
let len = unsafe { oakengine_project_filename(project, buf.as_mut_ptr(), 256) };
|
||||
assert!(len > 0);
|
||||
assert!(unsafe { read_buf(&mut buf) }.ends_with("oakfacade_node_test.ovexml"));
|
||||
assert!(unsafe { read_buf(&mut buf) }.ends_with("oakengine_node_test.ovexml"));
|
||||
let len = unsafe { oakengine_project_name(project, buf.as_mut_ptr(), 256) };
|
||||
assert!(len > 0);
|
||||
assert_eq!(unsafe { read_buf(&mut buf) }, "oakfacade_node_test");
|
||||
assert_eq!(unsafe { read_buf(&mut buf) }, "oakengine_node_test");
|
||||
|
||||
// ---- factory + node creation ---------------------------------------
|
||||
let factory_count = oakengine_node_factory_id_count();
|
||||
@@ -269,9 +269,9 @@ fn project_node_keyframe_lifecycle() {
|
||||
assert!((at.f[0] - 0.5).abs() < 1e-6);
|
||||
|
||||
// ---- project save → fresh load round-trip ---------------------------
|
||||
let path = c"/tmp/oakfacade_node_test.ovexml";
|
||||
let path = c"/tmp/oakengine_node_test.ovexml";
|
||||
assert_eq!(unsafe { oakengine_project_save(project, path.as_ptr()) }, 0);
|
||||
assert!(std::path::Path::new("/tmp/oakfacade_node_test.ovexml").exists());
|
||||
assert!(std::path::Path::new("/tmp/oakengine_node_test.ovexml").exists());
|
||||
|
||||
unsafe { oakengine_project_free(project) };
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#[path = "common/mod.rs"]
|
||||
mod common;
|
||||
|
||||
use oakfacade::plugin::{
|
||||
use oakengine::plugin::{
|
||||
oakengine_plugin_load_plugins, oakengine_plugin_node_push_button_clicked,
|
||||
oakengine_plugin_set_active_viewer_provider, oakengine_plugin_set_progress_reporter_factory,
|
||||
};
|
||||
@@ -27,7 +27,7 @@ use oakfacade::plugin::{
|
||||
/// Callback registration round-trips (NULL clears).
|
||||
#[test]
|
||||
fn provider_registration() {
|
||||
unsafe extern "C" fn viewer(_userdata: *mut std::ffi::c_void) -> *mut oakfacade::handle::OakEngineNode {
|
||||
unsafe extern "C" fn viewer(_userdata: *mut std::ffi::c_void) -> *mut oakengine::handle::OakEngineNode {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
assert_eq!(unsafe {
|
||||
@@ -26,7 +26,7 @@ mod common;
|
||||
|
||||
use std::ffi::{c_char, c_double};
|
||||
|
||||
use oakfacade::render::{
|
||||
use oakengine::render::{
|
||||
oakengine_color_last_error, oakengine_color_manager_get_config_filename,
|
||||
oakengine_color_processor_convert_color, oakengine_color_processor_create,
|
||||
oakengine_color_processor_free, oakengine_color_processor_is_valid,
|
||||
@@ -34,11 +34,11 @@ mod common;
|
||||
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
|
||||
use oakfacade::node::{
|
||||
use oakengine::node::{
|
||||
oakengine_node_free, oakengine_project_create, oakengine_project_free, oakengine_project_new,
|
||||
oakengine_project_root, oakengine_project_set_filename,
|
||||
};
|
||||
use oakfacade::task::{
|
||||
use oakengine::task::{
|
||||
oakengine_cli_task_dialog_run, oakengine_task_cancel, oakengine_task_create_export,
|
||||
oakengine_task_create_project_import, oakengine_task_create_project_load,
|
||||
oakengine_task_create_project_load_otio, oakengine_task_create_project_save,
|
||||
@@ -187,7 +187,7 @@ fn project_task_lifecycle() {
|
||||
|
||||
// ---- save task on a real project → sync run writes the file ----------
|
||||
let save_path = std::env::temp_dir().join(format!(
|
||||
"oakfacade_task_save_{}.ovexml",
|
||||
"oakengine_task_save_{}.ovexml",
|
||||
std::process::id()
|
||||
));
|
||||
let save_c = std::ffi::CString::new(save_path.to_str().unwrap()).unwrap();
|
||||
@@ -217,7 +217,7 @@ fn project_task_lifecycle() {
|
||||
// project's own filename; NULL without one, a real task with one. ------
|
||||
assert!(unsafe { oakengine_task_create_project_save_otio(project) }.is_null());
|
||||
assert_eq!(
|
||||
unsafe { oakengine_project_set_filename(project, c"/tmp/oakfacade_task_otio.otio".as_ptr()) },
|
||||
unsafe { oakengine_project_set_filename(project, c"/tmp/oakengine_task_otio.otio".as_ptr()) },
|
||||
0
|
||||
);
|
||||
let otio_task = unsafe { oakengine_task_create_project_save_otio(project) };
|
||||
@@ -31,12 +31,12 @@ mod common;
|
||||
|
||||
use std::ffi::{c_char, c_int};
|
||||
|
||||
use oakfacade::handle::{box_handle, OakEngineNode};
|
||||
use oakfacade::node::{
|
||||
use oakengine::handle::{box_handle, OakEngineNode};
|
||||
use oakengine::node::{
|
||||
oakengine_footage_borrow, oakengine_project_create, oakengine_project_free,
|
||||
oakengine_project_new,
|
||||
};
|
||||
use oakfacade::timeline::{
|
||||
use oakengine::timeline::{
|
||||
oakengine_block_get_range, oakengine_block_get_track, oakengine_block_is_enabled,
|
||||
oakengine_block_is_gap, oakengine_block_link_count, oakengine_block_next,
|
||||
oakengine_block_prev, oakengine_block_set_enabled, oakengine_block_set_length_and_media_out,
|
||||
@@ -103,7 +103,7 @@ fn force_runtime_syms() -> usize {
|
||||
|
||||
/// Convert a facade `CHandle` to the layout-identical oaknode `CHandle`
|
||||
/// (distinct Rust types over the same C ABI struct).
|
||||
fn to_node_handle(h: oakfacade::handle::CHandle) -> oaknode::handle::CHandle {
|
||||
fn to_node_handle(h: oakengine::handle::CHandle) -> oaknode::handle::CHandle {
|
||||
oaknode::handle::CHandle {
|
||||
ctx: h.ctx,
|
||||
addref: h.addref,
|
||||
@@ -113,8 +113,8 @@ fn to_node_handle(h: oakfacade::handle::CHandle) -> oaknode::handle::CHandle {
|
||||
}
|
||||
|
||||
/// Convert an oaknode `CHandle` back to the facade `CHandle`.
|
||||
fn to_facade_handle(h: oaknode::handle::CHandle) -> oakfacade::handle::CHandle {
|
||||
oakfacade::handle::CHandle {
|
||||
fn to_facade_handle(h: oaknode::handle::CHandle) -> oakengine::handle::CHandle {
|
||||
oakengine::handle::CHandle {
|
||||
ctx: h.ctx,
|
||||
addref: h.addref,
|
||||
release: h.release,
|
||||
@@ -28,7 +28,7 @@ mod common;
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
use oakfacade::undo::{
|
||||
use oakengine::undo::{
|
||||
oakengine_undo_can_redo, oakengine_undo_can_undo, oakengine_undo_clear,
|
||||
oakengine_undo_command_create, oakengine_undo_command_create_multi,
|
||||
oakengine_undo_command_free, oakengine_undo_command_multi_add_child,
|
||||
@@ -53,16 +53,28 @@ static CMD_FREE_COUNT: AtomicI32 = AtomicI32::new(0);
|
||||
static STK_REDO_COUNT: AtomicI32 = AtomicI32::new(0);
|
||||
static STK_UNDO_COUNT: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
/// Stack-test callbacks: bump only the `STK_*` counters. They must not
|
||||
/// touch the `CMD_*` counters — the command-lifecycle tests reset and
|
||||
/// assert those in parallel threads, so a stray bump here would race.
|
||||
unsafe extern "C" fn redo_cb(_userdata: *mut c_void) {
|
||||
CMD_REDO_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
STK_REDO_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn undo_cb(_userdata: *mut c_void) {
|
||||
CMD_UNDO_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
STK_UNDO_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Command-lifecycle-only callbacks: bump only the `CMD_*` counters. The
|
||||
/// serialized stack test runs in a parallel thread and must not flip
|
||||
/// these.
|
||||
unsafe extern "C" fn cmd_redo_cb(_userdata: *mut c_void) {
|
||||
CMD_REDO_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cmd_undo_cb(_userdata: *mut c_void) {
|
||||
CMD_UNDO_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn free_cb(_userdata: *mut c_void) {
|
||||
CMD_FREE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
@@ -77,8 +89,8 @@ fn command_create_redo_undo_free() {
|
||||
let cmd = unsafe {
|
||||
oakengine_undo_command_create(
|
||||
c"custom".as_ptr(),
|
||||
Some(redo_cb),
|
||||
Some(undo_cb),
|
||||
Some(cmd_redo_cb),
|
||||
Some(cmd_undo_cb),
|
||||
Some(free_cb),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
@@ -108,8 +120,8 @@ fn multi_command_add_child_count_redo() {
|
||||
let child = unsafe {
|
||||
oakengine_undo_command_create(
|
||||
c"child".as_ptr(),
|
||||
Some(redo_cb),
|
||||
Some(undo_cb),
|
||||
Some(cmd_redo_cb),
|
||||
Some(cmd_undo_cb),
|
||||
None,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
@@ -1,55 +0,0 @@
|
||||
[package]
|
||||
name = "oakfacade"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Oak Video Editor facade: re-exports the frozen oakengine_* C ABI over the module C ABIs (Rust)"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
# FFI discipline: panics must be catchable at every exported entry.
|
||||
panic = "unwind"
|
||||
|
||||
[dependencies]
|
||||
# NDJSON control-plane protocol for the worker session (src/worker.rs) and
|
||||
# the shm error formatting in src/ipc.rs.
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
# POSIX shm_open/mmap/munmap/shm_unlink constants + syscalls for the
|
||||
# shared-memory frame-slot transport (src/ipc.rs).
|
||||
libc = "0.2"
|
||||
|
||||
# Every module call crosses the module C ABI as an `extern "C"` import
|
||||
# (src/bridge/), resolved at the final link against the module shared
|
||||
# libraries (see README.md).
|
||||
#
|
||||
# `cargo test` links the module crates' rlibs instead (dev-dependencies
|
||||
# below): the crates' `#[no_mangle]` exports satisfy the facade's bridge
|
||||
# imports, so the smoke tests exercise the real module code where the
|
||||
# crates implement it. Tests reference every crate so rustc pulls the
|
||||
# rlibs into the link (see tests/common/mod.rs).
|
||||
#
|
||||
# test-stubs on oakcommon/oakplugin compiles those crates' in-crate C ABI
|
||||
# mocks: oakcommon's stub replaces the ffmpeg_bridge symbol, and
|
||||
# oakplugin's stubs replace its runtime dlsym lookups.
|
||||
#
|
||||
# NOTE (oaktimeline/oaktask): linked WITHOUT their `test-stubs` features.
|
||||
# Their in-crate mocks define `oakundo_command_init` etc., which would
|
||||
# collide with the real oakundo rlib in one test binary; without
|
||||
# test-stubs their real exports reference the oaknode/oakundo/oakcommon
|
||||
# C ABI symbols as link-time externs, which the dev-dependency rlibs
|
||||
# (oaknode, oakundo, oakcommon[test-stubs]) provide. oaknode itself
|
||||
# resolves cross-module symbols at runtime with dlsym(RTLD_DEFAULT), which
|
||||
# finds the linked rlibs in the test binary (see src/bridge/node.rs).
|
||||
[dev-dependencies]
|
||||
oakundo = { path = "../../undo/rust" }
|
||||
oakcodec = { path = "../../codec/rust" }
|
||||
oakaudio = { path = "../../audio/rust" }
|
||||
oakrender = { path = "../../render/rust" }
|
||||
oakcommon = { path = "../../common/rust", features = ["test-stubs"] }
|
||||
oakplugin = { path = "../../plugin/rust", features = ["test-stubs"] }
|
||||
oaknode = { path = "../../node/rust" }
|
||||
oaktimeline = { path = "../../timeline/rust" }
|
||||
oaktask = { path = "../../task/rust" }
|
||||
Reference in New Issue
Block a user