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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user