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
+27 -15
View File
@@ -561,10 +561,14 @@ pub fn markers_of(list: &CHandle) -> Vec<(Rational, String, i32)> {
}
// SAFETY: `list` boxes a `TimelineMarkerList` (created by
// `marker_list_create`); the read is shared and brief.
let Some(l) = (unsafe { oaktimeline::handle::get::<oaktimeline::marker::TimelineMarkerList>(list) })
else {
let Some(l) = (unsafe {
oaktimeline::handle::get::<std::sync::Arc<std::sync::Mutex<oaktimeline::marker::TimelineMarkerList>>>(
list,
)
}) else {
return Vec::new();
};
let l = l.lock().unwrap_or_else(|e| e.into_inner());
(0..l.size())
.filter_map(|i| l.at(i))
.map(|m| (m.time().in_(), m.name().to_string(), m.color()))
@@ -577,7 +581,12 @@ pub fn marker_index_at(list: &CHandle, time: Rational) -> Option<usize> {
return None;
}
// SAFETY: as `markers_of`.
let l = unsafe { oaktimeline::handle::get::<oaktimeline::marker::TimelineMarkerList>(list) }?;
let l = unsafe {
oaktimeline::handle::get::<std::sync::Arc<std::sync::Mutex<oaktimeline::marker::TimelineMarkerList>>>(
list,
)
}?;
let l = l.lock().unwrap_or_else(|e| e.into_inner());
(0..l.size()).find(|&i| l.at(i).map(|m| m.time().in_()) == Some(time))
}
@@ -588,7 +597,12 @@ pub fn workarea_state(wa: &CHandle) -> Option<(bool, TimeRange)> {
}
// SAFETY: `wa` boxes a `TimelineWorkArea` (created by
// `workarea_create`); the read is shared and brief.
let w = unsafe { oaktimeline::handle::get::<oaktimeline::workarea::TimelineWorkArea>(wa) }?;
let w = unsafe {
oaktimeline::handle::get::<std::sync::Arc<std::sync::Mutex<oaktimeline::workarea::TimelineWorkArea>>>(
wa,
)
}?;
let w = w.lock().unwrap_or_else(|e| e.into_inner());
Some((w.enabled(), *w.range()))
}
@@ -599,8 +613,12 @@ pub fn workarea_set(wa: &CHandle, enabled: bool, range: TimeRange) {
}
// SAFETY: `wa` boxes a `TimelineWorkArea`; the engine writes it only
// from the UI thread.
if let Some(w) = unsafe { oaktimeline::handle::get_mut::<oaktimeline::workarea::TimelineWorkArea>(wa) }
{
if let Some(w) = unsafe {
oaktimeline::handle::get_mut::<std::sync::Arc<std::sync::Mutex<oaktimeline::workarea::TimelineWorkArea>>>(
wa,
)
} {
let mut w = w.lock().unwrap_or_else(|e| e.into_inner());
w.set_enabled(enabled);
w.set_range(range);
}
@@ -675,11 +693,9 @@ pub fn library_create(name: &str) -> Result<String, String> {
.insert("projectname".to_string(), name.to_string());
guard.uuid.clone()
};
let handle = oakstorage::nodeutil::make_project_owned(project);
let result = oakstorage::writethrough::backend()
.save(handle, &uri, 0)
.save_project(&project, &uri, 0)
.map_err(|e| e.to_string());
oakstorage::nodeutil::release_project(handle);
result?;
Ok(uuid)
}
@@ -758,12 +774,8 @@ pub fn library_open(uuid: &str) -> Result<ProjectRef, String> {
));
}
let handle = result.project;
let project = oakstorage::nodeutil::project_arc_of(&handle)
.ok_or_else(|| "library load returned a foreign project handle".to_string());
// The loaded handle's ownership moves to the caller's Arc; release the
// handle shell (the Arc keeps the project alive).
oakstorage::nodeutil::release_project(handle);
let project = project?;
let project = unsafe { oakstorage::nodeutil::project_arc(&handle) }
.map_err(|e| e.to_string())?;
lock(&project).set_modified(false);
Ok(project)
}