use std::cell::RefCell;
use std::collections::VecDeque;
use std::ops::Deref;
use std::rc::Rc;
use crate::pcp;
use super::diff::{apply_edits_verbatim, forward_diff, inverse_diff};
use super::sink::{CommittedChange, PendingChange, StageSink, StageSinkId};
use super::stage::{Stage, StageAuthoringError};
use super::{Diff, Edit};
const DEFAULT_CAPACITY: usize = 128;
pub struct UndoStage {
capture: CaptureStage<Journal>,
}
impl From<Stage> for UndoStage {
fn from(stage: Stage) -> Self {
Self::with_capacity(stage, DEFAULT_CAPACITY)
}
}
impl UndoStage {
pub fn with_capacity(stage: Stage, capacity: usize) -> Self {
let journal = Journal {
stack: VecDeque::new(),
in_flight: Vec::new(),
suspended: false,
capacity: capacity.max(1),
};
Self {
capture: CaptureStage::new(stage, journal, |journal| UndoRecorder { journal }),
}
}
pub fn undo(&self) -> Result<bool, StageAuthoringError> {
self.capture.process_pending();
let transaction = {
let mut journal = self.capture.log().borrow_mut();
match journal.stack.pop_back() {
Some(transaction) => {
journal.suspended = true;
transaction
}
None => return Ok(false),
}
};
let result = {
let _resume = ClearSuspend(self.capture.log());
self.replay(&transaction)
};
match result {
Ok(()) => Ok(true),
Err(error) => {
self.capture.log().borrow_mut().stack.push_back(transaction);
Err(error)
}
}
}
pub fn reset(&self) {
let mut journal = self.capture.log().borrow_mut();
journal.stack.clear();
journal.in_flight.clear();
}
pub fn can_undo(&self) -> bool {
self.capture.process_pending();
!self.capture.log().borrow().stack.is_empty()
}
pub fn undo_depth(&self) -> usize {
self.capture.process_pending();
self.capture.log().borrow().stack.len()
}
pub fn into_inner(self) -> Stage {
self.capture.into_inner()
}
fn replay(&self, transaction: &Transaction) -> Result<(), StageAuthoringError> {
let batch: Vec<(pcp::LayerId, &[Edit])> = {
let graph = self.capture.layers();
transaction
.layers
.iter()
.filter_map(|(identifier, edits)| graph.id_of(identifier).map(|id| (id, edits.as_slice())))
.collect()
};
if batch.is_empty() {
return Ok(());
}
let ids: Vec<pcp::LayerId> = batch.iter().map(|(id, _)| *id).collect();
self.capture
.author_layers_txn(&ids, transaction.mapping.as_ref(), true, |realized, edits| {
for (edit, &id) in edits.iter_mut().zip(realized) {
if let Some((_, ops)) = batch.iter().find(|(batch_id, _)| *batch_id == id) {
apply_edits_verbatim(edit, ops).map_err(StageAuthoringError::from)?;
}
}
Ok(())
})
}
}
impl Deref for UndoStage {
type Target = Stage;
fn deref(&self) -> &Stage {
&self.capture
}
}
struct Transaction {
mapping: Option<pcp::MapFunction>,
layers: Vec<(String, Vec<Edit>)>,
}
struct InFlight {
generation: u64,
mapping: Option<pcp::MapFunction>,
layers: Vec<(String, Vec<Edit>)>,
}
struct Journal {
stack: VecDeque<Transaction>,
in_flight: Vec<InFlight>,
suspended: bool,
capacity: usize,
}
struct ClearSuspend<'a>(&'a RefCell<Journal>);
impl Drop for ClearSuspend<'_> {
fn drop(&mut self) {
self.0.borrow_mut().suspended = false;
}
}
struct UndoRecorder {
journal: Rc<RefCell<Journal>>,
}
impl StageSink for UndoRecorder {
fn before_commit(&self, _stage: &Stage, change: &PendingChange<'_>) {
let mut journal = self.journal.borrow_mut();
if journal.suspended {
return;
}
let Ok(edits) = inverse_diff(change) else {
return;
};
if edits.is_empty() {
return;
}
let layer = (change.layer_identifier.to_string(), edits);
match journal.in_flight.last_mut() {
Some(bucket) if bucket.generation == change.generation => bucket.layers.push(layer),
_ => journal.in_flight.push(InFlight {
generation: change.generation,
mapping: change.mapping.cloned(),
layers: vec![layer],
}),
}
}
fn after_commit(&self, _stage: &Stage, change: &CommittedChange<'_>) {
let mut journal = self.journal.borrow_mut();
if journal.suspended {
return;
}
let generation = change.generation;
while journal
.in_flight
.first()
.is_some_and(|bucket| bucket.generation < generation)
{
journal.in_flight.remove(0);
}
if journal.in_flight.first().map(|bucket| bucket.generation) != Some(generation) {
return;
}
let bucket = journal.in_flight.remove(0);
journal.stack.push_back(Transaction {
mapping: bucket.mapping,
layers: bucket.layers,
});
while journal.stack.len() > journal.capacity {
journal.stack.pop_front();
}
}
}
pub struct ReplayStage {
capture: CaptureStage<Vec<Diff>>,
}
impl From<Stage> for ReplayStage {
fn from(stage: Stage) -> Self {
Self {
capture: CaptureStage::new(stage, Vec::new(), |log| ReplayRecorder { log }),
}
}
}
impl ReplayStage {
pub fn diff(&self) -> Vec<Diff> {
self.capture.log().borrow().clone()
}
pub fn clear(&self) {
self.capture.log().borrow_mut().clear();
}
pub fn into_inner(self) -> Stage {
self.capture.into_inner()
}
}
impl Deref for ReplayStage {
type Target = Stage;
fn deref(&self) -> &Stage {
&self.capture
}
}
struct ReplayRecorder {
log: Rc<RefCell<Vec<Diff>>>,
}
impl StageSink for ReplayRecorder {
fn after_commit(&self, stage: &Stage, change: &CommittedChange<'_>) {
if let Ok(diffs) = forward_diff(stage, change) {
self.log.borrow_mut().extend(diffs);
}
}
}
struct CaptureStage<L> {
stage: Stage,
log: Rc<RefCell<L>>,
sink: StageSinkId,
}
impl<L> CaptureStage<L> {
fn new<S: StageSink + 'static>(stage: Stage, log: L, make_sink: impl FnOnce(Rc<RefCell<L>>) -> S) -> Self {
let log = Rc::new(RefCell::new(log));
let sink = stage.add_sink(make_sink(log.clone()));
Self { stage, log, sink }
}
fn log(&self) -> &RefCell<L> {
&self.log
}
fn into_inner(self) -> Stage {
self.stage.clone()
}
}
impl<L> Deref for CaptureStage<L> {
type Target = Stage;
fn deref(&self) -> &Stage {
&self.stage
}
}
impl<L> Drop for CaptureStage<L> {
fn drop(&mut self) {
self.stage.remove_sink(self.sink);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf;
use crate::usd::ApplyMode;
use anyhow::Result;
fn in_memory_stage() -> Result<Stage> {
Stage::builder().in_memory("anon.usda")
}
fn author_prim(layer: &mut sdf::LayerEdit<'_>, path: &str) -> Result<(), sdf::AuthoringError> {
sdf::PrimSpec::new(layer.data_mut(), path, sdf::Specifier::Def, "Xform")?;
Ok(())
}
#[test]
fn undo_unwinds_stack() -> Result<()> {
let stage = UndoStage::from(in_memory_stage()?);
stage.define_prim("/World")?.set_type_name("Xform")?;
assert!(stage.prim("/World").is_valid()?);
assert_eq!(stage.undo_depth(), 2);
assert!(stage.undo()?);
assert_eq!(stage.prim("/World").type_name()?.as_deref(), None);
assert!(stage.prim("/World").is_valid()?);
assert!(stage.undo()?);
assert!(!stage.prim("/World").is_valid()?);
assert!(!stage.can_undo());
assert!(!stage.undo()?);
Ok(())
}
#[test]
fn undo_restores_value() -> Result<()> {
let stage = UndoStage::from(in_memory_stage()?);
stage.define_prim("/World")?;
stage.create_attribute("/World.size", "double")?.set(1.0_f64)?;
stage.attribute("/World.size").set(2.0_f64)?;
assert_eq!(stage.attribute("/World.size").get::<f64>()?, Some(2.0));
assert!(stage.undo()?);
assert_eq!(stage.attribute("/World.size").get::<f64>()?, Some(1.0));
Ok(())
}
#[test]
fn undo_groups_transaction() -> Result<()> {
let stage = UndoStage::from(in_memory_stage()?);
stage.define_prim("/A/B/C")?;
assert!(stage.prim("/A/B/C").is_valid()?);
assert_eq!(stage.undo_depth(), 1);
assert!(stage.undo()?);
assert!(!stage.prim("/A").is_valid()?);
assert!(!stage.prim("/A/B/C").is_valid()?);
Ok(())
}
#[test]
fn undo_pending_direct_edit() -> Result<()> {
let stage = UndoStage::from(in_memory_stage()?);
let root = stage.root_layer().identifier().to_string();
{
let mut layer = stage.layer_mut(&root).expect("root layer");
layer.edit(|l| author_prim(l, "/Direct"))?;
}
stage.define_prim("/Staged")?;
assert!(stage.prim("/Direct").is_valid()?);
assert!(stage.prim("/Staged").is_valid()?);
assert_eq!(stage.undo_depth(), 2);
assert!(stage.undo()?); assert!(!stage.prim("/Staged").is_valid()?);
assert!(stage.prim("/Direct").is_valid()?, "the direct edit survives");
assert!(stage.undo()?); assert!(!stage.prim("/Direct").is_valid()?);
Ok(())
}
#[test]
fn undo_direct_edit_no_drain() -> Result<()> {
let stage = UndoStage::from(in_memory_stage()?);
let root = stage.root_layer().identifier().to_string();
{
let mut layer = stage.layer_mut(&root).expect("root layer");
layer.edit(|l| author_prim(l, "/Direct"))?;
}
assert!(stage.undo()?, "the pending direct edit is undone");
assert!(!stage.prim("/Direct").is_valid()?);
assert!(!stage.can_undo());
Ok(())
}
#[test]
fn reset_keeps_scene() -> Result<()> {
let stage = UndoStage::from(in_memory_stage()?);
stage.define_prim("/World")?;
stage.reset();
assert!(stage.prim("/World").is_valid()?);
assert!(!stage.can_undo());
assert!(!stage.undo()?);
Ok(())
}
#[test]
fn capacity_eviction() -> Result<()> {
let stage = UndoStage::with_capacity(in_memory_stage()?, 1);
stage.define_prim("/A")?;
stage.define_prim("/B")?;
assert_eq!(stage.undo_depth(), 1);
assert!(stage.undo()?);
assert!(!stage.prim("/B").is_valid()?);
assert!(stage.prim("/A").is_valid()?, "the evicted /A edit is permanent");
assert!(!stage.undo()?);
Ok(())
}
#[test]
fn undo_into_inner_returns_stage() -> Result<()> {
let undo = UndoStage::from(in_memory_stage()?);
undo.define_prim("/World")?;
let stage = undo.into_inner();
assert_eq!(stage.sink_count(), 0, "into_inner removes the sink");
stage.define_prim("/Another")?;
assert!(stage.prim("/World").is_valid()?);
assert!(stage.prim("/Another").is_valid()?);
Ok(())
}
#[test]
fn undo_drop_removes_sink() -> Result<()> {
let stage = in_memory_stage()?;
assert_eq!(stage.sink_count(), 0);
{
let undo = UndoStage::from(stage.clone());
assert_eq!(stage.sink_count(), 1);
undo.define_prim("/World")?;
}
assert_eq!(stage.sink_count(), 0, "the sink is removed when the wrapper drops");
stage.define_prim("/Another")?;
assert!(stage.prim("/World").is_valid()?);
assert!(stage.prim("/Another").is_valid()?);
Ok(())
}
#[test]
fn replay_onto_mirror() -> Result<()> {
let source = ReplayStage::from(in_memory_stage()?);
source.define_prim("/World")?.set_type_name("Xform")?;
source.define_prim("/World/Mesh")?.set_type_name("Mesh")?;
source.create_attribute("/World/Mesh.size", "double")?.set(2.0_f64)?;
let mirror = in_memory_stage()?;
for diff in source.diff() {
mirror.apply_diff(&diff, ApplyMode::CurrentEditTarget)?;
}
assert_eq!(mirror.prim("/World").type_name()?.as_deref(), Some("Xform"));
assert_eq!(mirror.prim("/World/Mesh").type_name()?.as_deref(), Some("Mesh"));
assert_eq!(mirror.attribute("/World/Mesh.size").get::<f64>()?, Some(2.0));
Ok(())
}
#[test]
fn clear_and_into_inner() -> Result<()> {
let source = ReplayStage::from(in_memory_stage()?);
source.define_prim("/World")?;
assert!(!source.diff().is_empty());
source.clear();
assert!(source.diff().is_empty());
let stage = source.into_inner();
assert_eq!(stage.sink_count(), 0, "into_inner removes the sink");
stage.define_prim("/Another")?;
assert!(stage.prim("/Another").is_valid()?);
Ok(())
}
#[test]
fn replay_drop_removes_sink() -> Result<()> {
let stage = in_memory_stage()?;
{
let source = ReplayStage::from(stage.clone());
assert_eq!(stage.sink_count(), 1);
source.define_prim("/World")?;
}
assert_eq!(stage.sink_count(), 0, "the sink is removed when the wrapper drops");
Ok(())
}
}