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