Skip to main content

jellyflow_core/ops/history/
store.rs

1use crate::ops::{GraphTransaction, normalize_transaction};
2
3/// Maximum number of transactions retained by default.
4pub const DEFAULT_HISTORY_LIMIT: usize = 256;
5
6/// A simple undo/redo history for committed graph transactions.
7#[derive(Debug, Clone)]
8pub struct GraphHistory {
9    limit: usize,
10    undo: Vec<GraphTransaction>,
11    redo: Vec<GraphTransaction>,
12}
13
14impl Default for GraphHistory {
15    fn default() -> Self {
16        Self::new(DEFAULT_HISTORY_LIMIT)
17    }
18}
19
20impl GraphHistory {
21    pub fn new(limit: usize) -> Self {
22        Self {
23            limit: limit.max(1),
24            undo: Vec::new(),
25            redo: Vec::new(),
26        }
27    }
28
29    pub fn clear(&mut self) {
30        self.undo.clear();
31        self.redo.clear();
32    }
33
34    pub fn can_undo(&self) -> bool {
35        !self.undo.is_empty()
36    }
37
38    pub fn can_redo(&self) -> bool {
39        !self.redo.is_empty()
40    }
41
42    pub fn undo_len(&self) -> usize {
43        self.undo.len()
44    }
45
46    pub fn redo_len(&self) -> usize {
47        self.redo.len()
48    }
49
50    /// Records a committed transaction (original + derived concretization ops).
51    pub fn record(&mut self, tx: GraphTransaction) {
52        let tx = normalize_transaction(tx);
53        if tx.is_empty() {
54            return;
55        }
56        self.undo.push(tx);
57        self.redo.clear();
58        if self.undo.len() > self.limit {
59            let overflow = self.undo.len() - self.limit;
60            self.undo.drain(0..overflow);
61        }
62    }
63
64    /// Undoes the last recorded transaction by applying its inverse transaction.
65    ///
66    /// The `apply` closure is responsible for applying the transaction to the graph and returning
67    /// the committed transaction (including any derived ops produced by the profile pipeline).
68    pub fn undo<E>(
69        &mut self,
70        mut apply: impl FnMut(&GraphTransaction) -> Result<GraphTransaction, E>,
71    ) -> Result<bool, E> {
72        let Some(tx) = self.undo.pop() else {
73            return Ok(false);
74        };
75
76        let inverse = tx.inverse();
77        match apply(&inverse) {
78            Ok(committed) => {
79                let redo_tx = normalize_transaction(committed.inverse());
80                if !redo_tx.is_empty() {
81                    self.redo.push(redo_tx);
82                }
83                Ok(true)
84            }
85            Err(err) => {
86                self.undo.push(tx);
87                Err(err)
88            }
89        }
90    }
91
92    /// Redoes the last undone transaction.
93    pub fn redo<E>(
94        &mut self,
95        mut apply: impl FnMut(&GraphTransaction) -> Result<GraphTransaction, E>,
96    ) -> Result<bool, E> {
97        let Some(tx) = self.redo.pop() else {
98            return Ok(false);
99        };
100
101        match apply(&tx) {
102            Ok(committed) => {
103                let committed = normalize_transaction(committed);
104                if !committed.is_empty() {
105                    self.undo.push(committed);
106                }
107                Ok(true)
108            }
109            Err(err) => {
110                self.redo.push(tx);
111                Err(err)
112            }
113        }
114    }
115}