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