// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! The process-wide undo stack, undo groups and the command-success
//! observer hook.
//!
//! The retired engine facade used to own the process-wide stack (the
//! module-00 analogue of `EngineCore::undo_stack()`), the open undo group
//! and the write-through notification as facade state; all of it is
//! process state, so this module holds it directly. The observer registry
//! lets downstream modules (the oakstorage write-through session manager)
//! subscribe to "a command was recorded" notifications.
//!
//! Everything here is Rust-typed: the process-wide stack is a plain
//! `static Mutex` and the open group holds an
//! [`UndoCommand`] value. The former `CHandle`-marshalling entry point
//! (`push_or_run`) and the raw-pointer out-parameter queries
//! (`can_undo(out)` / `command_text(buf, size)` / `command_is_done(out)`)
//! were deleted with the C ABI — the same operations are exposed as
//! value-typed functions below.
use std::sync::{Mutex, OnceLock};
use crate::error::Result;
use crate::undocommand::UndoCommand;
use crate::undostack::UndoStack;
/// The process-wide undo stack, created lazily on first use and kept for
/// the process lifetime.
fn global_stack() -> &'static Mutex {
static STACK: OnceLock> = OnceLock::new();
STACK.get_or_init(|| Mutex::new(UndoStack::new()))
}
/// Run `f` on the process-wide stack; a poisoned mutex is recovered (its
/// inner value is still valid).
fn with_stack(f: impl FnOnce(&mut UndoStack) -> Result) -> Result {
let mut guard = global_stack().lock().unwrap_or_else(|e| e.into_inner());
f(&mut guard)
}
// ---------------------------------------------------------------------------
// Command-success observers
// ---------------------------------------------------------------------------
/// A callback invoked after a command is successfully recorded on the
/// process-wide stack (a stack push, a group end or a jump).
pub type CommandObserver = fn();
static OBSERVERS: OnceLock>> = OnceLock::new();
fn observers() -> &'static Mutex> {
OBSERVERS.get_or_init(|| Mutex::new(Vec::new()))
}
/// Register a command-success observer. The callback runs after the stack
/// mutation is complete, outside the stack/group locks; multiple observers
/// are supported and run in registration order. There is no un-registration
/// API — observers are process-lifetime, mirroring the global stack itself.
pub fn add_observer(f: CommandObserver) {
observers().lock().unwrap_or_else(|e| e.into_inner()).push(f);
}
/// Invoke every registered observer (called on the stack-path push, the
/// group end and the jump success).
fn notify_observers() {
let callbacks: Vec = observers()
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone();
for cb in callbacks {
cb();
}
}
// ---------------------------------------------------------------------------
// Undo group
// ---------------------------------------------------------------------------
/// The currently open undo group (a multi command value) plus its name.
struct OpenGroup {
/// Multi command value; owned by this state until end/abort.
multi: UndoCommand,
/// Group label.
#[allow(dead_code)]
name: String,
}
static GROUP: Mutex