refactor: purge CHandle from module internals (M14 R5)

Module-internal object references are Rust types now (values, Arc,
Mutex); CHandle remains only at the oakengine C-ABI boundary:

- oakundo: the global stack holds UndoStack/UndoCommand values
  directly (stack token is the static's address)
- oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands
  share the same allocation through Arc clones (readers in oakengine
  stubs and the app's graphops updated to lock)
- oaktask/oakstorage: sessions, write-through bindings and the
  database backend pass ProjectArc; the Session drops its manual
  release bookkeeping; nodeutil keeps the CHandle<->Arc boundary
  conversion (release_project restored for the app)
- oakcodec: handle.rs deleted outright (no facade entry needed it);
  texture/block placeholders are unit structs
- oakrender: copier's project handle is an identity u64; alive-count
  machinery removed; handle.rs is make_owned/get/get_mut only
- oakplugin: the instance registry is gone (its unregister key never
  matched, leaking weak entries); handle.rs is the RefBox boundary type
- oaknode/oakcommon: only dead guard/borrow helpers removed; external
  payload handles (texture/processor) documented as the boundary

Flake hunts landed along the way: the audio recording test serializes
on the shared manager lock with a normalized state; the autocacher
cancel test uses a slow producer so cancellation is deterministic.
This commit is contained in:
2026-08-17 16:40:15 +08:00
parent ede03d0bfe
commit b36cbd6b6f
53 changed files with 1260 additions and 2369 deletions
+13 -12
View File
@@ -10622,11 +10622,11 @@ pub mod timeline {
}
// SAFETY: the caller guarantees a valid NUL-terminated string.
let name = unsafe { crate::handle::read_cstr(name) };
let list_mut = unsafe { oaktimeline::handle::get_mut::<oaktimeline::marker::TimelineMarkerList>(&list) };
let list_mut = unsafe { oaktimeline::handle::get_mut::<std::sync::Arc<std::sync::Mutex<oaktimeline::marker::TimelineMarkerList>>>(&list) };
let Some(list_mut) = list_mut else {
return oaktimeline::error::OAKTIMELINE_E_INVALID;
};
list_mut.add_marker(oaktimeline::marker::TimelineMarker::with_time(
list_mut.lock().unwrap_or_else(|e| e.into_inner()).add_marker(oaktimeline::marker::TimelineMarker::with_time(
color,
TimeRange::new(
Rational::new(in_num as i64, in_den as i64),
@@ -10643,10 +10643,10 @@ pub mod timeline {
return oaktimeline::error::OAKTIMELINE_E_INVALID;
}
// SAFETY: marker-list handles box TimelineMarkerList.
match unsafe { oaktimeline::handle::get::<oaktimeline::marker::TimelineMarkerList>(&list) } {
match unsafe { oaktimeline::handle::get::<std::sync::Arc<std::sync::Mutex<oaktimeline::marker::TimelineMarkerList>>>(&list) } {
Some(l) => {
// SAFETY: valid out pointer.
unsafe { *out_count = l.size() as c_int };
unsafe { *out_count = l.lock().unwrap_or_else(|e| e.into_inner()).size() as c_int };
oaktimeline::error::OAKTIMELINE_OK
}
None => oaktimeline::error::OAKTIMELINE_E_INVALID,
@@ -10675,10 +10675,11 @@ pub mod timeline {
return oaktimeline::error::OAKTIMELINE_E_INVALID;
}
// SAFETY: marker-list handles box TimelineMarkerList.
let l = match unsafe { oaktimeline::handle::get::<oaktimeline::marker::TimelineMarkerList>(&list) } {
let l = match unsafe { oaktimeline::handle::get::<std::sync::Arc<std::sync::Mutex<oaktimeline::marker::TimelineMarkerList>>>(&list) } {
Some(l) => l,
None => return oaktimeline::error::OAKTIMELINE_E_INVALID,
};
let l = l.lock().unwrap_or_else(|e| e.into_inner());
let Some(m) = l.at(index as usize) else {
return oaktimeline::error::OAKTIMELINE_E_NOT_FOUND;
};
@@ -10852,9 +10853,9 @@ pub mod timeline {
/// `oaktimeline_workarea_set_enabled`.
pub fn oaktimeline_workarea_set_enabled(w: CHandle, enabled: c_int) -> c_int {
// SAFETY: work-area handles box TimelineWorkArea.
match unsafe { oaktimeline::handle::get_mut::<oaktimeline::workarea::TimelineWorkArea>(&w) } {
match unsafe { oaktimeline::handle::get_mut::<std::sync::Arc<std::sync::Mutex<oaktimeline::workarea::TimelineWorkArea>>>(&w) } {
Some(wa) => {
wa.set_enabled(enabled != 0);
wa.lock().unwrap_or_else(|e| e.into_inner()).set_enabled(enabled != 0);
oaktimeline::error::OAKTIMELINE_OK
}
None => oaktimeline::error::OAKTIMELINE_E_INVALID,
@@ -10871,11 +10872,11 @@ pub mod timeline {
enabled: *mut c_int,
) -> c_int {
// SAFETY: work-area handles box TimelineWorkArea.
let wa = match unsafe { oaktimeline::handle::get::<oaktimeline::workarea::TimelineWorkArea>(&w) } {
let wa = match unsafe { oaktimeline::handle::get::<std::sync::Arc<std::sync::Mutex<oaktimeline::workarea::TimelineWorkArea>>>(&w) } {
Some(wa) => wa,
None => return oaktimeline::error::OAKTIMELINE_E_INVALID,
};
let range = wa.range();
let range = *wa.lock().unwrap_or_else(|e| e.into_inner()).range();
// Out params may individually be NULL (the header contract); write
// each only when the caller supplied a target.
unsafe {
@@ -10892,7 +10893,7 @@ pub mod timeline {
*out_den = range.out().denominator() as c_int;
}
if !enabled.is_null() {
*enabled = if wa.enabled() { 1 } else { 0 };
*enabled = if wa.lock().unwrap_or_else(|e| e.into_inner()).enabled() { 1 } else { 0 };
}
}
oaktimeline::error::OAKTIMELINE_OK
@@ -10910,9 +10911,9 @@ pub mod timeline {
return oaktimeline::error::OAKTIMELINE_E_INVALID;
}
// SAFETY: work-area handles box TimelineWorkArea.
match unsafe { oaktimeline::handle::get_mut::<oaktimeline::workarea::TimelineWorkArea>(&w) } {
match unsafe { oaktimeline::handle::get_mut::<std::sync::Arc<std::sync::Mutex<oaktimeline::workarea::TimelineWorkArea>>>(&w) } {
Some(wa) => {
wa.set_range(TimeRange::new(
wa.lock().unwrap_or_else(|e| e.into_inner()).set_range(TimeRange::new(
Rational::new(in_num as i64, in_den as i64),
Rational::new(out_num as i64, out_den as i64),
));
+11 -5
View File
@@ -21,6 +21,7 @@ use super::common;
use std::ffi::{c_char, c_int};
use crate::audio::oakengine_audio_destroy_instance;
use crate::codec::{
oakengine_encoding_codec_is_lossless, oakengine_encoding_codec_is_still_image,
oakengine_encoding_codec_name, oakengine_encoding_filename_contains_digit_placeholder,
@@ -299,9 +300,14 @@ fn params_handle_round_trip() {
/// Audio recording without a running audio manager fails with E_STATE.
#[test]
fn start_audio_recording_no_manager() {
let p = unsafe { oakengine_encoding_params_create() };
assert!(!p.is_null());
let rc = unsafe { oakengine_encoding_start_audio_recording(p, std::ptr::null_mut(), 0) };
assert_eq!(rc, -2); // OAKENGINE_E_STATE
unsafe { oakengine_encoding_params_destroy(p) };
// The audio manager is a process-wide singleton shared with it_audio;
// serialize and normalize it (no instance) before asserting E_STATE.
common::with_manager(|| unsafe {
let _ = oakengine_audio_destroy_instance();
let p = oakengine_encoding_params_create();
assert!(!p.is_null());
let rc = oakengine_encoding_start_audio_recording(p, std::ptr::null_mut(), 0);
assert_eq!(rc, -2); // OAKENGINE_E_STATE
oakengine_encoding_params_destroy(p);
});
}