Skip to main content

cranpose_core/snapshot_v2/
transparent.rs

1use super::*;
2
3/// A transparent mutable snapshot that allows observer replacement.
4///
5/// This snapshot type is optimized for cases where observers need to be
6/// temporarily added or removed without creating a new snapshot structure.
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 TransparentObserverMutableSnapshot {
13    pub(super) state: SnapshotState,
14    parent: Option<Weak<Self>>,
15    nested_count: Cell<usize>,
16    applied: Cell<bool>,
17    reusable: Cell<bool>,
18}
19
20impl TransparentObserverMutableSnapshot {
21    /// Creates a transparent mutable snapshot with the given id and invalid set. The observers are
22    /// called for reads and writes inside it and can be replaced later. `parent` is the snapshot it
23    /// was taken from, if that snapshot is still alive.
24    pub fn new(
25        id: SnapshotId,
26        invalid: SnapshotIdSet,
27        read_observer: Option<ReadObserver>,
28        write_observer: Option<WriteObserver>,
29        parent: Option<Weak<Self>>,
30    ) -> Rc<Self> {
31        Self::new_reusing(None, id, invalid, read_observer, write_observer, parent)
32    }
33
34    pub(crate) fn new_reusing(
35        recycled: Option<Rc<Self>>,
36        id: SnapshotId,
37        invalid: SnapshotIdSet,
38        read_observer: Option<ReadObserver>,
39        write_observer: Option<WriteObserver>,
40        parent: Option<Weak<Self>>,
41    ) -> Rc<Self> {
42        let fresh = Self {
43            state: SnapshotState::new_with_pinning(
44                id,
45                invalid,
46                read_observer,
47                write_observer,
48                RuntimeTracking::Untracked,
49                Pinning::Unpinned,
50            ),
51            parent,
52            nested_count: Cell::new(0),
53            applied: Cell::new(false),
54            reusable: Cell::new(true),
55        };
56        if let Some(mut recycled) = recycled
57            && let Some(target) = Rc::get_mut(&mut recycled)
58        {
59            *target = fresh;
60            recycled
61        } else {
62            Rc::new(fresh)
63        }
64    }
65
66    /// Check if this snapshot can be reused for observer changes.
67    pub fn can_reuse(&self) -> bool {
68        self.reusable.get()
69    }
70
71    pub(crate) fn observers(&self) -> (Option<ReadObserver>, Option<WriteObserver>) {
72        (
73            self.state.read_observer.borrow().clone(),
74            self.state.write_observer.borrow().clone(),
75        )
76    }
77
78    pub(crate) fn reads_into(&self, observer: &ReadObserver) -> bool {
79        self.state
80            .read_observer
81            .borrow()
82            .as_ref()
83            .is_some_and(|installed| Rc::ptr_eq(installed, observer))
84    }
85
86    /// Set the read observer (only allowed if reusable).
87    pub fn set_read_observer(&self, observer: Option<ReadObserver>) {
88        assert!(
89            self.can_reuse(),
90            "Cannot change observers on non-reusable snapshot"
91        );
92        *self.state.read_observer.borrow_mut() = observer;
93    }
94
95    /// Set the write observer (only allowed if reusable).
96    pub fn set_write_observer(&self, observer: Option<WriteObserver>) {
97        assert!(
98            self.can_reuse(),
99            "Cannot change observers on non-reusable snapshot"
100        );
101        *self.state.write_observer.borrow_mut() = observer;
102    }
103
104    /// Returns the id of this snapshot. Ids grow with every snapshot taken.
105    pub fn snapshot_id(&self) -> SnapshotId {
106        self.state.id.get()
107    }
108
109    /// Returns the ids of snapshots whose writes this snapshot does not see.
110    pub fn invalid(&self) -> SnapshotIdSet {
111        self.state.invalid.borrow().clone()
112    }
113
114    /// Returns `false`: this snapshot accepts writes.
115    pub fn read_only(&self) -> bool {
116        false
117    }
118
119    /// Returns the outermost parent that is still alive, or this snapshot when it has none.
120    pub fn root_transparent_mutable(self: &Rc<Self>) -> Rc<Self> {
121        match &self.parent {
122            Some(weak) => weak.upgrade().map_or_else(
123                || Rc::clone(self),
124                |parent| parent.root_transparent_mutable(),
125            ),
126            None => Rc::clone(self),
127        }
128    }
129
130    /// Runs `f` with this snapshot as the current snapshot of the thread and returns its result. If
131    /// this snapshot is already current, `f` runs directly. The previous current snapshot is
132    /// restored when `f` returns.
133    pub fn enter<T>(self: &Rc<Self>, f: impl FnOnce() -> T) -> T {
134        let prev = current_snapshot();
135
136        if let Some(ref snapshot) = prev
137            && snapshot.is_same_transparent(self)
138        {
139            return f();
140        }
141
142        enter_snapshot_scope(AnySnapshot::TransparentMutable(Rc::clone(self)), f)
143    }
144
145    /// Takes a read-only snapshot of this snapshot's view of state. Reads inside the new snapshot
146    /// call both `read_observer` and this snapshot's own read observer.
147    pub fn take_nested_snapshot(
148        &self,
149        read_observer: Option<ReadObserver>,
150    ) -> Rc<ReadonlySnapshot> {
151        let merged_observer =
152            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
153        ReadonlySnapshot::new(
154            self.state.id.get(),
155            self.state.invalid.borrow().clone(),
156            merged_observer,
157        )
158    }
159
160    /// Returns `true` when this snapshot recorded writes that were not applied yet.
161    pub fn has_pending_changes(&self) -> bool {
162        !self.state.modified.borrow().is_empty()
163    }
164
165    /// Releases the snapshot. Does nothing if it was already disposed or if nested snapshots taken
166    /// from it are still open.
167    pub fn dispose(&self) {
168        if !self.state.disposed.get() && self.nested_count.get() == 0 {
169            self.state.dispose();
170        }
171    }
172
173    /// Calls the read observer, if any, with `state`.
174    pub fn record_read(&self, state: &dyn StateObject) {
175        self.state.record_read(state);
176    }
177
178    /// Records `state` as written in this snapshot. The write observer, if any, is called the first
179    /// time a state object is recorded. Panics if the snapshot was applied.
180    pub fn record_write(&self, state: Rc<dyn StateObject>) {
181        assert!(!self.applied.get(), "Cannot write to an applied snapshot");
182        self.state.record_write(state, self.state.id.get());
183    }
184
185    /// Returns `true` once the snapshot was disposed.
186    pub fn is_disposed(&self) -> bool {
187        self.state.disposed.get()
188    }
189
190    /// Marks the snapshot as applied. Returns `Failure` if it was already applied or disposed.
191    pub fn apply(&self) -> SnapshotApplyResult {
192        if self.state.disposed.get() || self.applied.get() {
193            return SnapshotApplyResult::Failure;
194        }
195
196        self.applied.set(true);
197        SnapshotApplyResult::Success
198    }
199
200    /// Takes a transparent mutable snapshot nested in this one. Reads and writes inside it call
201    /// both the given observers and this snapshot's own observers.
202    pub fn take_nested_mutable_snapshot(
203        &self,
204        read_observer: Option<ReadObserver>,
205        write_observer: Option<WriteObserver>,
206    ) -> Rc<Self> {
207        let merged_read =
208            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
209        let merged_write =
210            merge_write_observers(write_observer, self.state.write_observer.borrow().clone());
211
212        let mut invalid = self.state.invalid.borrow().clone();
213        let new_id = self.state.id.get().next();
214        invalid = invalid.set(new_id);
215
216        Self::new(
217            new_id,
218            invalid,
219            merged_read,
220            merged_write,
221            self.parent.clone(),
222        )
223    }
224}
225
226/// A transparent read-only snapshot.
227///
228/// Similar to TransparentObserverMutableSnapshot but for read-only snapshots.
229///
230/// # Thread Safety
231/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
232/// snapshots are stored in thread-local storage and never shared across threads. The `Rc`
233/// is used for cheap cloning within a single thread, not for cross-thread sharing.
234pub struct TransparentObserverSnapshot {
235    pub(super) state: SnapshotState,
236    parent: Option<Weak<Self>>,
237    reusable: Cell<bool>,
238}
239
240impl TransparentObserverSnapshot {
241    /// Creates a transparent read-only snapshot with the given id and invalid set. `read_observer`
242    /// is called for reads inside it and can be replaced later. `parent` is the snapshot it was
243    /// taken from, if that snapshot is still alive.
244    pub fn new(
245        id: SnapshotId,
246        invalid: SnapshotIdSet,
247        read_observer: Option<ReadObserver>,
248        parent: Option<Weak<Self>>,
249    ) -> Rc<Self> {
250        Rc::new(Self {
251            state: SnapshotState::new_with_pinning(
252                id,
253                invalid,
254                read_observer,
255                None,
256                RuntimeTracking::Untracked,
257                Pinning::Unpinned,
258            ),
259            parent,
260            reusable: Cell::new(true),
261        })
262    }
263
264    /// Check if this snapshot can be reused for observer changes.
265    pub fn can_reuse(&self) -> bool {
266        self.reusable.get()
267    }
268
269    /// Set the read observer (only allowed if reusable).
270    pub fn set_read_observer(&self, observer: Option<ReadObserver>) {
271        assert!(
272            self.can_reuse(),
273            "Cannot change observers on non-reusable snapshot"
274        );
275        *self.state.read_observer.borrow_mut() = observer;
276    }
277
278    /// Returns the id of this snapshot. Ids grow with every snapshot taken.
279    pub fn snapshot_id(&self) -> SnapshotId {
280        self.state.id.get()
281    }
282
283    /// Returns the ids of snapshots whose writes this snapshot does not see.
284    pub fn invalid(&self) -> SnapshotIdSet {
285        self.state.invalid.borrow().clone()
286    }
287
288    /// Returns `true`: this snapshot rejects writes.
289    pub fn read_only(&self) -> bool {
290        true
291    }
292
293    /// Returns the outermost parent that is still alive, or this snapshot when it has none.
294    pub fn root_transparent_readonly(self: &Rc<Self>) -> Rc<Self> {
295        match &self.parent {
296            Some(weak) => weak.upgrade().map_or_else(
297                || Rc::clone(self),
298                |parent| parent.root_transparent_readonly(),
299            ),
300            None => Rc::clone(self),
301        }
302    }
303
304    /// Runs `f` with this snapshot as the current snapshot of the thread and returns its result. If
305    /// this snapshot is already current, `f` runs directly. The previous current snapshot is
306    /// restored when `f` returns.
307    pub fn enter<T>(self: &Rc<Self>, f: impl FnOnce() -> T) -> T {
308        let previous = current_snapshot();
309
310        if let Some(ref prev_snapshot) = previous
311            && prev_snapshot.is_same_transparent_readonly(self)
312        {
313            return f();
314        }
315
316        enter_snapshot_scope(AnySnapshot::TransparentReadonly(Rc::clone(self)), f)
317    }
318
319    /// Takes another transparent read-only snapshot with the same view of state. Reads inside the
320    /// new snapshot call both `read_observer` and this snapshot's own read observer.
321    pub fn take_nested_snapshot(&self, read_observer: Option<ReadObserver>) -> Rc<Self> {
322        let merged_observer =
323            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
324        Self::new(
325            self.state.id.get(),
326            self.state.invalid.borrow().clone(),
327            merged_observer,
328            self.parent.clone(),
329        )
330    }
331
332    /// Returns `false`: a read-only snapshot has no writes.
333    pub fn has_pending_changes(&self) -> bool {
334        false
335    }
336
337    /// Releases the snapshot. Call it once the snapshot is no longer needed.
338    pub fn dispose(&self) {
339        self.state.dispose();
340    }
341
342    /// Calls the read observer, if any, with `state`.
343    pub fn record_read(&self, state: &dyn StateObject) {
344        self.state.record_read(state);
345    }
346
347    /// Always panics: a read-only snapshot rejects writes.
348    pub fn record_write(&self, _state: Rc<dyn StateObject>) {
349        panic!("Cannot write to a read-only snapshot");
350    }
351
352    /// Returns `true` once the snapshot was disposed.
353    pub fn is_disposed(&self) -> bool {
354        self.state.disposed.get()
355    }
356}
357
358#[cfg(test)]
359#[path = "tests/transparent_tests.rs"]
360mod tests;