Skip to main content

cranpose_core/snapshot_v2/
readonly.rs

1use super::*;
2
3/// A read-only snapshot of state at a specific point in time.
4///
5/// This snapshot cannot be used to modify state. Any attempts to write
6/// to state objects while this snapshot is active will fail.
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 ReadonlySnapshot {
14    state: SnapshotState,
15}
16
17impl ReadonlySnapshot {
18    /// Create a new read-only snapshot.
19    pub fn new(
20        id: SnapshotId,
21        invalid: SnapshotIdSet,
22        read_observer: Option<ReadObserver>,
23    ) -> Arc<Self> {
24        Arc::new(Self {
25            state: SnapshotState::new(id, invalid, read_observer, None, false),
26        })
27    }
28
29    pub fn snapshot_id(&self) -> SnapshotId {
30        self.state.id.get()
31    }
32
33    pub fn invalid(&self) -> SnapshotIdSet {
34        self.state.invalid.borrow().clone()
35    }
36
37    pub fn read_only(&self) -> bool {
38        true
39    }
40
41    pub fn root_readonly(&self) -> Arc<Self> {
42        ReadonlySnapshot::new(
43            self.state.id.get(),
44            self.state.invalid.borrow().clone(),
45            self.state.read_observer.borrow().clone(),
46        )
47    }
48
49    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
50        enter_snapshot_scope(AnySnapshot::Readonly(self.root_readonly()), f)
51    }
52
53    pub fn take_nested_snapshot(&self, read_observer: Option<ReadObserver>) -> Arc<Self> {
54        let merged_observer =
55            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
56        ReadonlySnapshot::new(
57            self.state.id.get(),
58            self.state.invalid.borrow().clone(),
59            merged_observer,
60        )
61    }
62
63    pub fn has_pending_changes(&self) -> bool {
64        false
65    }
66
67    pub fn dispose(&self) {
68        self.state.dispose();
69    }
70
71    pub fn record_read(&self, state: &dyn StateObject) {
72        self.state.record_read(state);
73    }
74
75    pub fn record_write(&self, _state: Arc<dyn StateObject>) {
76        panic!("Cannot write to a read-only snapshot");
77    }
78
79    pub fn is_disposed(&self) -> bool {
80        self.state.disposed.get()
81    }
82
83    pub(crate) fn set_on_dispose<F>(&self, f: F)
84    where
85        F: FnOnce() + 'static,
86    {
87        self.state.set_on_dispose(f);
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use std::rc::Rc;
94
95    use super::*;
96    use crate::state::{PREEXISTING_SNAPSHOT_ID, StateObject};
97
98    fn mock_state_record() -> Rc<crate::state::StateRecord> {
99        crate::state::StateRecord::new(PREEXISTING_SNAPSHOT_ID, (), None)
100    }
101
102    struct MockStateObject;
103
104    impl StateObject for MockStateObject {
105        fn object_id(&self) -> crate::state::ObjectId {
106            crate::state::ObjectId(0)
107        }
108
109        fn first_record(&self) -> Rc<crate::state::StateRecord> {
110            mock_state_record()
111        }
112
113        fn try_readable_record(
114            &self,
115            snapshot_id: crate::snapshot_id_set::SnapshotId,
116            invalid: &SnapshotIdSet,
117        ) -> Option<Rc<crate::state::StateRecord>> {
118            Some(self.readable_record(snapshot_id, invalid))
119        }
120
121        fn readable_record(
122            &self,
123            _snapshot_id: crate::snapshot_id_set::SnapshotId,
124            _invalid: &SnapshotIdSet,
125        ) -> Rc<crate::state::StateRecord> {
126            mock_state_record()
127        }
128
129        fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
130
131        fn promote_record(
132            &self,
133            _child_id: crate::snapshot_id_set::SnapshotId,
134        ) -> Result<(), &'static str> {
135            Ok(())
136        }
137
138        fn as_any(&self) -> &dyn std::any::Any {
139            self
140        }
141    }
142
143    #[test]
144    fn test_readonly_snapshot_creation() {
145        let snapshot = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
146        assert_eq!(snapshot.snapshot_id(), 1);
147        assert!(!snapshot.is_disposed());
148    }
149
150    #[test]
151    fn test_readonly_snapshot_is_valid() {
152        let invalid = SnapshotIdSet::new().set(5);
153        let snapshot = ReadonlySnapshot::new(10, invalid, None);
154
155        let any_snapshot = AnySnapshot::Readonly(snapshot.clone());
156        assert!(any_snapshot.is_valid(1));
157        assert!(any_snapshot.is_valid(10));
158        assert!(!any_snapshot.is_valid(5));
159        assert!(!any_snapshot.is_valid(11));
160    }
161
162    #[test]
163    fn test_readonly_snapshot_no_pending_changes() {
164        let snapshot = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
165        assert!(!snapshot.has_pending_changes());
166    }
167
168    #[test]
169    fn test_readonly_snapshot_enter() {
170        let snapshot = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
171
172        set_current_snapshot(None);
173        assert!(current_snapshot().is_none());
174
175        snapshot.enter(|| {
176            let current = current_snapshot();
177            assert!(current.is_some());
178            assert_eq!(current.unwrap().snapshot_id(), 1);
179        });
180
181        assert!(current_snapshot().is_none());
182    }
183
184    #[test]
185    fn test_readonly_snapshot_enter_restores_previous() {
186        let snapshot1 = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
187        let snapshot2 = ReadonlySnapshot::new(2, SnapshotIdSet::new(), None);
188
189        snapshot1.enter(|| {
190            snapshot2.enter(|| {
191                let current = current_snapshot();
192                assert_eq!(current.unwrap().snapshot_id(), 2);
193            });
194
195            let current = current_snapshot();
196            assert_eq!(current.unwrap().snapshot_id(), 1);
197        });
198    }
199
200    #[test]
201    fn test_readonly_snapshot_nested() {
202        let parent = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
203        let nested = parent.take_nested_snapshot(None);
204
205        assert_eq!(nested.snapshot_id(), 1);
206    }
207
208    #[test]
209    fn test_readonly_snapshot_read_observer() {
210        use std::sync::{Arc as StdArc, Mutex};
211
212        let read_count = StdArc::new(Mutex::new(0));
213        let read_count_clone = read_count.clone();
214
215        let observer = Arc::new(move |_: &dyn StateObject| {
216            *read_count_clone.lock().unwrap() += 1;
217        });
218
219        let snapshot = ReadonlySnapshot::new(1, SnapshotIdSet::new(), Some(observer));
220        let mock_state = MockStateObject;
221
222        snapshot.record_read(&mock_state);
223        snapshot.record_read(&mock_state);
224
225        assert_eq!(*read_count.lock().unwrap(), 2);
226    }
227
228    #[test]
229    fn test_readonly_snapshot_nested_with_observer() {
230        use std::sync::{Arc as StdArc, Mutex};
231
232        let parent_reads = StdArc::new(Mutex::new(0));
233        let parent_reads_clone = parent_reads.clone();
234        let parent_observer = Arc::new(move |_: &dyn StateObject| {
235            *parent_reads_clone.lock().unwrap() += 1;
236        });
237
238        let nested_reads = StdArc::new(Mutex::new(0));
239        let nested_reads_clone = nested_reads.clone();
240        let nested_observer = Arc::new(move |_: &dyn StateObject| {
241            *nested_reads_clone.lock().unwrap() += 1;
242        });
243
244        let parent = ReadonlySnapshot::new(1, SnapshotIdSet::new(), Some(parent_observer));
245        let nested = parent.take_nested_snapshot(Some(nested_observer));
246
247        let mock_state = MockStateObject;
248
249        nested.record_read(&mock_state);
250
251        assert_eq!(*parent_reads.lock().unwrap(), 1);
252        assert_eq!(*nested_reads.lock().unwrap(), 1);
253    }
254
255    #[test]
256    #[should_panic(expected = "Cannot write to a read-only snapshot")]
257    fn test_readonly_snapshot_write_panics() {
258        let snapshot = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
259        let mock_state = Arc::new(MockStateObject);
260        snapshot.record_write(mock_state);
261    }
262
263    #[test]
264    fn test_readonly_snapshot_dispose() {
265        let snapshot = ReadonlySnapshot::new(1, SnapshotIdSet::new(), None);
266        assert!(!snapshot.is_disposed());
267
268        snapshot.dispose();
269        assert!(snapshot.is_disposed());
270    }
271}