Skip to main content

snapshottable/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3
4extern crate alloc;
5
6use alloc::{
7    boxed::Box,
8    rc::{Rc, Weak},
9    vec,
10};
11use core::{
12    cell::{Cell, RefCell},
13    sync::atomic::AtomicUsize,
14};
15
16/// A bag of mutable objects (references) with snapshot and restore capabilities.
17pub struct Store {
18    root: Node,
19    generation: usize,
20    store_id: usize,
21}
22
23impl Store {
24    /// Creates a new `Store` with an initial empty mapping.
25    #[allow(clippy::new_without_default)]
26    pub fn new() -> Self {
27        static STORE_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
28        Store {
29            root: Node(Rc::new(Cell::new(NodeData::Mem))),
30            generation: 0,
31            store_id: STORE_ID_COUNTER.fetch_add(1, core::sync::atomic::Ordering::SeqCst),
32        }
33    }
34
35    /// Sets the value of a reference `r` inside this store.
36    pub fn set<T: 'static + Clone>(&mut self, r: &Ref<T>, value: T) {
37        if self.generation == r.0.generation.get() {
38            r.0.value.replace(value);
39        } else {
40            let new_root = Node(Rc::new(Cell::new(NodeData::Mem)));
41            let old_root = core::mem::replace(&mut self.root, new_root.clone());
42            old_root.0.replace(NodeData::Diff(Box::new(Diff {
43                r: r.clone(),
44                value: r.0.value.replace(value),
45                generation: r.0.generation.replace(self.generation),
46                parent: new_root,
47            })));
48        }
49    }
50
51    /// Captures the current state of all references in a `Snapshot`.
52    pub fn capture(&mut self) -> Snapshot {
53        let snap = Snapshot {
54            root: self.root.clone(),
55            generation: self.generation,
56            store_id: self.store_id,
57        };
58        self.generation += 1;
59        snap
60    }
61
62    /// Restores the references to the exact state they were in when this
63    /// `Snapshot` was taken.
64    ///
65    /// Restoring panics if you try to restore a snapshot spanning from a
66    /// different store instance.
67    pub fn restore(&mut self, snap: Snapshot) {
68        if snap.store_id != self.store_id {
69            panic!("Cannot restore from a snapshot from a different store");
70        }
71        // SAFETY: There are no mutable references to the store's internal graph.
72        if let NodeData::Mem = unsafe { &*snap.root.0.as_ptr() } {
73            return;
74        }
75        reroot(&snap.root);
76        self.root = snap.root;
77        self.generation = snap.generation + 1;
78    }
79}
80
81/// A snapshot-aware mutable reference to a value.
82pub struct Ref<T>(Rc<RefInner<T>>);
83
84impl<T> Ref<T> {
85    /// Creates a new, detached reference wrapping the given `value`.
86    pub fn new(value: T) -> Self {
87        Ref(Rc::new(RefInner {
88            value: RefCell::new(value),
89            generation: Cell::new(0),
90        }))
91    }
92
93    /// Fetches and clones the current value of this reference.
94    pub fn get(&self) -> T
95    where
96        T: Clone,
97    {
98        self.borrow().clone()
99    }
100
101    /// Borrows the current value of this reference.
102    pub fn borrow(&self) -> core::cell::Ref<'_, T> {
103        self.0.value.borrow()
104    }
105
106    /// Sets the value of this reference in the provided `Store`.
107    pub fn set(&self, store: &mut Store, value: T)
108    where
109        T: Clone + 'static,
110    {
111        store.set(self, value);
112    }
113
114    /// Creates a weak reference to this reference.
115    pub fn downgrade(this: &Self) -> WeakRef<T> {
116        WeakRef(Rc::downgrade(&this.0))
117    }
118}
119
120impl<T> Clone for Ref<T> {
121    fn clone(&self) -> Self {
122        Ref(Rc::clone(&self.0))
123    }
124}
125
126struct RefInner<T> {
127    value: RefCell<T>,
128    generation: Cell<usize>,
129}
130
131/// Weak reference version of [`Ref`], like [`Weak`] is to [`Rc`].
132pub struct WeakRef<T>(Weak<RefInner<T>>);
133
134impl<T> WeakRef<T> {
135    /// Attempts to upgrade this weak reference to a strong one.
136    pub fn upgrade(&self) -> Option<Ref<T>> {
137        self.0.upgrade().map(Ref)
138    }
139}
140
141impl<T> Clone for WeakRef<T> {
142    fn clone(&self) -> Self {
143        WeakRef(self.0.clone())
144    }
145}
146
147/// An opaque handle recording a captured version of the store references.
148#[derive(Clone)]
149pub struct Snapshot {
150    root: Node,
151    generation: usize,
152    store_id: usize,
153}
154
155// Internal Representation of history graph. The `Node` tree structure maps state
156// generations recursively. A `Mem` node represents the "current" memory baseline.
157// When traversing a `Diff` back, the old values update the current references.
158#[derive(Clone)]
159struct Node(Rc<Cell<NodeData>>);
160
161enum NodeData {
162    // Defines the current branch point. Exactly ONE `Mem` node always exists as
163    // the globally "tracked" active graph head inside the store.
164    Mem,
165    // Holds the dynamic boxed callback trait back-linking graph layers.
166    Diff(Box<dyn ReRoot>),
167}
168
169struct Diff<T> {
170    r: Ref<T>,
171    // The previous generation's baseline value.
172    value: T,
173    // The previously tracked generator epoch.
174    generation: usize,
175    // Ascends via reversed pointers towards the older ancestor mapping.
176    parent: Node,
177}
178
179// Dynamic dispatch fallback enforcing type-erasure mapping so single node histories
180// can backtrack values universally without storing type signatures throughout Node traversal.
181trait ReRoot {
182    fn reroot(&self, this: Node, parent: &Node);
183    fn parent(&self) -> &Node;
184}
185
186impl<T: 'static + Clone> ReRoot for Diff<T> {
187    // Unwinds the captured specific mutation into the references underlying cell
188    // and updates reverse graph edges mapping it to its newer successor snapshot tree.
189    fn reroot(&self, this: Node, parent: &Node) {
190        assert!(Rc::ptr_eq(&self.parent.0, &parent.0));
191        parent.0.replace(NodeData::Diff(Box::new(Diff {
192            r: self.r.clone(),
193            value: self.r.0.value.replace(self.value.clone()),
194            generation: self.r.0.generation.replace(self.generation),
195            parent: this,
196        })));
197    }
198
199    fn parent(&self) -> &Node {
200        &self.parent
201    }
202}
203
204// Crawls from the arbitrary snapshot `n` up the parent chains, collecting
205// diff inversions into `Mem`. Flushes the inverted stack pushing values back to
206// the active globally-viewed variables and rotating the `Mem` node backwards.
207fn reroot(mut n: &Node) {
208    let mut stack = vec![];
209    loop {
210        // SAFETY: There are no mutable references to the store's internal graph.
211        match unsafe { &*n.0.as_ptr() } {
212            NodeData::Mem => break,
213            NodeData::Diff(diff) => {
214                stack.push((diff, n));
215                n = diff.parent();
216            }
217        }
218    }
219    while let Some((diff, node)) = stack.pop() {
220        diff.reroot(node.clone(), n);
221        n = node;
222    }
223    n.0.replace(NodeData::Mem);
224}