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
+41 -81
View File
@@ -14,18 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Refcounted-handle scaffolding. Same pattern as the oaknode crate
//! (`src/node/rust/src/handle.rs`); intentionally duplicated rather
//! than shared — each module DLL must run its own addref/release code
//! (the function pointers in a handle always point into the DLL that
//! created the object). Value handles (`OakTimelineMarkerList`,
//! `OakTimelineWorkArea`) share this box.
//! Refcounted-handle scaffolding for the oakengine facade boundary.
//!
//! After the single-lib unification the crate's internal object
//! references are plain Rust types: the timeline commands hold
//! [`Arc<Mutex<…>>`] handles to the shared marker list / work area, and
//! [`CHandle`] appears only in the facade-facing entries that the
//! oakengine C ABI export layer (and the first-party frontends) call.
//!
//! Every [`make_owned`] value handle boxes an [`Arc<Mutex<T>>`] behind a
//! [`RefBox`] (the same pattern as the oaknode project handles): the
//! facade reads the shared object back through
//! `get::<Arc<Mutex<T>>>`, clones the `Arc`, and the command entries do
//! the same when they convert a handle into the crate's Rust-typed
//! command constructors. The box's addref/release functions only manage
//! the handle shell — the `Arc` keeps the actual value alive.
use std::ffi::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU32, Ordering};
use crate::error::{Result, OAKTIMELINE_E_FAILED, OAKTIMELINE_OK};
use std::sync::{Arc, Mutex};
/// ABI version stamped into every oaktimeline handle.
pub const OAKTIMELINE_ABI_VERSION: u32 = 1;
@@ -74,56 +81,50 @@ unsafe extern "C" fn release_box<T: 'static>(ptr: *mut c_void) {
}
/// Owned handle with count 1; empty on allocation failure.
///
/// The boxed value is an [`Arc<Mutex<T>>`] (see the module docs): every
/// handle produced here references the same shared object that the
/// timeline commands hold directly.
pub fn make_owned<T: Send + 'static>(value: T) -> CHandle {
make_owned_arc(Arc::new(Mutex::new(value)))
}
/// Owned handle over an already-shared `Arc<Mutex<T>>`; empty on
/// allocation failure. The `Arc` is moved into the box, so every handle
/// created from the same `Arc` shares one object.
pub fn make_owned_arc<T: Send + 'static>(arc: Arc<Mutex<T>>) -> CHandle {
let boxed = Box::new(RefBox {
refs: AtomicU32::new(1),
value,
value: arc,
});
let ptr = Box::into_raw(boxed) as *mut c_void;
CHandle {
ctx: ptr,
addref: Some(addref_box::<T>),
release: Some(release_box::<T>),
addref: Some(addref_box::<Arc<Mutex<T>>>),
release: Some(release_box::<Arc<Mutex<T>>>),
abi_version: OAKTIMELINE_ABI_VERSION,
}
}
/// Borrowed handle for an object owned elsewhere (release frees only
/// the box).
///
/// The box holds a detached copy of `*ptr`, so the underlying object is
/// never touched by the handle's release; `get` returns the copy. The
/// caller retains ownership of `ptr`.
///
/// # Safety
/// Caller guarantees `ptr` is valid for reading for the duration of the
/// call.
pub unsafe fn make_borrowed<T: Send + 'static>(ptr: *mut T) -> CHandle {
let value = unsafe { ptr.read() };
make_owned(value)
}
/// Typed view into a handle; `None` for empty handles.
///
/// Value handles box an [`Arc<Mutex<T>>`]; read the shared object with
/// `get::<Arc<Mutex<T>>>(h)` and clone the `Arc` (or lock it in place).
///
/// # Safety
/// `T` must be the boxed type.
pub unsafe fn get<T: 'static>(h: &CHandle) -> Option<&T> {
if h.ctx.is_null() {
return None;
}
if h.addref.is_some() {
// Owned (or borrowed-via-copy) handle: `ctx` points at a `RefBox<T>`.
let rb = unsafe { &*(h.ctx as *const RefBox<T>) };
Some(&rb.value)
} else {
// Borrowed handle wrapping a raw object pointer (e.g. test-stub
// handles): `ctx` is the object itself, not a `RefBox<T>`.
Some(unsafe { &*(h.ctx as *const T) })
}
let rb = unsafe { &*(h.ctx as *const RefBox<T>) };
Some(&rb.value)
}
/// Mutable typed view into a handle; `None` for empty handles. Used by
/// undo commands to mutate the boxed value they hold a handle to.
/// Mutable typed view into a handle; `None` for empty handles. With the
/// `Arc`-boxed value handles this yields `&mut Arc<Mutex<T>>` — use
/// [`get::<Arc<Mutex<T>>>`](get) plus `Mutex::lock` to mutate the shared
/// value instead.
///
/// # Safety
/// `T` must be the boxed type, and the caller must guarantee exclusive
@@ -133,47 +134,6 @@ pub unsafe fn get_mut<T: 'static>(h: &CHandle) -> Option<&mut T> {
if h.ctx.is_null() {
return None;
}
if h.addref.is_some() {
// Owned (or borrowed-via-copy) handle: `ctx` points at a `RefBox<T>`.
let rb = unsafe { &mut *(h.ctx as *mut RefBox<T>) };
Some(&mut rb.value)
} else {
// Borrowed handle wrapping a raw object pointer (e.g. test-stub
// handles): `ctx` is the object itself, not a `RefBox<T>`.
Some(unsafe { &mut *(h.ctx as *mut T) })
}
}
/// Panic-catching FFI wrapper for i32-returning exports.
pub fn guard<F: FnOnce() -> Result<()>>(f: F) -> i32 {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(())) => OAKTIMELINE_OK,
Ok(Err(e)) => e.code(),
Err(_) => OAKTIMELINE_E_FAILED,
}
}
/// Panic-catching FFI wrapper for handle-returning exports.
pub fn guard_handle<F: FnOnce() -> Result<CHandle>>(f: F) -> CHandle {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(h)) => h,
_ => CHandle::null(),
}
}
/// Panic-catching FFI wrapper for void exports.
pub fn guard_void<F: FnOnce()>(f: F) {
let _ = catch_unwind(AssertUnwindSafe(f));
}
/// Panic-catching FFI wrapper for exports returning an `i32` value that is
/// not an error code (e.g. two-stage string lengths): a successful closure
/// returns its value verbatim, errors map through `Error::code`, and a
/// panic becomes `OAKTIMELINE_E_FAILED`.
pub fn guard_i32<F: FnOnce() -> Result<i32>>(f: F) -> i32 {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(v)) => v,
Ok(Err(e)) => e.code(),
Err(_) => OAKTIMELINE_E_FAILED,
}
let rb = unsafe { &mut *(h.ctx as *mut RefBox<T>) };
Some(&mut rb.value)
}
+182 -73
View File
@@ -25,10 +25,12 @@
//! not re-sort (the De-Qt marker has no parent pointer); callers restore
//! order via `TimelineMarkerList::resort`/`resort_at`.
use std::sync::{Arc, Mutex};
use oakcore_rs::{Rational, TimeRange};
use oakundo::undocommand::UndoCommand;
use crate::handle::{get, get_mut};
use crate::handle::get;
use crate::undocommon::{box_command, Command};
/// `TimelineMarker` — a named, colored time range on a timeline
@@ -211,6 +213,15 @@ impl TimelineMarkerList {
self.markers_.get_mut(i)
}
/// Remove the marker at `index`, returning it; `None` out of range.
fn remove_at(&mut self, index: usize) -> Option<TimelineMarker> {
if index < self.markers_.len() {
Some(self.markers_.remove(index))
} else {
None
}
}
/// Remove the marker at `index` and re-insert it sorted; no-op when
/// `index` is out of range. Used by the time-change command after
/// mutating a marker in place.
@@ -238,8 +249,10 @@ fn rational_abs(r: Rational) -> Rational {
/// `MarkerAddCommand` (timelinemarker.h).
pub struct MarkerAddCommand {
/// Target list.
marker_list: crate::handle::CHandle,
/// Target list (the shared marker list behind the facade's value
/// handle; `None` for an empty/null handle, which makes the command a
/// no-op).
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
/// Marker range.
range: TimeRange,
/// Marker name.
@@ -258,8 +271,28 @@ impl MarkerAddCommand {
name: &str,
color: i32,
) -> Self {
// SAFETY: marker-list handles box an `Arc<Mutex<TimelineMarkerList>>`
// (created by `make_owned`); reading it clones the shared `Arc`.
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
Self {
marker_list,
marker_list: list,
range,
name: name.to_string(),
color,
added: false,
}
}
/// Construct over an already-shared marker list (the crate's
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(
marker_list: Arc<Mutex<TimelineMarkerList>>,
range: TimeRange,
name: &str,
color: i32,
) -> Self {
Self {
marker_list: Some(marker_list),
range,
name: name.to_string(),
color,
@@ -272,11 +305,10 @@ impl MarkerAddCommand {
if self.added {
return;
}
let marker = TimelineMarker::with_time(self.color, self.range, &self.name);
// SAFETY: the boxed value is a `TimelineMarkerList` created by
// `make_owned`, and the command holds exclusive access to it.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
list.add_marker(marker);
if let Some(list) = &self.marker_list {
let marker = TimelineMarker::with_time(self.color, self.range, &self.name);
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
l.add_marker(marker);
self.added = true;
}
}
@@ -290,13 +322,12 @@ impl MarkerAddCommand {
if !self.added {
return;
}
// SAFETY: as `redo`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.get_marker_at_time(self.range.in_()) {
// SAFETY: `m` borrows from `list`; `remove_marker` uses it
// only for identity comparison before detaching.
let mptr = m as *const TimelineMarker;
let _ = list.remove_marker(unsafe { &*mptr });
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(index) = (0..l.size())
.find(|&i| l.at(i).is_some_and(|m| m.time().in_() == self.range.in_()))
{
l.remove_at(index);
}
self.added = false;
}
@@ -322,8 +353,9 @@ impl Command for MarkerAddCommand {
/// `MarkerRemoveCommand` (timelinemarker.h).
pub struct MarkerRemoveCommand {
/// Target list.
marker_list: crate::handle::CHandle,
/// Target list (shared marker list behind the facade's value handle;
/// `None` for an empty/null handle, which makes the command a no-op).
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
/// Index of the marker to remove.
index: usize,
/// Marker detached on `redo`, re-inserted by `undo`.
@@ -333,8 +365,20 @@ pub struct MarkerRemoveCommand {
impl MarkerRemoveCommand {
/// Construct from list + index of the marker to remove.
pub fn new(marker_list: crate::handle::CHandle, index: usize) -> Self {
// SAFETY: as `MarkerAddCommand::new`.
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
Self {
marker_list,
marker_list: list,
index,
removed: None,
}
}
/// Construct over an already-shared marker list (the crate's
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(marker_list: Arc<Mutex<TimelineMarkerList>>, index: usize) -> Self {
Self {
marker_list: Some(marker_list),
index,
removed: None,
}
@@ -345,26 +389,20 @@ impl MarkerRemoveCommand {
if self.removed.is_some() {
return;
}
// SAFETY: the boxed value is a `TimelineMarkerList` created by
// `make_owned`, and the command holds exclusive access to it.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at(self.index) {
// SAFETY: `m` borrows from `list`; `remove_marker` uses it
// only for identity comparison before detaching.
let mptr = m as *const TimelineMarker;
if let Some(removed) = list.remove_marker(unsafe { &*mptr }) {
self.removed = Some(removed);
}
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(removed) = l.remove_at(self.index) {
self.removed = Some(removed);
}
}
}
/// `undo`: re-insert the marker, sorted.
pub fn undo(&mut self) {
// SAFETY: as `redo`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(marker) = self.removed.take() {
list.add_marker(marker);
l.add_marker(marker);
}
}
}
@@ -389,8 +427,9 @@ impl Command for MarkerRemoveCommand {
/// `MarkerChangeColorCommand` (timelinemarker.h).
pub struct MarkerChangeColorCommand {
/// Target list.
marker_list: crate::handle::CHandle,
/// Target list (shared marker list behind the facade's value handle;
/// `None` for an empty/null handle, which makes the command a no-op).
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
/// Index of the marker to change.
index: usize,
/// Color before the change.
@@ -403,14 +442,38 @@ impl MarkerChangeColorCommand {
/// Construct from list + index + new color, capturing the current color
/// as old.
pub fn new(marker_list: crate::handle::CHandle, index: usize, new_color: i32) -> Self {
// SAFETY: the boxed value is a `TimelineMarkerList` created by
// `make_owned`; reading it here is the command's own handle.
let old_color = unsafe { get::<TimelineMarkerList>(&marker_list) }
.and_then(|l| l.at(index))
// SAFETY: as `MarkerAddCommand::new`.
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
let old_color = list
.as_ref()
.and_then(|l| {
let l = l.lock().unwrap_or_else(|e| e.into_inner());
l.at(index).map(|m| m.color())
})
.unwrap_or(0);
Self {
marker_list: list,
index,
old_color,
new_color,
}
}
/// Construct over an already-shared marker list (the crate's
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(
marker_list: Arc<Mutex<TimelineMarkerList>>,
index: usize,
new_color: i32,
) -> Self {
let old_color = marker_list
.lock()
.unwrap_or_else(|e| e.into_inner())
.at(index)
.map(|m| m.color())
.unwrap_or(0);
Self {
marker_list,
marker_list: Some(marker_list),
index,
old_color,
new_color,
@@ -419,9 +482,9 @@ impl MarkerChangeColorCommand {
/// `redo`: apply the new color.
pub fn redo(&mut self) {
// SAFETY: as `new`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at_mut(self.index) {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(m) = l.at_mut(self.index) {
m.set_color(self.new_color);
}
}
@@ -429,9 +492,9 @@ impl MarkerChangeColorCommand {
/// `undo`: restore the old color.
pub fn undo(&mut self) {
// SAFETY: as `new`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at_mut(self.index) {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(m) = l.at_mut(self.index) {
m.set_color(self.old_color);
}
}
@@ -457,8 +520,9 @@ impl Command for MarkerChangeColorCommand {
/// `MarkerChangeNameCommand` (timelinemarker.h).
pub struct MarkerChangeNameCommand {
/// Target list.
marker_list: crate::handle::CHandle,
/// Target list (shared marker list behind the facade's value handle;
/// `None` for an empty/null handle, which makes the command a no-op).
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
/// Index of the marker to change.
index: usize,
/// Name before the change.
@@ -471,14 +535,34 @@ impl MarkerChangeNameCommand {
/// Construct from list + index + new name, capturing the current name as
/// old.
pub fn new(marker_list: crate::handle::CHandle, index: usize, name: &str) -> Self {
// SAFETY: the boxed value is a `TimelineMarkerList` created by
// `make_owned`; reading it here is the command's own handle.
let old_name = unsafe { get::<TimelineMarkerList>(&marker_list) }
.and_then(|l| l.at(index))
// SAFETY: as `MarkerAddCommand::new`.
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
let old_name = list
.as_ref()
.and_then(|l| {
let l = l.lock().unwrap_or_else(|e| e.into_inner());
l.at(index).map(|m| m.name().to_string())
})
.unwrap_or_default();
Self {
marker_list: list,
index,
old_name,
new_name: name.to_string(),
}
}
/// Construct over an already-shared marker list (the crate's
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(marker_list: Arc<Mutex<TimelineMarkerList>>, index: usize, name: &str) -> Self {
let old_name = marker_list
.lock()
.unwrap_or_else(|e| e.into_inner())
.at(index)
.map(|m| m.name().to_string())
.unwrap_or_default();
Self {
marker_list,
marker_list: Some(marker_list),
index,
old_name,
new_name: name.to_string(),
@@ -487,9 +571,9 @@ impl MarkerChangeNameCommand {
/// `redo`: apply the new name.
pub fn redo(&mut self) {
// SAFETY: as `new`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at_mut(self.index) {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(m) = l.at_mut(self.index) {
m.set_name(&self.new_name);
}
}
@@ -497,9 +581,9 @@ impl MarkerChangeNameCommand {
/// `undo`: restore the old name.
pub fn undo(&mut self) {
// SAFETY: as `new`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at_mut(self.index) {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(m) = l.at_mut(self.index) {
m.set_name(&self.old_name);
}
}
@@ -526,8 +610,9 @@ impl Command for MarkerChangeNameCommand {
/// `MarkerChangeTimeCommand` (timelinemarker.h). The old range is captured at
/// construction when not supplied.
pub struct MarkerChangeTimeCommand {
/// Target list.
marker_list: crate::handle::CHandle,
/// Target list (shared marker list behind the facade's value handle;
/// `None` for an empty/null handle, which makes the command a no-op).
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
/// Index of the marker to change.
index: usize,
/// Time range before the change.
@@ -540,14 +625,38 @@ impl MarkerChangeTimeCommand {
/// Construct from list + index + new time, capturing the current range as
/// old.
pub fn new(marker_list: crate::handle::CHandle, index: usize, time: TimeRange) -> Self {
// SAFETY: the boxed value is a `TimelineMarkerList` created by
// `make_owned`; reading it here is the command's own handle.
let old_time = unsafe { get::<TimelineMarkerList>(&marker_list) }
.and_then(|l| l.at(index))
// SAFETY: as `MarkerAddCommand::new`.
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
let old_time = list
.as_ref()
.and_then(|l| {
let l = l.lock().unwrap_or_else(|e| e.into_inner());
l.at(index).map(|m| *m.time())
})
.unwrap_or_else(|| TimeRange::new(Rational::new(0, 1), Rational::new(0, 1)));
Self {
marker_list: list,
index,
old_time,
new_time: time,
}
}
/// Construct over an already-shared marker list (the crate's
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(
marker_list: Arc<Mutex<TimelineMarkerList>>,
index: usize,
time: TimeRange,
) -> Self {
let old_time = marker_list
.lock()
.unwrap_or_else(|e| e.into_inner())
.at(index)
.map(|m| *m.time())
.unwrap_or_else(|| TimeRange::new(Rational::new(0, 1), Rational::new(0, 1)));
Self {
marker_list,
marker_list: Some(marker_list),
index,
old_time,
new_time: time,
@@ -556,23 +665,23 @@ impl MarkerChangeTimeCommand {
/// `redo`: apply the new time (resorts).
pub fn redo(&mut self) {
// SAFETY: as `new`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at_mut(self.index) {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(m) = l.at_mut(self.index) {
m.set_time(self.new_time);
}
list.resort_at(self.index);
l.resort_at(self.index);
}
}
/// `undo`: restore the old time (resorts).
pub fn undo(&mut self) {
// SAFETY: as `new`.
if let Some(list) = unsafe { get_mut::<TimelineMarkerList>(&self.marker_list) } {
if let Some(m) = list.at_mut(self.index) {
if let Some(list) = &self.marker_list {
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
if let Some(m) = l.at_mut(self.index) {
m.set_time(self.old_time);
}
list.resort_at(self.index);
l.resort_at(self.index);
}
}
+2 -39
View File
@@ -22,9 +22,8 @@
//! ([`crate::util::NodeRef`] + the project's graph arena).
//!
//! `CHandleCommandWrapper` in C++ subclasses `olive::UndoCommand` to wrap a
//! raw `OakUndoCommand`; the Rust equivalent holds an
//! [`oakundo::undocommand::UndoCommand`] and forwards `redo`/`undo` to
//! `redo_now`/`undo_now`.
//! raw `OakUndoCommand`; the Rust equivalent is the crate's own commands
//! boxed through [`box_command`], so the wrapper is gone.
use std::ffi::c_void;
@@ -188,42 +187,6 @@ pub(crate) fn box_command<T: Command + 'static>(cmd: T) -> UndoCommand {
UndoCommand::from_vtable(vtable, userdata)
}
/// `CHandleCommandWrapper` — an oakundo command exposed as a timeline-level
/// command. `redo`/`undo` forward to `redo_now`/`undo_now`; dropping drops
/// the wrapped command.
pub struct CHandleCommandWrapper {
/// Wrapped command; `None` mirrors the old empty (null) command handle.
command: Option<UndoCommand>,
}
impl CHandleCommandWrapper {
/// Construct over an owned command value.
pub fn new(command: UndoCommand) -> Self {
Self {
command: Some(command),
}
}
/// Whether the wrapper holds a command (the old non-null check).
pub fn is_valid(&self) -> bool {
self.command.is_some()
}
/// `redo`: forward to `UndoCommand::redo_now`.
pub fn redo(&mut self) {
if let Some(c) = self.command.as_mut() {
c.redo_now();
}
}
/// `undo`: forward to `UndoCommand::undo_now`.
pub fn undo(&mut self) {
if let Some(c) = self.command.as_mut() {
c.undo_now();
}
}
}
/// `MultiUndoCommand` — a command that runs several child commands in order
/// on `redo` and in reverse on `undo` (timelineundocommon.h
/// `MultiUndoCommand`). Children are boxed `Command`s; the whole group wraps
+78 -14
View File
@@ -21,9 +21,13 @@
//! De-Qt: no QObject, no signals — change notifications are the facade's
//! job.
use std::sync::{Arc, Mutex};
use oakcore_rs::{Rational, TimeRange};
use oakundo::undocommand::UndoCommand;
use crate::handle::get;
/// The reset sentinel `k_reset_in` (timelineworkarea.h): 0/1. Exposed as a
/// function because `Rational::new` is not yet `const` in oakcore-rs.
pub fn reset_in() -> Rational {
@@ -90,8 +94,9 @@ impl TimelineWorkArea {
/// `WorkareaSetEnabledCommand` (timelineundoworkarea.h).
pub struct WorkareaSetEnabledCommand {
/// Target work area handle.
points: crate::handle::CHandle,
/// Target work area (shared behind the facade's value handle; `None`
/// for an empty/null handle, which makes the command a no-op).
points: Option<Arc<Mutex<TimelineWorkArea>>>,
/// New enabled flag.
new_enabled: bool,
/// Enabled flag captured at construction, restored by `undo`.
@@ -101,11 +106,29 @@ pub struct WorkareaSetEnabledCommand {
impl WorkareaSetEnabledCommand {
/// Construct from work area + new enabled value (captures old at ctor).
pub fn new(points: crate::handle::CHandle, enabled: bool) -> Self {
let old_enabled = unsafe { crate::handle::get::<TimelineWorkArea>(&points) }
.map(|wa| wa.enabled())
// SAFETY: work-area handles box an `Arc<Mutex<TimelineWorkArea>>`
// (created by `make_owned`); reading it clones the shared `Arc`.
let wa = unsafe { get::<Arc<Mutex<TimelineWorkArea>>>(&points) }.cloned();
let old_enabled = wa
.as_ref()
.and_then(|w| {
let w = w.lock().unwrap_or_else(|e| e.into_inner());
Some(w.enabled())
})
.unwrap_or(false);
Self {
points,
points: wa,
new_enabled: enabled,
old_enabled,
}
}
/// Construct over an already-shared work area (the crate's Rust-typed
/// entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(points: Arc<Mutex<TimelineWorkArea>>, enabled: bool) -> Self {
let old_enabled = points.lock().unwrap_or_else(|e| e.into_inner()).enabled();
Self {
points: Some(points),
new_enabled: enabled,
old_enabled,
}
@@ -113,14 +136,16 @@ impl WorkareaSetEnabledCommand {
/// `redo`: set enabled.
pub fn redo(&mut self) {
if let Some(wa) = unsafe { crate::handle::get_mut::<TimelineWorkArea>(&self.points) } {
if let Some(wa) = &self.points {
let mut wa = wa.lock().unwrap_or_else(|e| e.into_inner());
wa.set_enabled(self.new_enabled);
}
}
/// `undo`: restore old enabled.
pub fn undo(&mut self) {
if let Some(wa) = unsafe { crate::handle::get_mut::<TimelineWorkArea>(&self.points) } {
if let Some(wa) = &self.points {
let mut wa = wa.lock().unwrap_or_else(|e| e.into_inner());
wa.set_enabled(self.old_enabled);
}
}
@@ -146,8 +171,9 @@ impl crate::undocommon::Command for WorkareaSetEnabledCommand {
/// `WorkareaSetRangeCommand` (timelineundoworkarea.h). The old range is
/// captured at construction when not supplied.
pub struct WorkareaSetRangeCommand {
/// Target work area handle.
workarea: crate::handle::CHandle,
/// Target work area (shared behind the facade's value handle; `None`
/// for an empty/null handle, which makes the command a no-op).
workarea: Option<Arc<Mutex<TimelineWorkArea>>>,
/// New range.
new_range: TimeRange,
/// Range captured at construction, restored by `undo`.
@@ -157,10 +183,23 @@ pub struct WorkareaSetRangeCommand {
impl WorkareaSetRangeCommand {
/// Construct from work area + new range (captures current as old).
pub fn new(workarea: crate::handle::CHandle, range: TimeRange) -> Self {
let old_range = unsafe { crate::handle::get::<TimelineWorkArea>(&workarea) }
.map(|wa| *wa.range())
// SAFETY: as `WorkareaSetEnabledCommand::new`.
let wa = unsafe { get::<Arc<Mutex<TimelineWorkArea>>>(&workarea) }.cloned();
let old_range = wa
.as_ref()
.and_then(|w| {
let w = w.lock().unwrap_or_else(|e| e.into_inner());
Some(*w.range())
})
.unwrap_or(range);
Self::new_with_old(workarea, range, old_range)
Self::new_with_old_opt(wa, range, old_range)
}
/// Construct over an already-shared work area (the crate's Rust-typed
/// entry; the facade-facing [`Self::new`] forwards here).
pub fn new_arc(workarea: Arc<Mutex<TimelineWorkArea>>, range: TimeRange) -> Self {
let old_range = *workarea.lock().unwrap_or_else(|e| e.into_inner()).range();
Self::new_with_old_opt(Some(workarea), range, old_range)
}
/// Construct from work area + new range + explicitly supplied old
@@ -170,6 +209,29 @@ impl WorkareaSetRangeCommand {
workarea: crate::handle::CHandle,
range: TimeRange,
old_range: TimeRange,
) -> Self {
// SAFETY: as `WorkareaSetEnabledCommand::new`.
let wa = unsafe { get::<Arc<Mutex<TimelineWorkArea>>>(&workarea) }.cloned();
Self::new_with_old_opt(wa, range, old_range)
}
/// Construct over an already-shared work area with an explicitly
/// supplied old range (the crate's Rust-typed entry; the facade-facing
/// [`Self::new_with_old`] forwards here).
pub fn new_with_old_arc(
workarea: Arc<Mutex<TimelineWorkArea>>,
range: TimeRange,
old_range: TimeRange,
) -> Self {
Self::new_with_old_opt(Some(workarea), range, old_range)
}
/// Shared constructor over an optional work area (`None` mirrors the
/// empty/null handle: the command is a no-op).
fn new_with_old_opt(
workarea: Option<Arc<Mutex<TimelineWorkArea>>>,
range: TimeRange,
old_range: TimeRange,
) -> Self {
Self {
workarea,
@@ -180,14 +242,16 @@ impl WorkareaSetRangeCommand {
/// `redo`: set the range.
pub fn redo(&mut self) {
if let Some(wa) = unsafe { crate::handle::get_mut::<TimelineWorkArea>(&self.workarea) } {
if let Some(wa) = &self.workarea {
let mut wa = wa.lock().unwrap_or_else(|e| e.into_inner());
wa.set_range(self.new_range);
}
}
/// `undo`: restore the old range.
pub fn undo(&mut self) {
if let Some(wa) = unsafe { crate::handle::get_mut::<TimelineWorkArea>(&self.workarea) } {
if let Some(wa) = &self.workarea {
let mut wa = wa.lock().unwrap_or_else(|e| e.into_inner());
wa.set_range(self.old_range);
}
}