Skip to main content

cranpose_core/snapshot_v2/
nested.rs

1use super::*;
2
3/// A nested read-only snapshot.
4///
5/// This is a read-only snapshot that has a parent snapshot. It inherits
6/// the parent's invalid set and can be disposed independently.
7///
8/// # Thread Safety
9/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
10/// snapshots are stored in thread-local storage and never shared across threads. The `Rc`
11/// is used for cheap cloning within a single thread, not for cross-thread sharing.
12pub struct NestedReadonlySnapshot {
13    pub(super) state: SnapshotState,
14    parent: Weak<Self>,
15}
16
17impl NestedReadonlySnapshot {
18    /// Creates a nested read-only snapshot with the given id and invalid set. `read_observer` is
19    /// called for reads inside it. `parent` is the snapshot it was taken from, if that snapshot is
20    /// still alive.
21    pub fn new(
22        id: SnapshotId,
23        invalid: SnapshotIdSet,
24        read_observer: Option<ReadObserver>,
25        parent: Weak<Self>,
26    ) -> Rc<Self> {
27        Rc::new(Self {
28            state: SnapshotState::new(id, invalid, read_observer, None, RuntimeTracking::Untracked),
29            parent,
30        })
31    }
32
33    /// Returns the id of this snapshot. Ids grow with every snapshot taken.
34    pub fn snapshot_id(&self) -> SnapshotId {
35        self.state.id.get()
36    }
37
38    /// Returns the ids of snapshots whose writes this snapshot does not see.
39    pub fn invalid(&self) -> SnapshotIdSet {
40        self.state.invalid.borrow().clone()
41    }
42
43    /// Returns `true`: this snapshot rejects writes.
44    pub fn read_only(&self) -> bool {
45        true
46    }
47
48    /// Returns the outermost parent that is still alive, or a copy of this snapshot when it has no
49    /// live parent.
50    pub fn root_nested_readonly(&self) -> Rc<Self> {
51        if let Some(parent) = self.parent.upgrade() {
52            parent.root_nested_readonly()
53        } else {
54            Self::new(
55                self.state.id.get(),
56                self.state.invalid.borrow().clone(),
57                self.state.read_observer.borrow().clone(),
58                Weak::new(),
59            )
60        }
61    }
62
63    /// Runs `f` with this snapshot as the current snapshot of the thread and returns its result.
64    /// The previous current snapshot is restored when `f` returns.
65    pub fn enter<T>(self: &Rc<Self>, f: impl FnOnce() -> T) -> T {
66        enter_snapshot_scope(AnySnapshot::NestedReadonly(Rc::clone(self)), f)
67    }
68
69    /// Takes another read-only snapshot with the same view of state. Reads inside the new snapshot
70    /// call both `read_observer` and this snapshot's own read observer.
71    pub fn take_nested_snapshot(&self, read_observer: Option<ReadObserver>) -> Rc<Self> {
72        let merged_observer =
73            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
74
75        Self::new(
76            self.state.id.get(),
77            self.state.invalid.borrow().clone(),
78            merged_observer,
79            Weak::clone(&self.parent),
80        )
81    }
82
83    /// Returns `false`: a read-only snapshot has no writes.
84    pub fn has_pending_changes(&self) -> bool {
85        false
86    }
87
88    /// Releases the snapshot. Does nothing if it was already disposed.
89    pub fn dispose(&self) {
90        if !self.state.disposed.get() {
91            self.state.dispose();
92        }
93    }
94
95    /// Calls the read observer, if any, with `state`.
96    pub fn record_read(&self, state: &dyn StateObject) {
97        self.state.record_read(state);
98    }
99
100    /// Always panics: a read-only snapshot rejects writes.
101    pub fn record_write(&self, _state: Rc<dyn StateObject>) {
102        panic!("Cannot write to a read-only snapshot");
103    }
104
105    /// Returns `true` once the snapshot was disposed.
106    pub fn is_disposed(&self) -> bool {
107        self.state.disposed.get()
108    }
109}
110
111/// A nested mutable snapshot.
112///
113/// This is a mutable snapshot that has a parent. Changes made in this
114/// snapshot are applied to the parent when `apply()` is called, not
115/// to the global snapshot.
116///
117/// # Thread Safety
118/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
119/// snapshots are stored in thread-local storage and never shared across threads. The `Rc`
120/// is used for cheap cloning within a single thread, not for cross-thread sharing.
121pub struct NestedMutableSnapshot {
122    pub(super) state: SnapshotState,
123    parent: Weak<MutableSnapshot>,
124    nested_count: Cell<usize>,
125    applied: Cell<bool>,
126    base_parent_id: SnapshotId,
127}
128
129impl NestedMutableSnapshot {
130    /// Creates a nested mutable snapshot with the given id and invalid set. `parent` receives the
131    /// writes on `apply`. `base_parent_id` is the id of the parent when this snapshot was taken.
132    pub fn new(
133        id: SnapshotId,
134        invalid: SnapshotIdSet,
135        read_observer: Option<ReadObserver>,
136        write_observer: Option<WriteObserver>,
137        parent: Weak<MutableSnapshot>,
138        base_parent_id: SnapshotId,
139    ) -> Rc<Self> {
140        Rc::new(Self {
141            state: SnapshotState::new(
142                id,
143                invalid,
144                read_observer,
145                write_observer,
146                RuntimeTracking::Tracked,
147            ),
148            parent,
149            nested_count: Cell::new(0),
150            applied: Cell::new(false),
151            base_parent_id,
152        })
153    }
154
155    /// Returns the id of this snapshot. Ids grow with every snapshot taken.
156    pub fn snapshot_id(&self) -> SnapshotId {
157        self.state.id.get()
158    }
159
160    /// Returns the ids of snapshots whose writes this snapshot does not see.
161    pub fn invalid(&self) -> SnapshotIdSet {
162        self.state.invalid.borrow().clone()
163    }
164
165    /// Returns `false`: this snapshot accepts writes.
166    pub fn read_only(&self) -> bool {
167        false
168    }
169
170    pub(crate) fn set_on_dispose<F>(&self, f: F)
171    where
172        F: FnOnce() + 'static,
173    {
174        self.state.set_on_dispose(f);
175    }
176
177    /// Returns the root mutable snapshot above this one. If the root is gone, returns a new root
178    /// snapshot built from this snapshot's id, invalid set and observers.
179    pub fn root_mutable(&self) -> Rc<MutableSnapshot> {
180        if let Some(parent) = self.parent.upgrade() {
181            parent.root_mutable()
182        } else {
183            MutableSnapshot::new(
184                self.state.id.get(),
185                self.state.invalid.borrow().clone(),
186                self.state.read_observer.borrow().clone(),
187                self.state.write_observer.borrow().clone(),
188                self.base_parent_id,
189            )
190        }
191    }
192
193    /// Runs `f` with this snapshot as the current snapshot of the thread and returns its result.
194    /// The previous current snapshot is restored when `f` returns.
195    pub fn enter<T>(self: &Rc<Self>, f: impl FnOnce() -> T) -> T {
196        enter_snapshot_scope(AnySnapshot::NestedMutable(Rc::clone(self)), f)
197    }
198
199    /// Takes a read-only snapshot of this snapshot's view of state. Reads inside the new snapshot
200    /// call both `read_observer` and this snapshot's own read observer.
201    pub fn take_nested_snapshot(
202        &self,
203        read_observer: Option<ReadObserver>,
204    ) -> Rc<ReadonlySnapshot> {
205        let merged_observer =
206            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
207
208        ReadonlySnapshot::new(
209            self.state.id.get(),
210            self.state.invalid.borrow().clone(),
211            merged_observer,
212        )
213    }
214
215    /// Returns `true` when this snapshot recorded writes that were not applied yet.
216    pub fn has_pending_changes(&self) -> bool {
217        !self.state.modified.borrow().is_empty()
218    }
219
220    /// Returns the ids of nested mutable snapshots taken from this snapshot and not yet disposed.
221    pub fn pending_children(&self) -> Vec<SnapshotId> {
222        self.state.pending_children()
223    }
224
225    /// Returns `true` when a nested mutable snapshot taken from this snapshot is still open.
226    pub fn has_pending_children(&self) -> bool {
227        self.state.has_pending_children()
228    }
229
230    /// Discards the writes that were not applied and releases the snapshot. Does nothing while
231    /// nested snapshots taken from it are still open.
232    pub fn dispose(&self) {
233        if !self.state.disposed.get() && self.nested_count.get() == 0 {
234            self.state.dispose();
235        }
236    }
237
238    /// Calls the read observer, if any, with `state`.
239    pub fn record_read(&self, state: &dyn StateObject) {
240        self.state.record_read(state);
241    }
242
243    /// Records `state` as written in this snapshot. The write observer, if any, is called the first
244    /// time a state object is recorded. Panics if the snapshot was applied or disposed.
245    pub fn record_write(&self, state: Rc<dyn StateObject>) {
246        assert!(!self.applied.get(), "Cannot write to an applied snapshot");
247        assert!(
248            !self.state.disposed.get(),
249            "Cannot write to a disposed snapshot"
250        );
251        self.state.record_write(state, self.state.id.get());
252    }
253
254    /// Returns `true` once the snapshot was disposed.
255    pub fn is_disposed(&self) -> bool {
256        self.state.disposed.get()
257    }
258
259    /// Moves the writes of this snapshot into its parent. Returns `Failure` if the snapshot was
260    /// already applied or disposed, if the parent is gone, or if the parent wrote one of the same
261    /// state objects.
262    pub fn apply(&self) -> SnapshotApplyResult {
263        if self.state.disposed.get() {
264            return SnapshotApplyResult::Failure;
265        }
266
267        if self.applied.get() {
268            return SnapshotApplyResult::Failure;
269        }
270
271        if let Some(parent) = self.parent.upgrade() {
272            let child_modified = self.state.modified.borrow();
273            if child_modified.is_empty() {
274                self.applied.set(true);
275                self.state.dispose();
276                return SnapshotApplyResult::Success;
277            }
278            if parent.merge_child_modifications(&child_modified).is_err() {
279                return SnapshotApplyResult::Failure;
280            }
281
282            self.applied.set(true);
283            self.state.dispose();
284            SnapshotApplyResult::Success
285        } else {
286            SnapshotApplyResult::Failure
287        }
288    }
289
290    /// Takes a mutable snapshot nested in this one. Its `apply` moves the writes into this
291    /// snapshot.
292    pub fn take_nested_mutable_snapshot(
293        self: &Rc<Self>,
294        read_observer: Option<ReadObserver>,
295        write_observer: Option<WriteObserver>,
296    ) -> Rc<Self> {
297        let root = Rc::downgrade(&self.root_mutable());
298        allocate_nested_mutable_snapshot(self, root, read_observer, write_observer)
299    }
300}
301
302impl NestedMutableHost for NestedMutableSnapshot {
303    fn snapshot_state(&self) -> &SnapshotState {
304        &self.state
305    }
306
307    fn nested_count(&self) -> &Cell<usize> {
308        &self.nested_count
309    }
310}
311
312#[cfg(test)]
313#[path = "tests/nested_tests.rs"]
314mod tests;