Skip to main content

cranpose_core/snapshot_v2/
mutable.rs

1//! Mutable snapshot implementation.
2
3use super::*;
4use crate::collections::map::HashMap;
5use crate::state::{StateRecord, PREEXISTING_SNAPSHOT_ID};
6use std::rc::Rc;
7use std::sync::Arc;
8
9pub(super) fn find_record_by_id(
10    head: &Rc<StateRecord>,
11    target: SnapshotId,
12) -> Option<Rc<StateRecord>> {
13    let mut cursor = Some(Rc::clone(head));
14    while let Some(record) = cursor {
15        if !record.is_tombstone() && record.snapshot_id() == target {
16            return Some(record);
17        }
18        cursor = record.next();
19    }
20    None
21}
22
23/// Find the record that was readable when the snapshot was created.
24///
25/// This uses both base_snapshot_id and invalid set to find the record,
26/// matching Kotlin's `readable(first, snapshotId, applyingSnapshot.invalid)`.
27/// Records in the invalid set are filtered out even if their ID <= base_snapshot_id.
28pub(super) fn find_previous_record(
29    head: &Rc<StateRecord>,
30    base_snapshot_id: SnapshotId,
31    invalid: &SnapshotIdSet,
32) -> (Option<Rc<StateRecord>>, bool) {
33    let mut cursor = Some(Rc::clone(head));
34    let mut best: Option<Rc<StateRecord>> = None;
35    let mut fallback: Option<Rc<StateRecord>> = None;
36    let mut found_base = false;
37
38    while let Some(record) = cursor {
39        if !record.is_tombstone() {
40            let id = record.snapshot_id();
41            // A record is valid if id <= base_snapshot_id AND id is NOT in invalid set
42            let is_valid = id <= base_snapshot_id && !invalid.get(id);
43            if is_valid {
44                found_base = true;
45                let replace = best
46                    .as_ref()
47                    .map(|current| current.snapshot_id() < id)
48                    .unwrap_or(true);
49                if replace {
50                    best = Some(record.clone());
51                }
52            }
53            // Fallback captures the first non-tombstone record regardless of validity
54            if fallback.is_none() {
55                fallback = Some(record.clone());
56            }
57        }
58        cursor = record.next();
59    }
60
61    (best.or(fallback), found_base)
62}
63
64enum ApplyOperation {
65    PromoteChild {
66        object_id: StateObjectId,
67        state: Arc<dyn StateObject>,
68        writer_id: SnapshotId,
69    },
70    PromoteExisting {
71        object_id: StateObjectId,
72        state: Arc<dyn StateObject>,
73        source_id: SnapshotId,
74        applied: Rc<StateRecord>,
75    },
76    CommitMerged {
77        object_id: StateObjectId,
78        state: Arc<dyn StateObject>,
79        merged: Rc<StateRecord>,
80        applied: Rc<StateRecord>,
81    },
82}
83
84/// A mutable snapshot that allows isolated state changes.
85///
86/// Changes made in a mutable snapshot are isolated from other snapshots
87/// until `apply()` is called, at which point they become visible atomically.
88/// This is a root mutable snapshot (not nested).
89///
90/// # Thread Safety
91/// Contains `Cell<T>` which is not `Send`/`Sync`. This is safe because snapshots
92/// are stored in thread-local storage and never shared across threads. The `Arc`
93/// is used for cheap cloning within a single thread, not for cross-thread sharing.
94#[allow(clippy::arc_with_non_send_sync)]
95pub struct MutableSnapshot {
96    state: SnapshotState,
97    /// The parent's snapshot id at the time this snapshot was created
98    base_parent_id: SnapshotId,
99    /// Number of active nested snapshots
100    nested_count: Cell<usize>,
101    /// Whether this snapshot has been applied
102    applied: Cell<bool>,
103}
104
105impl MutableSnapshot {
106    pub(crate) fn from_parts(
107        id: SnapshotId,
108        invalid: SnapshotIdSet,
109        read_observer: Option<ReadObserver>,
110        write_observer: Option<WriteObserver>,
111        base_parent_id: SnapshotId,
112        runtime_tracked: bool,
113    ) -> Arc<Self> {
114        Arc::new(Self {
115            state: SnapshotState::new(id, invalid, read_observer, write_observer, runtime_tracked),
116            base_parent_id,
117            nested_count: Cell::new(0),
118            applied: Cell::new(false),
119        })
120    }
121
122    /// Create a new root mutable snapshot using the global runtime.
123    pub fn new_root(
124        read_observer: Option<ReadObserver>,
125        write_observer: Option<WriteObserver>,
126    ) -> Arc<Self> {
127        GlobalSnapshot::get_or_create().take_nested_mutable_snapshot(read_observer, write_observer)
128    }
129
130    /// Create a new root mutable snapshot.
131    pub fn new(
132        id: SnapshotId,
133        invalid: SnapshotIdSet,
134        read_observer: Option<ReadObserver>,
135        write_observer: Option<WriteObserver>,
136        base_parent_id: SnapshotId,
137    ) -> Arc<Self> {
138        Self::from_parts(
139            id,
140            invalid,
141            read_observer,
142            write_observer,
143            base_parent_id,
144            false,
145        )
146    }
147
148    fn validate_not_applied(&self) {
149        if self.applied.get() {
150            panic!("Snapshot has already been applied");
151        }
152    }
153
154    fn validate_not_disposed(&self) {
155        if self.state.disposed.get() {
156            panic!("Snapshot has been disposed");
157        }
158    }
159
160    pub fn snapshot_id(&self) -> SnapshotId {
161        self.state.id.get()
162    }
163
164    pub fn invalid(&self) -> SnapshotIdSet {
165        self.state.invalid.borrow().clone()
166    }
167
168    pub fn read_only(&self) -> bool {
169        false
170    }
171
172    pub(crate) fn set_on_dispose<F>(&self, f: F)
173    where
174        F: FnOnce() + 'static,
175    {
176        self.state.set_on_dispose(f);
177    }
178
179    pub fn root_mutable(self: &Arc<Self>) -> Arc<Self> {
180        self.clone()
181    }
182
183    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
184        enter_snapshot_scope(AnySnapshot::Mutable(self.clone()), f)
185    }
186
187    pub fn take_nested_snapshot(
188        self: &Arc<Self>,
189        read_observer: Option<ReadObserver>,
190    ) -> Arc<ReadonlySnapshot> {
191        self.validate_not_disposed();
192        self.validate_not_applied();
193
194        let merged_observer =
195            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
196
197        // Create a nested read-only snapshot
198        let nested = ReadonlySnapshot::new(
199            self.state.id.get(),
200            self.state.invalid.borrow().clone(),
201            merged_observer,
202        );
203
204        self.nested_count.set(self.nested_count.get() + 1);
205
206        // When the nested snapshot is disposed, decrement this parent's nested_count
207        let parent_weak = Arc::downgrade(self);
208        nested.set_on_dispose(move || {
209            if let Some(parent) = parent_weak.upgrade() {
210                let cur = parent.nested_count.get();
211                if cur > 0 {
212                    parent.nested_count.set(cur - 1);
213                }
214            }
215        });
216        nested
217    }
218
219    pub fn has_pending_changes(&self) -> bool {
220        !self.state.modified.borrow().is_empty()
221    }
222
223    pub fn pending_children(&self) -> Vec<SnapshotId> {
224        self.state.pending_children()
225    }
226
227    pub fn has_pending_children(&self) -> bool {
228        self.state.has_pending_children()
229    }
230
231    pub fn dispose(&self) {
232        if !self.state.disposed.get() && self.nested_count.get() == 0 {
233            self.state.dispose();
234        }
235    }
236
237    pub fn record_read(&self, state: &dyn StateObject) {
238        self.state.record_read(state);
239    }
240
241    pub fn record_write(&self, state: Arc<dyn StateObject>) {
242        self.validate_not_applied();
243        self.validate_not_disposed();
244        self.state.record_write(state, self.state.id.get());
245    }
246
247    pub fn close(&self) {
248        self.state.disposed.set(true);
249    }
250
251    pub fn is_disposed(&self) -> bool {
252        self.state.disposed.get()
253    }
254
255    pub fn apply(&self) -> SnapshotApplyResult {
256        // Check disposed state first - return Failure instead of panicking
257        if self.state.disposed.get() {
258            return SnapshotApplyResult::Failure;
259        }
260
261        if self.applied.get() {
262            return SnapshotApplyResult::Failure;
263        }
264
265        let modified = self.state.modified.borrow();
266        if modified.is_empty() {
267            // No changes to apply
268            self.applied.set(true);
269            self.state.dispose();
270            return SnapshotApplyResult::Success;
271        }
272
273        let this_id = self.state.id.get();
274        let mut modified_objects: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
275            Vec::with_capacity(modified.len());
276        for (&obj_id, (obj, writer_id)) in modified.iter() {
277            modified_objects.push((obj_id, obj.clone(), *writer_id));
278        }
279
280        drop(modified);
281
282        let parent_snapshot = GlobalSnapshot::get_or_create();
283        let parent_snapshot_id = parent_snapshot.snapshot_id();
284        let parent_invalid = parent_snapshot.invalid();
285        drop(parent_snapshot);
286
287        let next_invalid = super::runtime::open_snapshots().clear(parent_snapshot_id);
288        let this_invalid_for_optimistic = self.state.invalid.borrow().clone();
289        let optimistic = super::optimistic_merges(
290            parent_snapshot_id,
291            self.base_parent_id,
292            &modified_objects,
293            &next_invalid,
294            &this_invalid_for_optimistic,
295        );
296
297        let mut operations: Vec<ApplyOperation> = Vec::with_capacity(modified_objects.len());
298
299        for (obj_id, state, writer_id) in &modified_objects {
300            let head = state.first_record();
301            let applied = match find_record_by_id(&head, *writer_id) {
302                Some(record) => record,
303                None => return SnapshotApplyResult::Failure,
304            };
305
306            let Some(current) =
307                crate::state::readable_record_for(&head, parent_snapshot_id, &next_invalid)
308                    .or_else(|| state.try_readable_record(parent_snapshot_id, &parent_invalid))
309            else {
310                log::error!(
311                    "MutableSnapshot::apply missing parent readable record (object_id={:?}, parent_snapshot_id={})",
312                    obj_id,
313                    parent_snapshot_id
314                );
315                return SnapshotApplyResult::Failure;
316            };
317            // Use this snapshot's invalid set to find previous (matching Kotlin's
318            // `readable(first, snapshotId, applyingSnapshot.invalid)`)
319            let this_invalid = self.state.invalid.borrow();
320            let (previous_opt, found_base) =
321                find_previous_record(&head, self.base_parent_id, &this_invalid);
322            drop(this_invalid);
323            let Some(previous) = previous_opt else {
324                return SnapshotApplyResult::Failure;
325            };
326
327            if !found_base || previous.snapshot_id() == PREEXISTING_SNAPSHOT_ID {
328                operations.push(ApplyOperation::PromoteChild {
329                    object_id: *obj_id,
330                    state: state.clone(),
331                    writer_id: *writer_id,
332                });
333                continue;
334            }
335
336            if Rc::ptr_eq(&current, &previous) {
337                operations.push(ApplyOperation::PromoteChild {
338                    object_id: *obj_id,
339                    state: state.clone(),
340                    writer_id: *writer_id,
341                });
342                continue;
343            }
344
345            let merged = if let Some(candidate) = optimistic
346                .as_ref()
347                .and_then(|map| map.get(&(Rc::as_ptr(&current) as usize)))
348                .cloned()
349            {
350                candidate
351            } else {
352                match state.merge_records(
353                    Rc::clone(&previous),
354                    Rc::clone(&current),
355                    Rc::clone(&applied),
356                ) {
357                    Some(record) => record,
358                    None => return SnapshotApplyResult::Failure,
359                }
360            };
361
362            if Rc::ptr_eq(&merged, &applied) {
363                operations.push(ApplyOperation::PromoteChild {
364                    object_id: *obj_id,
365                    state: state.clone(),
366                    writer_id: *writer_id,
367                });
368            } else if Rc::ptr_eq(&merged, &current) {
369                operations.push(ApplyOperation::PromoteExisting {
370                    object_id: *obj_id,
371                    state: state.clone(),
372                    source_id: current.snapshot_id(),
373                    applied: applied.clone(),
374                });
375            } else {
376                operations.push(ApplyOperation::CommitMerged {
377                    object_id: *obj_id,
378                    state: state.clone(),
379                    merged: merged.clone(),
380                    applied: applied.clone(),
381                });
382            }
383        }
384
385        let mut applied_info: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
386            Vec::with_capacity(operations.len());
387
388        for operation in operations {
389            match operation {
390                ApplyOperation::PromoteChild {
391                    object_id,
392                    state,
393                    writer_id,
394                } => {
395                    if state.promote_record(writer_id).is_err() {
396                        return SnapshotApplyResult::Failure;
397                    }
398                    let new_head_id = state.first_record().snapshot_id();
399                    applied_info.push((object_id, state, new_head_id));
400                }
401                ApplyOperation::PromoteExisting {
402                    object_id,
403                    state,
404                    source_id,
405                    applied,
406                } => {
407                    if state.promote_record(source_id).is_err() {
408                        return SnapshotApplyResult::Failure;
409                    }
410                    applied.set_tombstone(true);
411                    applied.clear_value();
412                    let new_head_id = state.first_record().snapshot_id();
413                    applied_info.push((object_id, state, new_head_id));
414                }
415                ApplyOperation::CommitMerged {
416                    object_id,
417                    state,
418                    merged,
419                    applied,
420                } => {
421                    let Ok(new_head_id) = state.commit_merged_record(merged) else {
422                        return SnapshotApplyResult::Failure;
423                    };
424                    applied.set_tombstone(true);
425                    applied.clear_value();
426                    applied_info.push((object_id, state, new_head_id));
427                }
428            }
429        }
430
431        for (obj_id, _, head_id) in &applied_info {
432            super::set_last_write(*obj_id, *head_id);
433        }
434
435        self.applied.set(true);
436        self.state.dispose();
437
438        // Track modified states for future cleanup instead of cleaning immediately.
439        // This defers the O(record_chain) work to check_and_overwrite_unused_records_locked,
440        // which runs periodically (e.g., on global snapshot advance) rather than on every apply.
441        // This prevents performance regression during rapid scrolling while still preventing leaks.
442        for (_, state, _) in &applied_info {
443            super::EXTRA_STATE_OBJECTS.with(|cell| {
444                cell.borrow_mut().add_trait_object(state);
445            });
446        }
447
448        let observer_states: Vec<Arc<dyn StateObject>> = applied_info
449            .iter()
450            .map(|(_, state, _)| state.clone())
451            .collect();
452        super::notify_apply_observers(&observer_states, this_id);
453        SnapshotApplyResult::Success
454    }
455
456    pub fn take_nested_mutable_snapshot(
457        self: &Arc<Self>,
458        read_observer: Option<ReadObserver>,
459        write_observer: Option<WriteObserver>,
460    ) -> Arc<NestedMutableSnapshot> {
461        self.validate_not_disposed();
462        self.validate_not_applied();
463
464        let merged_read =
465            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
466        let merged_write =
467            merge_write_observers(write_observer, self.state.write_observer.borrow().clone());
468
469        // Get parent's current state BEFORE allocating child
470        let parent_id = self.state.id.get();
471        let current_invalid = self.state.invalid.borrow().clone();
472
473        // Allocate the new child snapshot ID
474        let (new_id, _runtime_invalid) = allocate_snapshot();
475
476        // Update parent's invalid to include the child
477        let parent_invalid_with_child = current_invalid.set(new_id);
478        self.state.invalid.replace(parent_invalid_with_child);
479
480        // Child's invalid = parent's invalid + range(parent_id + 1, new_id)
481        // This does NOT include parent_id, so child can read parent's records
482        // (matching Kotlin's currentInvalid.addRange(snapshotId + 1, newId))
483        let invalid = current_invalid.add_range(parent_id + 1, new_id);
484
485        let self_weak = Arc::downgrade(self);
486        let nested = NestedMutableSnapshot::new(
487            new_id,
488            invalid,
489            merged_read,
490            merged_write,
491            self_weak,
492            self.state.id.get(), // base_parent_id for child is this snapshot's id
493        );
494
495        self.nested_count.set(self.nested_count.get() + 1);
496        self.state.add_pending_child(new_id);
497
498        let parent_weak = Arc::downgrade(self);
499        nested.set_on_dispose({
500            let child_id = new_id;
501            move || {
502                if let Some(parent) = parent_weak.upgrade() {
503                    if parent.nested_count.get() > 0 {
504                        parent
505                            .nested_count
506                            .set(parent.nested_count.get().saturating_sub(1));
507                    }
508                    let mut invalid = parent.state.invalid.borrow_mut();
509                    let new_set = invalid.clone().clear(child_id);
510                    *invalid = new_set;
511                    parent.state.remove_pending_child(child_id);
512                }
513            }
514        });
515
516        nested
517    }
518
519    /// Merge a child's modified set into this snapshot's modified set.
520    ///
521    /// Returns Ok(()) on success, or Err(()) if a conflict is detected
522    /// (i.e., this snapshot already has a modification for the same object).
523    pub(crate) fn merge_child_modifications(
524        &self,
525        child_modified: &HashMap<StateObjectId, (Arc<dyn StateObject>, SnapshotId)>,
526    ) -> Result<(), ()> {
527        // Check for conflicts
528        {
529            let parent_mod = self.state.modified.borrow();
530            for key in child_modified.keys() {
531                if parent_mod.contains_key(key) {
532                    return Err(());
533                }
534            }
535        }
536
537        // Merge entries
538        let mut parent_mod = self.state.modified.borrow_mut();
539        for (key, value) in child_modified.iter() {
540            parent_mod.entry(*key).or_insert_with(|| value.clone());
541        }
542        Ok(())
543    }
544}
545
546#[cfg(test)]
547impl MutableSnapshot {
548    pub(crate) fn debug_modified_objects(
549        &self,
550    ) -> Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> {
551        let modified = self.state.modified.borrow();
552        modified
553            .iter()
554            .map(|(&obj_id, (state, writer_id))| (obj_id, state.clone(), *writer_id))
555            .collect()
556    }
557
558    pub(crate) fn debug_base_parent_id(&self) -> SnapshotId {
559        self.base_parent_id
560    }
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566    use crate::snapshot_v2::runtime::TestRuntimeGuard;
567    use crate::state::{NeverEqual, SnapshotMutableState, StateObject};
568    use std::sync::Arc;
569
570    fn reset_runtime() -> TestRuntimeGuard {
571        reset_runtime_for_tests()
572    }
573
574    fn new_state(initial: i32) -> Arc<SnapshotMutableState<i32>> {
575        SnapshotMutableState::new_in_arc(initial, Arc::new(NeverEqual))
576    }
577
578    // Mock StateObject for testing
579    struct MockStateObject;
580
581    fn mock_state_record() -> Rc<crate::state::StateRecord> {
582        crate::state::StateRecord::new(crate::state::PREEXISTING_SNAPSHOT_ID, (), None)
583    }
584
585    impl StateObject for MockStateObject {
586        fn object_id(&self) -> crate::state::ObjectId {
587            crate::state::ObjectId(0)
588        }
589
590        fn first_record(&self) -> Rc<crate::state::StateRecord> {
591            mock_state_record()
592        }
593
594        fn try_readable_record(
595            &self,
596            snapshot_id: crate::snapshot_id_set::SnapshotId,
597            invalid: &SnapshotIdSet,
598        ) -> Option<Rc<crate::state::StateRecord>> {
599            Some(self.readable_record(snapshot_id, invalid))
600        }
601
602        fn readable_record(
603            &self,
604            _snapshot_id: crate::snapshot_id_set::SnapshotId,
605            _invalid: &SnapshotIdSet,
606        ) -> Rc<crate::state::StateRecord> {
607            mock_state_record()
608        }
609
610        fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
611
612        fn promote_record(
613            &self,
614            _child_id: crate::snapshot_id_set::SnapshotId,
615        ) -> Result<(), &'static str> {
616            Ok(())
617        }
618
619        fn as_any(&self) -> &dyn std::any::Any {
620            self
621        }
622    }
623
624    struct MissingParentReadableStateObject {
625        record: Rc<crate::state::StateRecord>,
626    }
627
628    impl MissingParentReadableStateObject {
629        fn new(writer_id: SnapshotId) -> Self {
630            Self {
631                record: crate::state::StateRecord::new(writer_id, (), None),
632            }
633        }
634    }
635
636    impl StateObject for MissingParentReadableStateObject {
637        fn object_id(&self) -> crate::state::ObjectId {
638            crate::state::ObjectId(10_001)
639        }
640
641        fn first_record(&self) -> Rc<crate::state::StateRecord> {
642            Rc::clone(&self.record)
643        }
644
645        fn try_readable_record(
646            &self,
647            _: SnapshotId,
648            _: &SnapshotIdSet,
649        ) -> Option<Rc<crate::state::StateRecord>> {
650            None
651        }
652
653        fn readable_record(
654            &self,
655            _: SnapshotId,
656            _: &SnapshotIdSet,
657        ) -> Rc<crate::state::StateRecord> {
658            panic!("apply must use a fallible parent readable-record lookup")
659        }
660
661        fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
662
663        fn promote_record(
664            &self,
665            _child_id: crate::snapshot_id_set::SnapshotId,
666        ) -> Result<(), &'static str> {
667            Ok(())
668        }
669
670        fn as_any(&self) -> &dyn std::any::Any {
671            self
672        }
673    }
674
675    #[test]
676    fn test_mutable_snapshot_creation() {
677        let _guard = reset_runtime();
678        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
679        assert_eq!(snapshot.snapshot_id(), 1);
680        assert!(!snapshot.read_only());
681        assert!(!snapshot.is_disposed());
682        assert!(!snapshot.applied.get());
683    }
684
685    #[test]
686    fn test_mutable_snapshot_no_pending_changes_initially() {
687        let _guard = reset_runtime();
688        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
689        assert!(!snapshot.has_pending_changes());
690    }
691
692    #[test]
693    fn test_mutable_snapshot_enter() {
694        let _guard = reset_runtime();
695        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
696
697        set_current_snapshot(None);
698        assert!(current_snapshot().is_none());
699
700        snapshot.enter(|| {
701            let current = current_snapshot();
702            assert!(current.is_some());
703            assert_eq!(current.unwrap().snapshot_id(), 1);
704        });
705
706        assert!(current_snapshot().is_none());
707    }
708
709    #[test]
710    fn test_mutable_snapshot_read_observer() {
711        let _guard = reset_runtime();
712        use std::sync::{Arc as StdArc, Mutex};
713
714        let read_count = StdArc::new(Mutex::new(0));
715        let read_count_clone = read_count.clone();
716
717        let observer = Arc::new(move |_: &dyn StateObject| {
718            *read_count_clone.lock().unwrap() += 1;
719        });
720
721        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), Some(observer), None, 0);
722        let mock_state = MockStateObject;
723
724        snapshot.record_read(&mock_state);
725        snapshot.record_read(&mock_state);
726
727        assert_eq!(*read_count.lock().unwrap(), 2);
728    }
729
730    #[test]
731    fn test_mutable_snapshot_write_observer() {
732        let _guard = reset_runtime();
733        use std::sync::{Arc as StdArc, Mutex};
734
735        let write_count = StdArc::new(Mutex::new(0));
736        let write_count_clone = write_count.clone();
737
738        let observer = Arc::new(move |_: &dyn StateObject| {
739            *write_count_clone.lock().unwrap() += 1;
740        });
741
742        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, Some(observer), 0);
743        let mock_state = Arc::new(MockStateObject);
744
745        snapshot.record_write(mock_state.clone());
746        snapshot.record_write(mock_state.clone()); // Second write should not call observer
747
748        assert_eq!(*write_count.lock().unwrap(), 1);
749    }
750
751    #[test]
752    fn test_mutable_snapshot_apply_empty() {
753        let _guard = reset_runtime();
754        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
755        let result = snapshot.apply();
756        assert!(result.is_success());
757        assert!(snapshot.applied.get());
758    }
759
760    #[test]
761    fn mutable_apply_returns_failure_when_parent_readable_record_is_missing() {
762        let _guard = reset_runtime();
763        let snapshot = MutableSnapshot::new(7, SnapshotIdSet::new(), None, None, 1);
764        let state = Arc::new(MissingParentReadableStateObject::new(
765            snapshot.snapshot_id(),
766        ));
767
768        snapshot.record_write(state);
769
770        let result = snapshot.apply();
771
772        assert!(result.is_failure());
773    }
774
775    #[test]
776    fn test_mutable_snapshot_apply_twice_fails() {
777        let _guard = reset_runtime();
778        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
779        snapshot.apply().check();
780
781        let result = snapshot.apply();
782        assert!(result.is_failure());
783    }
784
785    #[test]
786    fn test_mutable_snapshot_nested_readonly() {
787        let _guard = reset_runtime();
788        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
789        let nested = parent.take_nested_snapshot(None);
790
791        assert_eq!(nested.snapshot_id(), 1);
792        assert!(nested.read_only());
793        assert_eq!(parent.nested_count.get(), 1);
794    }
795
796    #[test]
797    fn test_mutable_snapshot_nested_mutable() {
798        let _guard = reset_runtime();
799        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
800        let nested = parent.take_nested_mutable_snapshot(None, None);
801
802        assert!(nested.snapshot_id() > parent.snapshot_id());
803        assert!(!nested.read_only());
804        assert_eq!(parent.nested_count.get(), 1);
805    }
806
807    #[test]
808    fn test_mutable_snapshot_nested_mutable_dispose_clears_invalid() {
809        let _guard = reset_runtime();
810        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
811        let nested = parent.take_nested_mutable_snapshot(None, None);
812
813        let child_id = nested.snapshot_id();
814        assert!(parent.state.invalid.borrow().get(child_id));
815
816        nested.dispose();
817
818        assert_eq!(parent.nested_count.get(), 0);
819        assert!(!parent.state.invalid.borrow().get(child_id));
820    }
821
822    #[test]
823    fn test_mutable_snapshot_nested_dispose() {
824        let _guard = reset_runtime();
825        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
826        let nested = parent.take_nested_snapshot(None);
827
828        assert_eq!(parent.nested_count.get(), 1);
829
830        nested.dispose();
831        assert_eq!(parent.nested_count.get(), 0);
832    }
833
834    #[test]
835    #[should_panic(expected = "Snapshot has already been applied")]
836    fn test_mutable_snapshot_write_after_apply_panics() {
837        let _guard = reset_runtime();
838        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
839        snapshot.apply().check();
840
841        let mock_state = Arc::new(MockStateObject);
842        snapshot.record_write(mock_state);
843    }
844
845    #[test]
846    #[should_panic(expected = "Snapshot has been disposed")]
847    fn test_mutable_snapshot_write_after_dispose_panics() {
848        let _guard = reset_runtime();
849        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
850        snapshot.dispose();
851
852        let mock_state = Arc::new(MockStateObject);
853        snapshot.record_write(mock_state);
854    }
855
856    #[test]
857    fn test_mutable_snapshot_dispose() {
858        let _guard = reset_runtime();
859        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
860        assert!(!snapshot.is_disposed());
861
862        snapshot.dispose();
863        assert!(snapshot.is_disposed());
864    }
865
866    #[test]
867    fn test_mutable_snapshot_apply_observer() {
868        let _guard = reset_runtime();
869        use std::sync::{Arc as StdArc, Mutex};
870
871        let applied_count = StdArc::new(Mutex::new(0));
872        let applied_count_clone = applied_count.clone();
873
874        let observer = Rc::new(
875            move |_modified: &[Arc<dyn StateObject>], _snapshot_id: SnapshotId| {
876                *applied_count_clone.lock().unwrap() += 1;
877            },
878        );
879
880        let _handle = register_apply_observer(observer);
881
882        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
883        let state = new_state(0);
884
885        snapshot.enter(|| state.set(10));
886        snapshot.apply().check();
887
888        assert_eq!(*applied_count.lock().unwrap(), 1);
889    }
890
891    #[test]
892    fn test_mutable_conflict_detection_same_object() {
893        let _guard = reset_runtime();
894        let global = GlobalSnapshot::get_or_create();
895        let state = new_state(0);
896
897        let s1 = global.take_nested_mutable_snapshot(None, None);
898        s1.enter(|| state.set(1));
899
900        let s2 = global.take_nested_mutable_snapshot(None, None);
901        s2.enter(|| state.set(2));
902
903        assert!(s1.apply().is_success());
904        assert!(s2.apply().is_failure());
905    }
906
907    #[test]
908    fn test_mutable_no_conflict_different_objects() {
909        let _guard = reset_runtime();
910        let global = GlobalSnapshot::get_or_create();
911        let state1 = new_state(0);
912        let state2 = new_state(0);
913
914        let s1 = global.take_nested_mutable_snapshot(None, None);
915        s1.enter(|| state1.set(10));
916
917        let s2 = global.take_nested_mutable_snapshot(None, None);
918        s2.enter(|| state2.set(20));
919
920        assert!(s1.apply().is_success());
921        assert!(s2.apply().is_success());
922    }
923}