Skip to main content

cranpose_core/snapshot_v2/
transparent.rs

1//! Transparent observer snapshot implementations.
2//!
3//! These snapshots are optimized for observer chaining, allowing observers
4//! to be temporarily changed without allocating new snapshot structures.
5
6use super::*;
7
8/// A transparent mutable snapshot that allows observer replacement.
9///
10/// This snapshot type is optimized for cases where observers need to be
11/// temporarily added or removed without creating a new snapshot structure.
12///
13/// # Thread Safety
14/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
15/// snapshots are stored in thread-local storage and never shared across threads. The `Arc`
16/// is used for cheap cloning within a single thread, not for cross-thread sharing.
17#[allow(clippy::arc_with_non_send_sync)]
18pub struct TransparentObserverMutableSnapshot {
19    state: SnapshotState,
20    parent: Option<Weak<TransparentObserverMutableSnapshot>>,
21    nested_count: Cell<usize>,
22    applied: Cell<bool>,
23    /// Whether this snapshot can be reused for observer changes
24    reusable: Cell<bool>,
25}
26
27impl TransparentObserverMutableSnapshot {
28    pub fn new(
29        id: SnapshotId,
30        invalid: SnapshotIdSet,
31        read_observer: Option<ReadObserver>,
32        write_observer: Option<WriteObserver>,
33        parent: Option<Weak<TransparentObserverMutableSnapshot>>,
34    ) -> Arc<Self> {
35        Arc::new(Self {
36            // Transparent snapshots don't allocate new IDs, so they shouldn't pin
37            // to prevent garbage collection of old records
38            state: SnapshotState::new_with_pinning(
39                id,
40                invalid,
41                read_observer,
42                write_observer,
43                false,
44                false,
45            ),
46            parent,
47            nested_count: Cell::new(0),
48            applied: Cell::new(false),
49            reusable: Cell::new(true),
50        })
51    }
52
53    /// Check if this snapshot can be reused for observer changes.
54    pub fn can_reuse(&self) -> bool {
55        self.reusable.get()
56    }
57
58    /// Set the read observer (only allowed if reusable).
59    pub fn set_read_observer(&self, observer: Option<ReadObserver>) {
60        if !self.can_reuse() {
61            panic!("Cannot change observers on non-reusable snapshot");
62        }
63        *self.state.read_observer.borrow_mut() = observer;
64    }
65
66    /// Set the write observer (only allowed if reusable).
67    pub fn set_write_observer(&self, observer: Option<WriteObserver>) {
68        if !self.can_reuse() {
69            panic!("Cannot change observers on non-reusable snapshot");
70        }
71        *self.state.write_observer.borrow_mut() = observer;
72    }
73
74    pub fn snapshot_id(&self) -> SnapshotId {
75        self.state.id.get()
76    }
77
78    pub fn invalid(&self) -> SnapshotIdSet {
79        self.state.invalid.borrow().clone()
80    }
81
82    pub fn read_only(&self) -> bool {
83        false
84    }
85
86    pub fn root_transparent_mutable(self: &Arc<Self>) -> Arc<Self> {
87        match &self.parent {
88            Some(weak) => weak
89                .upgrade()
90                .map(|parent| parent.root_transparent_mutable())
91                .unwrap_or_else(|| self.clone()),
92            None => self.clone(),
93        }
94    }
95
96    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
97        let prev = current_snapshot();
98
99        if let Some(ref snapshot) = prev {
100            if snapshot.is_same_transparent(self) {
101                return f();
102            }
103        }
104
105        enter_snapshot_scope(AnySnapshot::TransparentMutable(self.clone()), f)
106    }
107
108    pub fn take_nested_snapshot(
109        &self,
110        read_observer: Option<ReadObserver>,
111    ) -> Arc<ReadonlySnapshot> {
112        let merged_observer =
113            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
114        ReadonlySnapshot::new(
115            self.state.id.get(),
116            self.state.invalid.borrow().clone(),
117            merged_observer,
118        )
119    }
120
121    pub fn has_pending_changes(&self) -> bool {
122        !self.state.modified.borrow().is_empty()
123    }
124
125    pub fn dispose(&self) {
126        if !self.state.disposed.get() && self.nested_count.get() == 0 {
127            self.state.dispose();
128        }
129    }
130
131    pub fn record_read(&self, state: &dyn StateObject) {
132        self.state.record_read(state);
133    }
134
135    pub fn record_write(&self, state: Arc<dyn StateObject>) {
136        if self.applied.get() {
137            panic!("Cannot write to an applied snapshot");
138        }
139        self.state.record_write(state, self.state.id.get());
140    }
141
142    pub fn close(&self) {
143        self.state.disposed.set(true);
144    }
145
146    pub fn is_disposed(&self) -> bool {
147        self.state.disposed.get()
148    }
149
150    pub fn apply(&self) -> SnapshotApplyResult {
151        if self.state.disposed.get() || self.applied.get() {
152            return SnapshotApplyResult::Failure;
153        }
154
155        self.applied.set(true);
156        SnapshotApplyResult::Success
157    }
158
159    pub fn take_nested_mutable_snapshot(
160        &self,
161        read_observer: Option<ReadObserver>,
162        write_observer: Option<WriteObserver>,
163    ) -> Arc<TransparentObserverMutableSnapshot> {
164        let merged_read =
165            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
166        let merged_write =
167            merge_write_observers(write_observer, self.state.write_observer.borrow().clone());
168
169        let mut invalid = self.state.invalid.borrow().clone();
170        let new_id = self.state.id.get() + 1;
171        invalid = invalid.set(new_id);
172
173        TransparentObserverMutableSnapshot::new(
174            new_id,
175            invalid,
176            merged_read,
177            merged_write,
178            self.parent.clone(),
179        )
180    }
181}
182
183/// A transparent read-only snapshot.
184///
185/// Similar to TransparentObserverMutableSnapshot but for read-only snapshots.
186///
187/// # Thread Safety
188/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
189/// snapshots are stored in thread-local storage and never shared across threads. The `Arc`
190/// is used for cheap cloning within a single thread, not for cross-thread sharing.
191#[allow(clippy::arc_with_non_send_sync)]
192pub struct TransparentObserverSnapshot {
193    state: SnapshotState,
194    parent: Option<Weak<TransparentObserverSnapshot>>,
195    reusable: Cell<bool>,
196}
197
198impl TransparentObserverSnapshot {
199    pub fn new(
200        id: SnapshotId,
201        invalid: SnapshotIdSet,
202        read_observer: Option<ReadObserver>,
203        parent: Option<Weak<TransparentObserverSnapshot>>,
204    ) -> Arc<Self> {
205        Arc::new(Self {
206            // Transparent snapshots don't allocate new IDs, so they shouldn't pin
207            state: SnapshotState::new_with_pinning(id, invalid, read_observer, None, false, false),
208            parent,
209            reusable: Cell::new(true),
210        })
211    }
212
213    /// Check if this snapshot can be reused for observer changes.
214    pub fn can_reuse(&self) -> bool {
215        self.reusable.get()
216    }
217
218    /// Set the read observer (only allowed if reusable).
219    pub fn set_read_observer(&self, observer: Option<ReadObserver>) {
220        if !self.can_reuse() {
221            panic!("Cannot change observers on non-reusable snapshot");
222        }
223        *self.state.read_observer.borrow_mut() = observer;
224    }
225
226    pub fn snapshot_id(&self) -> SnapshotId {
227        self.state.id.get()
228    }
229
230    pub fn invalid(&self) -> SnapshotIdSet {
231        self.state.invalid.borrow().clone()
232    }
233
234    pub fn read_only(&self) -> bool {
235        true
236    }
237
238    pub fn root_transparent_readonly(self: &Arc<Self>) -> Arc<Self> {
239        match &self.parent {
240            Some(weak) => weak
241                .upgrade()
242                .map(|parent| parent.root_transparent_readonly())
243                .unwrap_or_else(|| self.clone()),
244            None => self.clone(),
245        }
246    }
247
248    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
249        let previous = current_snapshot();
250
251        if let Some(ref prev_snapshot) = previous {
252            if prev_snapshot.is_same_transparent_readonly(self) {
253                return f();
254            }
255        }
256
257        enter_snapshot_scope(AnySnapshot::TransparentReadonly(self.clone()), f)
258    }
259
260    pub fn take_nested_snapshot(
261        &self,
262        read_observer: Option<ReadObserver>,
263    ) -> Arc<TransparentObserverSnapshot> {
264        let merged_observer =
265            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
266        TransparentObserverSnapshot::new(
267            self.state.id.get(),
268            self.state.invalid.borrow().clone(),
269            merged_observer,
270            self.parent.clone(),
271        )
272    }
273
274    pub fn has_pending_changes(&self) -> bool {
275        false
276    }
277
278    pub fn dispose(&self) {
279        self.state.dispose();
280    }
281
282    pub fn record_read(&self, state: &dyn StateObject) {
283        self.state.record_read(state);
284    }
285
286    pub fn record_write(&self, _state: Arc<dyn StateObject>) {
287        panic!("Cannot write to a read-only snapshot");
288    }
289
290    pub fn close(&self) {
291        self.state.disposed.set(true);
292    }
293
294    pub fn is_disposed(&self) -> bool {
295        self.state.disposed.get()
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use crate::snapshot_v2::runtime::TestRuntimeGuard;
303    use crate::state::{ObjectId, StateObject, StateRecord, PREEXISTING_SNAPSHOT_ID};
304    use std::rc::Rc;
305
306    fn reset_runtime() -> TestRuntimeGuard {
307        reset_runtime_for_tests()
308    }
309
310    fn mock_state_record() -> Rc<StateRecord> {
311        StateRecord::new(PREEXISTING_SNAPSHOT_ID, (), None)
312    }
313
314    struct MockState(usize);
315
316    impl StateObject for MockState {
317        fn object_id(&self) -> ObjectId {
318            ObjectId(self.0)
319        }
320
321        fn first_record(&self) -> Rc<StateRecord> {
322            mock_state_record()
323        }
324
325        fn try_readable_record(
326            &self,
327            snapshot_id: SnapshotId,
328            invalid: &SnapshotIdSet,
329        ) -> Option<Rc<StateRecord>> {
330            Some(self.readable_record(snapshot_id, invalid))
331        }
332
333        fn readable_record(
334            &self,
335            _snapshot_id: SnapshotId,
336            _invalid: &SnapshotIdSet,
337        ) -> Rc<StateRecord> {
338            mock_state_record()
339        }
340
341        fn prepend_state_record(&self, _record: Rc<StateRecord>) {}
342
343        fn promote_record(&self, _child_id: SnapshotId) -> Result<(), &'static str> {
344            Ok(())
345        }
346
347        fn as_any(&self) -> &dyn std::any::Any {
348            self
349        }
350    }
351
352    #[test]
353    fn test_transparent_observer_mutable_snapshot() {
354        let _guard = reset_runtime();
355        let snapshot =
356            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
357
358        assert_eq!(snapshot.snapshot_id(), 1);
359        assert!(!snapshot.read_only());
360        assert!(snapshot.can_reuse());
361    }
362
363    #[test]
364    fn test_transparent_observer_mutable_apply() {
365        let _guard = reset_runtime();
366        let snapshot =
367            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
368
369        let result = snapshot.apply();
370        assert!(result.is_success());
371    }
372
373    #[test]
374    fn test_transparent_observer_snapshot() {
375        let _guard = reset_runtime();
376        let snapshot = TransparentObserverSnapshot::new(1, SnapshotIdSet::new(), None, None);
377
378        assert_eq!(snapshot.snapshot_id(), 1);
379        assert!(snapshot.read_only());
380        assert!(snapshot.can_reuse());
381    }
382
383    #[test]
384    #[should_panic(expected = "Cannot write to a read-only snapshot")]
385    fn test_transparent_observer_snapshot_write_panics() {
386        let _guard = reset_runtime();
387
388        let snapshot = TransparentObserverSnapshot::new(1, SnapshotIdSet::new(), None, None);
389
390        let mock_state = Arc::new(MockState(0));
391        snapshot.record_write(mock_state);
392    }
393
394    #[test]
395    fn transparent_mutable_set_read_observer_replaces_observer() {
396        let _guard = reset_runtime();
397        let initial_reads = Rc::new(Cell::new(0));
398        let replacement_reads = Rc::new(Cell::new(0));
399        let snapshot = TransparentObserverMutableSnapshot::new(
400            1,
401            SnapshotIdSet::new(),
402            Some(Arc::new({
403                let initial_reads = Rc::clone(&initial_reads);
404                move |_| initial_reads.set(initial_reads.get() + 1)
405            })),
406            None,
407            None,
408        );
409
410        snapshot.set_read_observer(Some(Arc::new({
411            let replacement_reads = Rc::clone(&replacement_reads);
412            move |_| replacement_reads.set(replacement_reads.get() + 1)
413        })));
414        snapshot.record_read(&MockState(1));
415
416        assert_eq!(initial_reads.get(), 0);
417        assert_eq!(replacement_reads.get(), 1);
418    }
419
420    #[test]
421    fn transparent_mutable_set_write_observer_replaces_observer() {
422        let _guard = reset_runtime();
423        let initial_writes = Rc::new(Cell::new(0));
424        let replacement_writes = Rc::new(Cell::new(0));
425        let snapshot = TransparentObserverMutableSnapshot::new(
426            1,
427            SnapshotIdSet::new(),
428            None,
429            Some(Arc::new({
430                let initial_writes = Rc::clone(&initial_writes);
431                move |_| initial_writes.set(initial_writes.get() + 1)
432            })),
433            None,
434        );
435
436        snapshot.set_write_observer(Some(Arc::new({
437            let replacement_writes = Rc::clone(&replacement_writes);
438            move |_| replacement_writes.set(replacement_writes.get() + 1)
439        })));
440        snapshot.record_write(Arc::new(MockState(2)));
441
442        assert_eq!(initial_writes.get(), 0);
443        assert_eq!(replacement_writes.get(), 1);
444    }
445
446    #[test]
447    fn transparent_mutable_nested_snapshot_inherits_replaced_observers() {
448        let _guard = reset_runtime();
449        let parent_reads = Rc::new(Cell::new(0));
450        let parent_writes = Rc::new(Cell::new(0));
451        let snapshot =
452            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
453        snapshot.set_read_observer(Some(Arc::new({
454            let parent_reads = Rc::clone(&parent_reads);
455            move |_| parent_reads.set(parent_reads.get() + 1)
456        })));
457        snapshot.set_write_observer(Some(Arc::new({
458            let parent_writes = Rc::clone(&parent_writes);
459            move |_| parent_writes.set(parent_writes.get() + 1)
460        })));
461
462        let nested = snapshot.take_nested_mutable_snapshot(None, None);
463        nested.record_read(&MockState(3));
464        nested.record_write(Arc::new(MockState(4)));
465
466        assert_eq!(parent_reads.get(), 1);
467        assert_eq!(parent_writes.get(), 1);
468    }
469
470    #[test]
471    fn transparent_readonly_set_read_observer_replaces_observer() {
472        let _guard = reset_runtime();
473        let initial_reads = Rc::new(Cell::new(0));
474        let replacement_reads = Rc::new(Cell::new(0));
475        let snapshot = TransparentObserverSnapshot::new(
476            1,
477            SnapshotIdSet::new(),
478            Some(Arc::new({
479                let initial_reads = Rc::clone(&initial_reads);
480                move |_| initial_reads.set(initial_reads.get() + 1)
481            })),
482            None,
483        );
484
485        snapshot.set_read_observer(Some(Arc::new({
486            let replacement_reads = Rc::clone(&replacement_reads);
487            move |_| replacement_reads.set(replacement_reads.get() + 1)
488        })));
489        snapshot.record_read(&MockState(5));
490
491        assert_eq!(initial_reads.get(), 0);
492        assert_eq!(replacement_reads.get(), 1);
493    }
494
495    #[test]
496    fn test_transparent_observer_mutable_nested() {
497        let _guard = reset_runtime();
498        let parent =
499            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
500
501        let nested = parent.take_nested_mutable_snapshot(None, None);
502        assert!(nested.snapshot_id() > parent.snapshot_id());
503    }
504
505    #[test]
506    fn test_transparent_observer_snapshot_nested() {
507        let _guard = reset_runtime();
508        let parent = TransparentObserverSnapshot::new(1, SnapshotIdSet::new(), None, None);
509
510        let nested = parent.take_nested_snapshot(None);
511        assert_eq!(nested.snapshot_id(), parent.snapshot_id());
512        assert!(nested.read_only());
513    }
514}