refactor(oakengine): absorb oakcore host symbols into the dylib
The 'host-provided' oakcore_audioparams_* runtime imports dated from the deleted C++ host; the facade is their only caller. The dylib now defines and exports the six symbols itself (repr(C) AudioParams mirror, liboakcore-compatible semantics), -Wl,-undefined,dynamic_lookup is gone, and the Windows DLL undefined-symbol blocker is removed by construction (Windows CI/packaging stays off until a real toolchain verifies links).
This commit is contained in:
@@ -125,6 +125,9 @@ image = "0.25"
|
|||||||
# `icons/icon.png` is NOT committed: it is generated from `Oak_Icon.svg`
|
# `icons/icon.png` is NOT committed: it is generated from `Oak_Icon.svg`
|
||||||
# (rsvg-convert) in CD right before packaging, because cargo-packager needs a
|
# (rsvg-convert) in CD right before packaging, because cargo-packager needs a
|
||||||
# bitmap (it converts a square PNG into .icns / .ico / hicolor PNGs itself).
|
# bitmap (it converts a square PNG into .icns / .ico / hicolor PNGs itself).
|
||||||
|
# It must be 512x512: cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x
|
||||||
|
# (and 1024x1024@2x) to an ICNS type — a plain 1024x1024 PNG aborts with
|
||||||
|
# "No matching IconType".
|
||||||
# Keep the path here in sync with the CD workflow.
|
# Keep the path here in sync with the CD workflow.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
[package.metadata.packager]
|
[package.metadata.packager]
|
||||||
|
|||||||
@@ -36,15 +36,18 @@
|
|||||||
//! `target/<profile>/deps/`, so dyld finds it by that absolute path at
|
//! `target/<profile>/deps/`, so dyld finds it by that absolute path at
|
||||||
//! load time; the `-rpath` flag covers `@rpath`-relative configurations.
|
//! load time; the `-rpath` flag covers `@rpath`-relative configurations.
|
||||||
//!
|
//!
|
||||||
//! Linux: undefined symbols in a `.so` need no link-time flag; the app's
|
//! Linux: the app's own link needs the search path plus
|
||||||
//! own link only needs the search path plus `-Wl,--export-dynamic` (the
|
//! `-Wl,--export-dynamic` (the ELF equivalent of `-export_dynamic`) so
|
||||||
//! ELF equivalent of `-export_dynamic`) so the host symbols resolve from
|
//! process-global symbol lookups resolve from the binary at runtime. An
|
||||||
//! the binary at runtime. An `$ORIGIN`-relative rpath lets a packaged
|
//! `$ORIGIN`-relative rpath lets a packaged binary find a sibling
|
||||||
//! binary find a sibling `liboakengine.so`.
|
//! `liboakengine.so`.
|
||||||
//!
|
//!
|
||||||
//! Windows: NOT SUPPORTED YET — a DLL cannot carry the undefined
|
//! Windows: the engine dylib now links there — the `oakcore_*` runtime
|
||||||
//! `oakcore_*` imports (they need a stub import library or delay-load
|
//! imports were folded into the cdylib in M12 P5 (crates/oakengine/src/
|
||||||
//! plumbing that does not exist yet), so the app does not link there.
|
//! stubs.rs, module `audio`), so `liboakengine.dll` carries no undefined
|
||||||
|
//! symbols. The app binary itself still has no Windows link
|
||||||
|
//! configuration here and the early return stays; that is a separate
|
||||||
|
//! effort (gpui win32 support).
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ libc = "0.2"
|
|||||||
# oakaudio/oakplugin, and clearly marked STUBs for the handle-based
|
# oakaudio/oakplugin, and clearly marked STUBs for the handle-based
|
||||||
# oaknode/oaktimeline paths).
|
# oaknode/oaktimeline paths).
|
||||||
#
|
#
|
||||||
# The remaining undefined imports are the C++ host-provided symbols
|
# The `oakcore_audioparams_*` accessors the audio paths read through are
|
||||||
# (`oakcore_audioparams_*` from liboakcore), which build.rs leaves as
|
# implemented inside the dylib too (src/stubs.rs, module `audio`, M12 P5):
|
||||||
# runtime lookups for the host app (see src/stubs.rs audio module).
|
# they used to be C++ liboakcore host-provided symbols left as runtime
|
||||||
|
# lookups, which blocked Windows DLL linking.
|
||||||
#
|
#
|
||||||
# src/linkage.rs anchors every crate (and oakcore-rs) so the linker pulls
|
# src/linkage.rs anchors every crate (and oakcore-rs) so the linker pulls
|
||||||
# their object files into the cdylib.
|
# their object files into the cdylib.
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ anchors them so their `#[no_mangle]` exports are linked into the
|
|||||||
`liboakengine` cdylib — the dylib carries the module C ABIs (oakundo_*,
|
`liboakengine` cdylib — the dylib carries the module C ABIs (oakundo_*,
|
||||||
oakcommon_*, oaktimeline_*, oakcodec_*, oakaudio_*, oakrender_*,
|
oakcommon_*, oaktimeline_*, oakcodec_*, oakaudio_*, oakrender_*,
|
||||||
oaktask_*, oakplugin_*, oaknode_*) next to the facade's oakengine_*
|
oaktask_*, oakplugin_*, oaknode_*) next to the facade's oakengine_*
|
||||||
exports. The only remaining imports are the C++ host symbols
|
exports. The `oakcore_audioparams_*` accessors the audio paths read
|
||||||
(`oakcore_*` from liboakcore, `fb_*` from ffmpeg_bridge), which
|
through are implemented in the dylib too (src/stubs.rs, module `audio`,
|
||||||
`build.rs` leaves as runtime lookups (macOS `-undefined dynamic_lookup`)
|
M12 P5) — they used to be C++ host symbols left as runtime lookups
|
||||||
resolved from the host Oak process.
|
(macOS `-undefined dynamic_lookup`); the cdylib now carries no undefined
|
||||||
|
imports.
|
||||||
|
|
||||||
### Handle mapping
|
### Handle mapping
|
||||||
|
|
||||||
@@ -103,8 +104,8 @@ binaries keep the in-crate mocks (ffmpeg_bridge stub / render mocks):
|
|||||||
oaknode/oakundo/oakcommon C ABI symbols as link-time externs, which the
|
oaknode/oakundo/oakcommon C ABI symbols as link-time externs, which the
|
||||||
sibling crate rlibs provide; oaknode itself resolves cross-module
|
sibling crate rlibs provide; oaknode itself resolves cross-module
|
||||||
symbols at runtime with `dlsym(RTLD_DEFAULT)`.
|
symbols at runtime with `dlsym(RTLD_DEFAULT)`.
|
||||||
- `tests/common/mod.rs` defines the `oakcore_*` (liboakcore) and `fb_*`
|
- `tests/common/mod.rs` re-exports the facade's in-dylib
|
||||||
(libffmpeg_bridge) symbols the oakcodec/oakaudio rlibs reference, and
|
`oakcore_audioparams_*` accessors (M12 P5 folded them in) and
|
||||||
force-links the oakcommon XML writer/reader + the oakundo command
|
force-links the oakcommon XML writer/reader + the oakundo command
|
||||||
factory so the oaknode serializer's dlsym lookups resolve in every test
|
factory so the oaknode serializer's dlsym lookups resolve in every test
|
||||||
binary.
|
binary.
|
||||||
@@ -120,8 +121,8 @@ the module crates' real implementations.
|
|||||||
```
|
```
|
||||||
cargo test # lib tests + integration families (undo, common, audio,
|
cargo test # lib tests + integration families (undo, common, audio,
|
||||||
# plugin, codec, render, linkage, node, timeline, task)
|
# plugin, codec, render, linkage, node, timeline, task)
|
||||||
cargo build # cdylib embeds the module C ABIs; oakcore_*/fb_* stay
|
cargo build # cdylib embeds the module C ABIs + the folded-in
|
||||||
# runtime lookups (see build.rs)
|
# oakcore_audioparams_* accessors (no undefined imports)
|
||||||
```
|
```
|
||||||
|
|
||||||
## FFI discipline
|
## FFI discipline
|
||||||
|
|||||||
+27
-25
@@ -16,39 +16,41 @@
|
|||||||
|
|
||||||
//! Build-time link configuration for the `liboakengine` cdylib.
|
//! Build-time link configuration for the `liboakengine` cdylib.
|
||||||
//!
|
//!
|
||||||
//! The dylib now carries the module C ABIs itself (oakundo_*, oakcommon_*,
|
//! The dylib carries the module C ABIs itself (oakundo_*, oakcommon_*, ...
|
||||||
//! ... — see Cargo.toml), so the only remaining undefined imports are the
|
//! — see Cargo.toml). The `oakcore_audioparams_*` accessors the audio
|
||||||
//! C++ host-provided symbols the modules call directly: `oakcore_*`
|
//! paths read through used to be host-provided C++ liboakcore symbols,
|
||||||
//! (liboakcore's `oakcore_audioparams_*` / `oakcore_rational_*`, called by
|
//! left as runtime lookups via `-undefined,dynamic_lookup`; M12 P5
|
||||||
//! oakcodec) and `fb_find_best_pix_fmt_of_list` (ffmpeg_bridge, called by
|
//! implemented them inside the dylib (src/stubs.rs, module `audio`), so
|
||||||
//! oakcommon's pixel-format helper). Those live in the host Oak process,
|
//! no undefined imports remain except system frameworks/libc++, and the
|
||||||
//! which loads this dylib, so macOS `ld` must accept them as runtime
|
//! cdylib links on every platform (Windows DLLs reject undefined symbols,
|
||||||
//! lookups instead of link-time errors. Only the cdylib gets this flag —
|
//! which was the blocker).
|
||||||
//! the rlib/staticlib (and the worker/cli consumers) are unaffected.
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
|
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||||
println!("cargo:rustc-cdylib-link-arg=-Wl,-undefined,dynamic_lookup");
|
if os == "macos" {
|
||||||
// The static FFmpeg's transitive system deps (libz etc.) are
|
// The static FFmpeg's transitive system deps (libz etc.) are
|
||||||
// recorded as `@rpath/libz.1.dylib`; the dylib itself carries the
|
// recorded as `@rpath/libz.1.dylib`; the dylib itself carries the
|
||||||
// rpath so standalone binaries (and the packaged app) resolve
|
// rpath so standalone binaries (and the packaged app) resolve
|
||||||
// them without extra host rpaths.
|
// them without extra host rpaths.
|
||||||
println!("cargo:rustc-cdylib-link-arg=-Wl,-rpath,/usr/lib");
|
println!("cargo:rustc-cdylib-link-arg=-Wl,-rpath,/usr/lib");
|
||||||
}
|
}
|
||||||
// The dlsym codec bridge (M12 P0) resolves `oakcodec_*` from the
|
if os == "macos" || os == "linux" {
|
||||||
// process-global scope; the engine's unit-test binary (the former
|
// The dlsym codec bridge (M12 P0) resolves `oakcodec_*` from the
|
||||||
// integration tests live in src/test_support/) statically links the
|
// process-global scope; the engine's unit-test binary (the former
|
||||||
// module crates, so their symbols must be exported from the test
|
// integration tests live in src/test_support/) statically links the
|
||||||
// executable. `cargo:rustc-link-arg-tests` is NOT usable: the facade
|
// module crates, so their symbols must be exported from the test
|
||||||
// is cdylib-only, so cargo reports "does not have a test target" for
|
// executable. `cargo:rustc-link-arg-tests` is NOT usable: the facade
|
||||||
// that directive — use the generic `rustc-link-arg` (harmless no-op
|
// is cdylib-only, so cargo reports "does not have a test target" for
|
||||||
// for the cdylib link itself).
|
// that directive — use the generic `rustc-link-arg` (a no-op for the
|
||||||
println!("cargo:rustc-link-arg=-Wl,-export_dynamic");
|
// cdylib link itself, and not emitted on Windows where the flag is
|
||||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
|
// meaningless and would break the DLL link).
|
||||||
// The bundled OpenColorIO's macos system monitor references
|
println!("cargo:rustc-link-arg=-Wl,-export_dynamic");
|
||||||
// IOKit / ColorSync / CoreGraphics display APIs; the engine
|
}
|
||||||
// dylib links with `-undefined,dynamic_lookup`, so test binaries
|
if os == "macos" {
|
||||||
// must resolve them.
|
// The bundled OpenColorIO's macOS system monitor references
|
||||||
|
// IOKit / ColorSync / CoreGraphics display APIs; link them for
|
||||||
|
// the cdylib link and the unit-test binary (which statically
|
||||||
|
// pulls the same OCIO rlib).
|
||||||
for fw in ["IOKit", "ColorSync", "CoreGraphics"] {
|
for fw in ["IOKit", "ColorSync", "CoreGraphics"] {
|
||||||
println!("cargo:rustc-link-arg=-framework");
|
println!("cargo:rustc-link-arg=-framework");
|
||||||
println!("cargo:rustc-link-arg={fw}");
|
println!("cargo:rustc-link-arg={fw}");
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
//! through [`manager()`] (a borrowed handle; empty when no instance
|
//! through [`manager()`] (a borrowed handle; empty when no instance
|
||||||
//! exists — engine semantics then report `paNoDevice`/error as
|
//! exists — engine semantics then report `paNoDevice`/error as
|
||||||
//! documented). The borrowed `OakAudioParams*` handles are read through
|
//! documented). The borrowed `OakAudioParams*` handles are read through
|
||||||
//! the liboakcore `oakcore_audioparams_*` accessors.
|
//! the `oakcore_audioparams_*` accessors the facade provides itself
|
||||||
|
//! (crate::stubs::audio, folded in M12 P5 — they used to be host-provided
|
||||||
|
//! liboakcore symbols).
|
||||||
|
|
||||||
use std::ffi::{c_char, c_double, c_int, c_void};
|
use std::ffi::{c_char, c_double, c_int, c_void};
|
||||||
|
|
||||||
@@ -126,6 +128,58 @@ pub extern "C" fn oakengine_audio_set_input_device(device: i64) -> c_int {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `oakengine_audio_output_device_count` — the number of host output
|
||||||
|
/// devices; the list index is the device index
|
||||||
|
/// `oakengine_audio_set_output_device` takes. Needs no AudioManager
|
||||||
|
/// instance.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakengine_audio_output_device_count() -> c_int {
|
||||||
|
guard_int(|| Ok(unsafe { a::oakaudio_output_device_count() }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakengine_audio_input_device_count` — the input side of
|
||||||
|
/// [`oakengine_audio_output_device_count`].
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakengine_audio_input_device_count() -> c_int {
|
||||||
|
guard_int(|| Ok(unsafe { a::oakaudio_input_device_count() }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakengine_audio_output_device_name` — the name of output device
|
||||||
|
/// `index` (buf/size; the length excludes the NUL).
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn oakengine_audio_output_device_name(
|
||||||
|
index: c_int,
|
||||||
|
buf: *mut c_char,
|
||||||
|
buf_size: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
guard_int(|| unsafe {
|
||||||
|
let rc = a::oakaudio_output_device_name(index, buf, buf_size);
|
||||||
|
if rc < 0 {
|
||||||
|
Err(Error::Module(rc))
|
||||||
|
} else {
|
||||||
|
Ok(crate::handle::string_result(rc))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakengine_audio_input_device_name` — the input side of
|
||||||
|
/// [`oakengine_audio_output_device_name`].
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn oakengine_audio_input_device_name(
|
||||||
|
index: c_int,
|
||||||
|
buf: *mut c_char,
|
||||||
|
buf_size: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
guard_int(|| unsafe {
|
||||||
|
let rc = a::oakaudio_input_device_name(index, buf, buf_size);
|
||||||
|
if rc < 0 {
|
||||||
|
Err(Error::Module(rc))
|
||||||
|
} else {
|
||||||
|
Ok(crate::handle::string_result(rc))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// `oakengine_audio_hard_reset` — re-initialize PortAudio and refresh the
|
/// `oakengine_audio_hard_reset` — re-initialize PortAudio and refresh the
|
||||||
/// device lists.
|
/// device lists.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|||||||
+181
-25
@@ -3944,7 +3944,7 @@ pub mod node {
|
|||||||
if params.is_null() {
|
if params.is_null() {
|
||||||
return OAKNODE_E_INVALID;
|
return OAKNODE_E_INVALID;
|
||||||
}
|
}
|
||||||
// SAFETY: the host liboakcore audioparams contract.
|
// SAFETY: the oakcore audioparams contract.
|
||||||
let (sample_rate, channel_layout, format) = unsafe {
|
let (sample_rate, channel_layout, format) = unsafe {
|
||||||
(
|
(
|
||||||
crate::stubs::audio::oakcore_audioparams_sample_rate(params),
|
crate::stubs::audio::oakcore_audioparams_sample_rate(params),
|
||||||
@@ -6064,7 +6064,7 @@ pub mod node {
|
|||||||
Some(p) => *p,
|
Some(p) => *p,
|
||||||
None => return OAKNODE_E_NOT_FOUND,
|
None => return OAKNODE_E_NOT_FOUND,
|
||||||
};
|
};
|
||||||
// SAFETY: the host liboakcore audioparams contract.
|
// SAFETY: the oakcore audioparams contract.
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
crate::stubs::audio::oakcore_audioparams_create(
|
crate::stubs::audio::oakcore_audioparams_create(
|
||||||
params.sample_rate,
|
params.sample_rate,
|
||||||
@@ -6089,7 +6089,7 @@ pub mod node {
|
|||||||
if params.is_null() || index < 0 {
|
if params.is_null() || index < 0 {
|
||||||
return OAKNODE_E_INVALID;
|
return OAKNODE_E_INVALID;
|
||||||
}
|
}
|
||||||
// SAFETY: the host liboakcore audioparams contract.
|
// SAFETY: the oakcore audioparams contract.
|
||||||
let converted = unsafe {
|
let converted = unsafe {
|
||||||
oaknode::value::AudioParams {
|
oaknode::value::AudioParams {
|
||||||
sample_rate: crate::stubs::audio::oakcore_audioparams_sample_rate(params),
|
sample_rate: crate::stubs::audio::oakcore_audioparams_sample_rate(params),
|
||||||
@@ -11291,9 +11291,11 @@ pub mod timeline {
|
|||||||
/// bridge functions; the manager CHandle is a borrow-only validity token
|
/// bridge functions; the manager CHandle is a borrow-only validity token
|
||||||
/// (the module owns the process-wide singleton), the processor CHandle
|
/// (the module owns the process-wide singleton), the processor CHandle
|
||||||
/// boxes an owned `oakaudio::processor::Processor` with a release
|
/// boxes an owned `oakaudio::processor::Processor` with a release
|
||||||
/// callback. The liboakcore `oakcore_audioparams_*` readers keep their
|
/// callback. The `oakcore_audioparams_*` C ABI (opaque `AudioParams`
|
||||||
/// `extern "C"` declarations — those are host-app-provided symbols
|
/// handles in the frozen engine contract) is implemented here, inside the
|
||||||
/// (liboakcore), not module C ABI.
|
/// dylib — the accessors were host-provided (C++ liboakcore, later Rust
|
||||||
|
/// mock shims in the app/cli/test binaries) until M12 P5 folded them in
|
||||||
|
/// so the cdylib carries no undefined imports.
|
||||||
pub mod audio {
|
pub mod audio {
|
||||||
use std::ffi::{c_char, c_double, c_int, c_void, CStr};
|
use std::ffi::{c_char, c_double, c_int, c_void, CStr};
|
||||||
|
|
||||||
@@ -11315,23 +11317,116 @@ pub mod audio {
|
|||||||
StretchOffsetResult,
|
StretchOffsetResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern "C" {
|
/// The liboakcore `AudioParams` object layout
|
||||||
/// `oakcore_audioparams_create` (host liboakcore).
|
/// (`oakcore_audioparams.h`): sample rate, ffmpeg channel-layout mask,
|
||||||
pub fn oakcore_audioparams_create(
|
/// sample format, stream index, duration and time base. Mirrors
|
||||||
sample_rate: c_int,
|
/// `olive::core::AudioParams` (and the app/cli mock shims it replaces),
|
||||||
channel_layout: u64,
|
/// so the engine contract's borrowed/owned `AudioParams*` handles
|
||||||
format: c_int,
|
/// round-trip unchanged.
|
||||||
) -> *mut c_void;
|
#[repr(C)]
|
||||||
/// `oakcore_audioparams_free` (host liboakcore).
|
struct HostAudioParams {
|
||||||
pub fn oakcore_audioparams_free(params: *mut c_void);
|
sample_rate: c_int,
|
||||||
/// `oakcore_audioparams_sample_rate` (host liboakcore).
|
channel_layout: u64,
|
||||||
pub fn oakcore_audioparams_sample_rate(params: *const c_void) -> c_int;
|
format: c_int,
|
||||||
/// `oakcore_audioparams_set_time_base` (host liboakcore).
|
stream_index: c_int,
|
||||||
pub fn oakcore_audioparams_set_time_base(params: *mut c_void, num: c_int, den: c_int);
|
duration: i64,
|
||||||
/// `oakcore_audioparams_channel_layout` (host liboakcore).
|
time_base_num: c_int,
|
||||||
pub fn oakcore_audioparams_channel_layout(params: *const c_void) -> u64;
|
time_base_den: c_int,
|
||||||
/// `oakcore_audioparams_format` (host liboakcore).
|
}
|
||||||
pub fn oakcore_audioparams_format(params: *const c_void) -> c_int;
|
|
||||||
|
/// `oakcore_audioparams_create` — new owned audio params (release with
|
||||||
|
/// `oakcore_audioparams_free`). The time base defaults to 1/sample_rate,
|
||||||
|
/// exactly like the liboakcore constructor.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakcore_audioparams_create(
|
||||||
|
sample_rate: c_int,
|
||||||
|
channel_layout: u64,
|
||||||
|
format: c_int,
|
||||||
|
) -> *mut c_void {
|
||||||
|
let den = if sample_rate > 0 { sample_rate } else { 1 };
|
||||||
|
Box::into_raw(Box::new(HostAudioParams {
|
||||||
|
sample_rate,
|
||||||
|
channel_layout,
|
||||||
|
format,
|
||||||
|
stream_index: 0,
|
||||||
|
duration: 0,
|
||||||
|
time_base_num: 1,
|
||||||
|
time_base_den: den,
|
||||||
|
})) as *mut c_void
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakcore_audioparams_free` — NULL no-op.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `params` must come from [`oakcore_audioparams_create`] or be NULL.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakcore_audioparams_free(params: *mut c_void) {
|
||||||
|
if params.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// SAFETY: produced by `oakcore_audioparams_create`; we hold the only
|
||||||
|
// reference after the box is dropped.
|
||||||
|
unsafe { drop(Box::from_raw(params as *mut HostAudioParams)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakcore_audioparams_sample_rate` — 0 for NULL (liboakcore contract).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `params` must come from [`oakcore_audioparams_create`] or be NULL.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakcore_audioparams_sample_rate(params: *const c_void) -> c_int {
|
||||||
|
if params.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// SAFETY: contract above.
|
||||||
|
unsafe { (*(params as *const HostAudioParams)).sample_rate }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakcore_audioparams_channel_layout` — 0 for NULL.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `params` must come from [`oakcore_audioparams_create`] or be NULL.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakcore_audioparams_channel_layout(params: *const c_void) -> u64 {
|
||||||
|
if params.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// SAFETY: contract above.
|
||||||
|
unsafe { (*(params as *const HostAudioParams)).channel_layout }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakcore_audioparams_format` — 0 for NULL.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `params` must come from [`oakcore_audioparams_create`] or be NULL.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakcore_audioparams_format(params: *const c_void) -> c_int {
|
||||||
|
if params.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// SAFETY: contract above.
|
||||||
|
unsafe { (*(params as *const HostAudioParams)).format }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `oakcore_audioparams_set_time_base` — NULL no-op.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `params` must come from [`oakcore_audioparams_create`] or be NULL.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn oakcore_audioparams_set_time_base(
|
||||||
|
params: *mut c_void,
|
||||||
|
num: c_int,
|
||||||
|
den: c_int,
|
||||||
|
) {
|
||||||
|
if params.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// SAFETY: contract above.
|
||||||
|
unsafe {
|
||||||
|
let p = &mut *(params as *mut HostAudioParams);
|
||||||
|
p.time_base_num = num;
|
||||||
|
p.time_base_den = den;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map an oakaudio `Box<dyn Error>` result to its public module code
|
/// Map an oakaudio `Box<dyn Error>` result to its public module code
|
||||||
@@ -11546,6 +11641,67 @@ pub mod audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of host output devices (enumeration order == the device
|
||||||
|
/// index `set_output_device` takes). Needs no manager instance.
|
||||||
|
pub fn oakaudio_output_device_count() -> c_int {
|
||||||
|
oakaudio::manager::output_device_names().len() as c_int
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of host input devices (see [`oakaudio_output_device_count`]).
|
||||||
|
pub fn oakaudio_input_device_count() -> c_int {
|
||||||
|
oakaudio::manager::input_device_names().len() as c_int
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The name of output device `index` (two-stage buf/size; reports the
|
||||||
|
/// required size INCLUDING the NUL, the module convention the facade
|
||||||
|
/// converts with `string_result`).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `buf` must point to `buf_size` writable bytes when non-NULL.
|
||||||
|
pub unsafe fn oakaudio_output_device_name(
|
||||||
|
index: c_int,
|
||||||
|
buf: *mut c_char,
|
||||||
|
buf_size: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
unsafe { device_name_result(oakaudio::manager::output_device_names(), index, buf, buf_size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The name of input device `index` (see [`oakaudio_output_device_name`]).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `buf` must point to `buf_size` writable bytes when non-NULL.
|
||||||
|
pub unsafe fn oakaudio_input_device_name(
|
||||||
|
index: c_int,
|
||||||
|
buf: *mut c_char,
|
||||||
|
buf_size: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
unsafe { device_name_result(oakaudio::manager::input_device_names(), index, buf, buf_size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared two-stage string write for the device-name getters: reports
|
||||||
|
/// `len + 1` (the required buffer size including the NUL), writes the
|
||||||
|
/// NUL-terminated name when it fits. `OAKAUDIO_E_NOT_FOUND` for an
|
||||||
|
/// out-of-range index.
|
||||||
|
unsafe fn device_name_result(
|
||||||
|
names: Vec<String>,
|
||||||
|
index: c_int,
|
||||||
|
buf: *mut c_char,
|
||||||
|
buf_size: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
let Some(name) = names.get(index.max(0) as usize).filter(|_| index >= 0) else {
|
||||||
|
return OAKAUDIO_E_NOT_FOUND;
|
||||||
|
};
|
||||||
|
// SAFETY: `buf` points to `buf_size` writable bytes when non-NULL.
|
||||||
|
unsafe {
|
||||||
|
if !buf.is_null() && buf_size > 0 {
|
||||||
|
let copy = name.len().min((buf_size as usize).saturating_sub(1));
|
||||||
|
std::ptr::copy_nonoverlapping(name.as_ptr(), buf as *mut u8, copy);
|
||||||
|
*buf.add(copy) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name.len() as c_int + 1
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the output stream and reset the playback state.
|
/// Close the output stream and reset the playback state.
|
||||||
pub fn oakaudio_manager_hard_reset(_self: CHandle) -> c_int {
|
pub fn oakaudio_manager_hard_reset(_self: CHandle) -> c_int {
|
||||||
match with_manager(_self).and_then(|mut m| m.hard_reset().map_err(|e| audio_code(&*e))) {
|
match with_manager(_self).and_then(|mut m| m.hard_reset().map_err(|e| audio_code(&*e))) {
|
||||||
@@ -12413,13 +12569,13 @@ pub mod render {
|
|||||||
let sample_rate = if params.is_null() {
|
let sample_rate = if params.is_null() {
|
||||||
48000
|
48000
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: the host liboakcore audioparams contract.
|
// SAFETY: the oakcore audioparams contract.
|
||||||
unsafe { crate::stubs::audio::oakcore_audioparams_sample_rate(params) }
|
unsafe { crate::stubs::audio::oakcore_audioparams_sample_rate(params) }
|
||||||
};
|
};
|
||||||
let channel_layout = if params.is_null() {
|
let channel_layout = if params.is_null() {
|
||||||
0x3
|
0x3
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: the host liboakcore audioparams contract.
|
// SAFETY: the oakcore audioparams contract.
|
||||||
unsafe { crate::stubs::audio::oakcore_audioparams_channel_layout(params) }
|
unsafe { crate::stubs::audio::oakcore_audioparams_channel_layout(params) }
|
||||||
};
|
};
|
||||||
let audio_params = oakrender::ticket::AudioTicketParams {
|
let audio_params = oakrender::ticket::AudioTicketParams {
|
||||||
|
|||||||
@@ -25,19 +25,16 @@
|
|||||||
//! would otherwise drop the dev-dependency rlibs from the link and
|
//! would otherwise drop the dev-dependency rlibs from the link and
|
||||||
//! leave the imports undefined.
|
//! leave the imports undefined.
|
||||||
//!
|
//!
|
||||||
//! 2. **Provide the `oakcore_*` symbols** that the
|
//! 2. **Re-export the folded-in `oakcore_audioparams_*` accessors** for
|
||||||
//! oakcodec rlib references: `oakcore_audioparams_*` /
|
//! the former mock call sites (`common::oakcore_audioparams_*`). The
|
||||||
//! `oakcore_rational_*` live in the C++ liboakcore (only linked in the
|
//! facade used to leave those symbols as runtime lookups for a C++
|
||||||
//! real build), so cargo tests define minimal in-memory mocks — the
|
//! liboakcore host, and the tests defined in-memory mocks; M12 P5
|
||||||
//! same mock the oakcodec crate itself compiles under `#[cfg(test)]`
|
//! implemented them inside the dylib (crates/oakengine/src/stubs.rs,
|
||||||
//! (src/bridge/test_stubs.rs). The real dylib behavior is required
|
//! module `audio`), so the tests just use those implementations.
|
||||||
//! for actual media decode; those facade tests are `#[ignore]`.
|
|
||||||
|
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::sync::Mutex;
|
||||||
use std::ffi::{c_int, c_void};
|
|
||||||
use std::sync::{Mutex, OnceLock};
|
|
||||||
|
|
||||||
/// One public direct-Rust symbol per module crate (the module C ABIs are
|
/// One public direct-Rust symbol per module crate (the module C ABIs are
|
||||||
/// deleted; this mirrors the anchors in `crates/oakengine/src/linkage.rs`).
|
/// deleted; this mirrors the anchors in `crates/oakengine/src/linkage.rs`).
|
||||||
@@ -106,201 +103,14 @@ pub fn storage_off_guard() -> std::sync::MutexGuard<'static, ()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// oakcore_* stubs (see module docs)
|
// oakcore_audioparams_* (see module docs)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Opaque `OakAudioParams` handle type (the real one lives in liboakcore).
|
/// The facade's in-dylib `oakcore_audioparams_*` C ABI (see
|
||||||
#[repr(C)]
|
/// `crate::stubs::audio`): the accessors were host-provided mocks until
|
||||||
pub struct OakAudioParams {
|
/// M12 P5 folded them into the engine, so the tests now share the real
|
||||||
_opaque: [u8; 0],
|
/// implementations instead of defining per-binary duplicates. Only the
|
||||||
}
|
/// two entry points the test files call (`create`/`free`) are re-exported;
|
||||||
|
/// the read accessors are reached through `crate::stubs::audio` where the
|
||||||
/// Per-`OakAudioParams` backing state.
|
/// tests need them.
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
pub use crate::stubs::audio::{oakcore_audioparams_create, oakcore_audioparams_free};
|
||||||
struct MockAudioParams {
|
|
||||||
sample_rate: i32,
|
|
||||||
channel_layout: u64,
|
|
||||||
format: i32,
|
|
||||||
stream_index: i32,
|
|
||||||
duration: i64,
|
|
||||||
time_base_num: i32,
|
|
||||||
time_base_den: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn audio_params_store() -> &'static Mutex<HashMap<usize, MockAudioParams>> {
|
|
||||||
static S: OnceLock<Mutex<HashMap<usize, MockAudioParams>>> = OnceLock::new();
|
|
||||||
S.get_or_init(|| Mutex::new(HashMap::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn audio_params_get(ctx: *const c_void) -> MockAudioParams {
|
|
||||||
let store = audio_params_store().lock().unwrap();
|
|
||||||
store.get(&(ctx as usize)).cloned().unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn audio_params_set(ctx: *mut c_void, f: impl FnOnce(&mut MockAudioParams)) {
|
|
||||||
let mut store = audio_params_store().lock().unwrap();
|
|
||||||
if let Some(p) = store.get_mut(&(ctx as usize)) {
|
|
||||||
f(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Per-`OakRational` backing state (an owned `(num, den)` pair).
|
|
||||||
fn rational_store() -> &'static Mutex<HashMap<usize, (i32, i32)>> {
|
|
||||||
static S: OnceLock<Mutex<HashMap<usize, (i32, i32)>>> = OnceLock::new();
|
|
||||||
S.get_or_init(|| Mutex::new(HashMap::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_create(
|
|
||||||
sample_rate: c_int,
|
|
||||||
channel_layout: u64,
|
|
||||||
format: c_int,
|
|
||||||
) -> *mut OakAudioParams {
|
|
||||||
let p = MockAudioParams {
|
|
||||||
sample_rate,
|
|
||||||
channel_layout,
|
|
||||||
format,
|
|
||||||
stream_index: 0,
|
|
||||||
duration: 0,
|
|
||||||
time_base_num: 1,
|
|
||||||
time_base_den: sample_rate,
|
|
||||||
};
|
|
||||||
let raw = Box::into_raw(Box::new(p.clone()));
|
|
||||||
audio_params_store().lock().unwrap().insert(raw as usize, p);
|
|
||||||
raw as *mut OakAudioParams
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_free(params: *mut OakAudioParams) {
|
|
||||||
if params.is_null() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
audio_params_store()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.remove(&(params as usize));
|
|
||||||
// SAFETY: produced by `oakcore_audioparams_create`; we hold the only
|
|
||||||
// reference after removal.
|
|
||||||
unsafe { drop(Box::from_raw(params as *mut MockAudioParams)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_sample_rate(params: *const OakAudioParams) -> c_int {
|
|
||||||
audio_params_get(params as *const c_void).sample_rate
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_set_sample_rate(
|
|
||||||
params: *mut OakAudioParams,
|
|
||||||
sample_rate: c_int,
|
|
||||||
) {
|
|
||||||
audio_params_set(params as *mut c_void, |p| p.sample_rate = sample_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_channel_layout(params: *const OakAudioParams) -> u64 {
|
|
||||||
audio_params_get(params as *const c_void).channel_layout
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_set_channel_layout(params: *mut OakAudioParams, layout: u64) {
|
|
||||||
audio_params_set(params as *mut c_void, |p| p.channel_layout = layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_set_time_base(
|
|
||||||
params: *mut OakAudioParams,
|
|
||||||
num: c_int,
|
|
||||||
den: c_int,
|
|
||||||
) {
|
|
||||||
audio_params_set(params as *mut c_void, |p| {
|
|
||||||
p.time_base_num = num;
|
|
||||||
p.time_base_den = den;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_set_format(params: *mut OakAudioParams, format: c_int) {
|
|
||||||
audio_params_set(params as *mut c_void, |p| p.format = format);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_set_stream_index(params: *mut OakAudioParams, index: c_int) {
|
|
||||||
audio_params_set(params as *mut c_void, |p| p.stream_index = index);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_set_duration(params: *mut OakAudioParams, duration: i64) {
|
|
||||||
audio_params_set(params as *mut c_void, |p| p.duration = duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_channel_count(params: *const OakAudioParams) -> c_int {
|
|
||||||
audio_params_get(params as *const c_void)
|
|
||||||
.channel_layout
|
|
||||||
.count_ones() as c_int
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_format(params: *const OakAudioParams) -> c_int {
|
|
||||||
audio_params_get(params as *const c_void).format
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_stream_index(params: *const OakAudioParams) -> c_int {
|
|
||||||
audio_params_get(params as *const c_void).stream_index
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_duration(params: *const OakAudioParams) -> i64 {
|
|
||||||
audio_params_get(params as *const c_void).duration
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_is_valid(params: *const OakAudioParams) -> c_int {
|
|
||||||
let p = audio_params_get(params as *const c_void);
|
|
||||||
(p.sample_rate > 0 && p.channel_layout != 0 && p.format >= 0) as c_int
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_audioparams_time_base(params: *const OakAudioParams) -> *mut c_void {
|
|
||||||
let p = audio_params_get(params as *const c_void);
|
|
||||||
let r = (p.time_base_num, p.time_base_den);
|
|
||||||
let raw = Box::into_raw(Box::new(r));
|
|
||||||
rational_store().lock().unwrap().insert(raw as usize, r);
|
|
||||||
raw as *mut c_void
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_rational_numerator(rational: *const c_void) -> c_int {
|
|
||||||
rational_store()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.get(&(rational as usize))
|
|
||||||
.map(|r| r.0)
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_rational_denominator(rational: *const c_void) -> c_int {
|
|
||||||
rational_store()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.get(&(rational as usize))
|
|
||||||
.map(|r| r.1)
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn oakcore_rational_free(rational: *mut c_void) {
|
|
||||||
if rational.is_null() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rational_store()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.remove(&(rational as usize));
|
|
||||||
// SAFETY: produced by `oakcore_audioparams_time_base` as a boxed
|
|
||||||
// `(i32, i32)` pair; we hold the only reference after removal.
|
|
||||||
unsafe { drop(Box::from_raw(rational as *mut (i32, i32))) };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -108,9 +108,10 @@ fn alive() -> c_int {
|
|||||||
crate::stubs::audio::oakaudio_debug_alive_count()
|
crate::stubs::audio::oakaudio_debug_alive_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A borrowed `OakAudioParams*` mock handle (tests/common/mod.rs provides
|
/// A borrowed `OakAudioParams*` handle created through the facade's
|
||||||
/// the `oakcore_audioparams_*` accessors the facade reads through).
|
/// in-dylib `oakcore_audioparams_*` accessors (tests/common/mod.rs
|
||||||
fn audio_params(rate: c_int, layout: u64, format: c_int) -> *mut common::OakAudioParams {
|
/// re-exports them; see `crate::stubs::audio`).
|
||||||
|
fn audio_params(rate: c_int, layout: u64, format: c_int) -> *mut c_void {
|
||||||
common::oakcore_audioparams_create(rate, layout, format)
|
common::oakcore_audioparams_create(rate, layout, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,9 @@
|
|||||||
//!
|
//!
|
||||||
//! Coverage rules (see the family test charter):
|
//! Coverage rules (see the family test charter):
|
||||||
//! 1. no mocks — every call goes through the real facade into the real
|
//! 1. no mocks — every call goes through the real facade into the real
|
||||||
//! module crates (the only stubs are the host-provided `oakcore_*`
|
//! module crates (the `oakcore_audioparams_*` accessors the facade
|
||||||
//! symbols in `tests/common`); the output file is a REAL mp4 written
|
//! reads through are its own in-dylib implementations, re-exported by
|
||||||
|
//! `tests/common`); the output file is a REAL mp4 written
|
||||||
//! by the statically linked FFmpeg (oakcodec encoder), asserted by
|
//! by the statically linked FFmpeg (oakcodec encoder), asserted by
|
||||||
//! its `ftyp` box;
|
//! its `ftyp` box;
|
||||||
//! 2. every exporter-family export is exercised on a legal path with the
|
//! 2. every exporter-family export is exercised on a legal path with the
|
||||||
|
|||||||
@@ -19,9 +19,10 @@
|
|||||||
//!
|
//!
|
||||||
//! Coverage rules (see the family test charter):
|
//! Coverage rules (see the family test charter):
|
||||||
//! 1. no mocks — every call goes through the real facade into the real
|
//! 1. no mocks — every call goes through the real facade into the real
|
||||||
//! oaktask/oaknode/oakundo/oakcodec module crates (the only stubs are
|
//! oaktask/oaknode/oakundo/oakcodec module crates (the
|
||||||
//! the host-provided `oakcore_*` symbols in `tests/common`,
|
//! `oakcore_audioparams_*` accessors the facade reads through are its
|
||||||
//! the same mechanism the other family tests use);
|
//! own in-dylib implementations, re-exported by `tests/common`, the
|
||||||
|
//! same mechanism the other family tests use);
|
||||||
//! 2. every one of the 27 `oakengine_task_*` / `oakengine_cli_task_*`
|
//! 2. every one of the 27 `oakengine_task_*` / `oakengine_cli_task_*`
|
||||||
//! exports is exercised on a legal path with the result asserted;
|
//! exports is exercised on a legal path with the result asserted;
|
||||||
//! 3. legal-input matrix (compression flags, url counts, indices, buffer
|
//! 3. legal-input matrix (compression flags, url counts, indices, buffer
|
||||||
|
|||||||
@@ -21,8 +21,9 @@
|
|||||||
//! undoable). Coverage rules (see the family test charter):
|
//! undoable). Coverage rules (see the family test charter):
|
||||||
//!
|
//!
|
||||||
//! 1. no mocks — every call goes through the real facade into the real
|
//! 1. no mocks — every call goes through the real facade into the real
|
||||||
//! module crates; the only stubs are the host-provided `oakcore_*`
|
//! module crates (the `oakcore_audioparams_*` accessors the facade
|
||||||
//! symbols in `tests/common` (no media is decoded, so no FFmpeg);
|
//! reads through are its own in-dylib implementations, re-exported by
|
||||||
|
//! `tests/common`; no media is decoded, so no FFmpeg);
|
||||||
//! 2. every export under test is exercised on a legal path with the
|
//! 2. every export under test is exercised on a legal path with the
|
||||||
//! result asserted;
|
//! result asserted;
|
||||||
//! 3. illegal inputs (NULL seq, bad track types, out-of-range indices,
|
//! 3. illegal inputs (NULL seq, bad track types, out-of-range indices,
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
//! They moved here (`src/test_support/`, pulled in by `src/lib.rs` under
|
//! They moved here (`src/test_support/`, pulled in by `src/lib.rs` under
|
||||||
//! `#[cfg(test)]`) and run as unit tests against `crate::*` instead of
|
//! `#[cfg(test)]`) and run as unit tests against `crate::*` instead of
|
||||||
//! `oakengine::*`. The old `#[path = "common/mod.rs"] mod common;` include
|
//! `oakengine::*`. The old `#[path = "common/mod.rs"] mod common;` include
|
||||||
//! is replaced by the single [`common`] declaration below — the
|
//! is replaced by the single [`common`] declaration below — its
|
||||||
//! `oakcore_*` mock symbols it defines may exist only once per binary.
|
//! `oakcore_audioparams_*` re-exports may exist only once per binary.
|
||||||
//!
|
//!
|
||||||
//! The node/timeline/render-graph families (and the graph-op tests that
|
//! The node/timeline/render-graph families (and the graph-op tests that
|
||||||
//! built fixtures through the deleted handle-based module C ABIs) are
|
//! built fixtures through the deleted handle-based module C ABIs) are
|
||||||
|
|||||||
Reference in New Issue
Block a user