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 std::rc::Rc;
302
303    use super::*;
304    use crate::{
305        snapshot_v2::runtime::TestRuntimeGuard,
306        state::{ObjectId, StateObject, StateRecord, PREEXISTING_SNAPSHOT_ID},
307    };
308
309    fn reset_runtime() -> TestRuntimeGuard {
310        reset_runtime_for_tests()
311    }
312
313    fn mock_state_record() -> Rc<StateRecord> {
314        StateRecord::new(PREEXISTING_SNAPSHOT_ID, (), None)
315    }
316
317    struct MockState(usize);
318
319    impl StateObject for MockState {
320        fn object_id(&self) -> ObjectId {
321            ObjectId(self.0)
322        }
323
324        fn first_record(&self) -> Rc<StateRecord> {
325            mock_state_record()
326        }
327
328        fn try_readable_record(
329            &self,
330            snapshot_id: SnapshotId,
331            invalid: &SnapshotIdSet,
332        ) -> Option<Rc<StateRecord>> {
333            Some(self.readable_record(snapshot_id, invalid))
334        }
335
336        fn readable_record(
337            &self,
338            _snapshot_id: SnapshotId,
339            _invalid: &SnapshotIdSet,
340        ) -> Rc<StateRecord> {
341            mock_state_record()
342        }
343
344        fn prepend_state_record(&self, _record: Rc<StateRecord>) {}
345
346        fn promote_record(&self, _child_id: SnapshotId) -> Result<(), &'static str> {
347            Ok(())
348        }
349
350        fn as_any(&self) -> &dyn std::any::Any {
351            self
352        }
353    }
354
355    #[test]
356    fn test_transparent_observer_mutable_snapshot() {
357        let _guard = reset_runtime();
358        let snapshot =
359            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
360
361        assert_eq!(snapshot.snapshot_id(), 1);
362        assert!(!snapshot.read_only());
363        assert!(snapshot.can_reuse());
364    }
365
366    #[test]
367    fn test_transparent_observer_mutable_apply() {
368        let _guard = reset_runtime();
369        let snapshot =
370            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
371
372        let result = snapshot.apply();
373        assert!(result.is_success());
374    }
375
376    #[test]
377    fn test_transparent_observer_snapshot() {
378        let _guard = reset_runtime();
379        let snapshot = TransparentObserverSnapshot::new(1, SnapshotIdSet::new(), None, None);
380
381        assert_eq!(snapshot.snapshot_id(), 1);
382        assert!(snapshot.read_only());
383        assert!(snapshot.can_reuse());
384    }
385
386    #[test]
387    #[should_panic(expected = "Cannot write to a read-only snapshot")]
388    fn test_transparent_observer_snapshot_write_panics() {
389        let _guard = reset_runtime();
390
391        let snapshot = TransparentObserverSnapshot::new(1, SnapshotIdSet::new(), None, None);
392
393        let mock_state = Arc::new(MockState(0));
394        snapshot.record_write(mock_state);
395    }
396
397    #[test]
398    fn transparent_mutable_set_read_observer_replaces_observer() {
399        let _guard = reset_runtime();
400        let initial_reads = Rc::new(Cell::new(0));
401        let replacement_reads = Rc::new(Cell::new(0));
402        let snapshot = TransparentObserverMutableSnapshot::new(
403            1,
404            SnapshotIdSet::new(),
405            Some(Arc::new({
406                let initial_reads = Rc::clone(&initial_reads);
407                move |_| initial_reads.set(initial_reads.get() + 1)
408            })),
409            None,
410            None,
411        );
412
413        snapshot.set_read_observer(Some(Arc::new({
414            let replacement_reads = Rc::clone(&replacement_reads);
415            move |_| replacement_reads.set(replacement_reads.get() + 1)
416        })));
417        snapshot.record_read(&MockState(1));
418
419        assert_eq!(initial_reads.get(), 0);
420        assert_eq!(replacement_reads.get(), 1);
421    }
422
423    #[test]
424    fn transparent_mutable_set_write_observer_replaces_observer() {
425        let _guard = reset_runtime();
426        let initial_writes = Rc::new(Cell::new(0));
427        let replacement_writes = Rc::new(Cell::new(0));
428        let snapshot = TransparentObserverMutableSnapshot::new(
429            1,
430            SnapshotIdSet::new(),
431            None,
432            Some(Arc::new({
433                let initial_writes = Rc::clone(&initial_writes);
434                move |_| initial_writes.set(initial_writes.get() + 1)
435            })),
436            None,
437        );
438
439        snapshot.set_write_observer(Some(Arc::new({
440            let replacement_writes = Rc::clone(&replacement_writes);
441            move |_| replacement_writes.set(replacement_writes.get() + 1)
442        })));
443        snapshot.record_write(Arc::new(MockState(2)));
444
445        assert_eq!(initial_writes.get(), 0);
446        assert_eq!(replacement_writes.get(), 1);
447    }
448
449    #[test]
450    fn transparent_mutable_nested_snapshot_inherits_replaced_observers() {
451        let _guard = reset_runtime();
452        let parent_reads = Rc::new(Cell::new(0));
453        let parent_writes = Rc::new(Cell::new(0));
454        let snapshot =
455            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
456        snapshot.set_read_observer(Some(Arc::new({
457            let parent_reads = Rc::clone(&parent_reads);
458            move |_| parent_reads.set(parent_reads.get() + 1)
459        })));
460        snapshot.set_write_observer(Some(Arc::new({
461            let parent_writes = Rc::clone(&parent_writes);
462            move |_| parent_writes.set(parent_writes.get() + 1)
463        })));
464
465        let nested = snapshot.take_nested_mutable_snapshot(None, None);
466        nested.record_read(&MockState(3));
467        nested.record_write(Arc::new(MockState(4)));
468
469        assert_eq!(parent_reads.get(), 1);
470        assert_eq!(parent_writes.get(), 1);
471    }
472
473    #[test]
474    fn transparent_readonly_set_read_observer_replaces_observer() {
475        let _guard = reset_runtime();
476        let initial_reads = Rc::new(Cell::new(0));
477        let replacement_reads = Rc::new(Cell::new(0));
478        let snapshot = TransparentObserverSnapshot::new(
479            1,
480            SnapshotIdSet::new(),
481            Some(Arc::new({
482                let initial_reads = Rc::clone(&initial_reads);
483                move |_| initial_reads.set(initial_reads.get() + 1)
484            })),
485            None,
486        );
487
488        snapshot.set_read_observer(Some(Arc::new({
489            let replacement_reads = Rc::clone(&replacement_reads);
490            move |_| replacement_reads.set(replacement_reads.get() + 1)
491        })));
492        snapshot.record_read(&MockState(5));
493
494        assert_eq!(initial_reads.get(), 0);
495        assert_eq!(replacement_reads.get(), 1);
496    }
497
498    #[test]
499    fn test_transparent_observer_mutable_nested() {
500        let _guard = reset_runtime();
501        let parent =
502            TransparentObserverMutableSnapshot::new(1, SnapshotIdSet::new(), None, None, None);
503
504        let nested = parent.take_nested_mutable_snapshot(None, None);
505        assert!(nested.snapshot_id() > parent.snapshot_id());
506    }
507
508    #[test]
509    fn test_transparent_observer_snapshot_nested() {
510        let _guard = reset_runtime();
511        let parent = TransparentObserverSnapshot::new(1, SnapshotIdSet::new(), None, None);
512
513        let nested = parent.take_nested_snapshot(None);
514        assert_eq!(nested.snapshot_id(), parent.snapshot_id());
515        assert!(nested.read_only());
516    }
517}