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        let previous = current_snapshot();
185        set_current_snapshot(Some(AnySnapshot::Mutable(self.clone())));
186        let result = f();
187        set_current_snapshot(previous);
188        result
189    }
190
191    pub fn take_nested_snapshot(
192        self: &Arc<Self>,
193        read_observer: Option<ReadObserver>,
194    ) -> Arc<ReadonlySnapshot> {
195        self.validate_not_disposed();
196        self.validate_not_applied();
197
198        let merged_observer = merge_read_observers(read_observer, self.state.read_observer.clone());
199
200        // Create a nested read-only snapshot
201        let nested = ReadonlySnapshot::new(
202            self.state.id.get(),
203            self.state.invalid.borrow().clone(),
204            merged_observer,
205        );
206
207        self.nested_count.set(self.nested_count.get() + 1);
208
209        // When the nested snapshot is disposed, decrement this parent's nested_count
210        let parent_weak = Arc::downgrade(self);
211        nested.set_on_dispose(move || {
212            if let Some(parent) = parent_weak.upgrade() {
213                let cur = parent.nested_count.get();
214                if cur > 0 {
215                    parent.nested_count.set(cur - 1);
216                }
217            }
218        });
219        nested
220    }
221
222    pub fn has_pending_changes(&self) -> bool {
223        !self.state.modified.borrow().is_empty()
224    }
225
226    pub fn pending_children(&self) -> Vec<SnapshotId> {
227        self.state.pending_children()
228    }
229
230    pub fn has_pending_children(&self) -> bool {
231        self.state.has_pending_children()
232    }
233
234    pub fn dispose(&self) {
235        if !self.state.disposed.get() && self.nested_count.get() == 0 {
236            self.state.dispose();
237        }
238    }
239
240    pub fn record_read(&self, state: &dyn StateObject) {
241        self.state.record_read(state);
242    }
243
244    pub fn record_write(&self, state: Arc<dyn StateObject>) {
245        self.validate_not_applied();
246        self.validate_not_disposed();
247        self.state.record_write(state, self.state.id.get());
248    }
249
250    pub fn notify_objects_initialized(&self) {
251        if !self.applied.get() && !self.state.disposed.get() {
252            // Mark that objects are initialized
253            // In a full implementation, this would update internal state
254        }
255    }
256
257    pub fn close(&self) {
258        self.state.disposed.set(true);
259    }
260
261    pub fn is_disposed(&self) -> bool {
262        self.state.disposed.get()
263    }
264
265    pub fn apply(&self) -> SnapshotApplyResult {
266        // Check disposed state first - return Failure instead of panicking
267        if self.state.disposed.get() {
268            return SnapshotApplyResult::Failure;
269        }
270
271        if self.applied.get() {
272            return SnapshotApplyResult::Failure;
273        }
274
275        let modified = self.state.modified.borrow();
276        if modified.is_empty() {
277            // No changes to apply
278            self.applied.set(true);
279            self.state.dispose();
280            return SnapshotApplyResult::Success;
281        }
282
283        let this_id = self.state.id.get();
284        let mut modified_objects: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
285            Vec::with_capacity(modified.len());
286        for (&obj_id, (obj, writer_id)) in modified.iter() {
287            modified_objects.push((obj_id, obj.clone(), *writer_id));
288        }
289
290        drop(modified);
291
292        let parent_snapshot = GlobalSnapshot::get_or_create();
293        let parent_snapshot_id = parent_snapshot.snapshot_id();
294        let parent_invalid = parent_snapshot.invalid();
295        drop(parent_snapshot);
296
297        let next_invalid = super::runtime::open_snapshots().clear(parent_snapshot_id);
298        let this_invalid_for_optimistic = self.state.invalid.borrow().clone();
299        let optimistic = super::optimistic_merges(
300            parent_snapshot_id,
301            self.base_parent_id,
302            &modified_objects,
303            &next_invalid,
304            &this_invalid_for_optimistic,
305        );
306
307        let mut operations: Vec<ApplyOperation> = Vec::with_capacity(modified_objects.len());
308
309        for (obj_id, state, writer_id) in &modified_objects {
310            let head = state.first_record();
311            let applied = match find_record_by_id(&head, *writer_id) {
312                Some(record) => record,
313                None => return SnapshotApplyResult::Failure,
314            };
315
316            let current =
317                crate::state::readable_record_for(&head, parent_snapshot_id, &next_invalid)
318                    .unwrap_or_else(|| state.readable_record(parent_snapshot_id, &parent_invalid));
319            // Use this snapshot's invalid set to find previous (matching Kotlin's
320            // `readable(first, snapshotId, applyingSnapshot.invalid)`)
321            let this_invalid = self.state.invalid.borrow();
322            let (previous_opt, found_base) =
323                find_previous_record(&head, self.base_parent_id, &this_invalid);
324            drop(this_invalid);
325            let Some(previous) = previous_opt else {
326                return SnapshotApplyResult::Failure;
327            };
328
329            if !found_base || previous.snapshot_id() == PREEXISTING_SNAPSHOT_ID {
330                operations.push(ApplyOperation::PromoteChild {
331                    object_id: *obj_id,
332                    state: state.clone(),
333                    writer_id: *writer_id,
334                });
335                continue;
336            }
337
338            if Rc::ptr_eq(&current, &previous) {
339                operations.push(ApplyOperation::PromoteChild {
340                    object_id: *obj_id,
341                    state: state.clone(),
342                    writer_id: *writer_id,
343                });
344                continue;
345            }
346
347            let merged = if let Some(candidate) = optimistic
348                .as_ref()
349                .and_then(|map| map.get(&(Rc::as_ptr(&current) as usize)))
350                .cloned()
351            {
352                candidate
353            } else {
354                match state.merge_records(
355                    Rc::clone(&previous),
356                    Rc::clone(&current),
357                    Rc::clone(&applied),
358                ) {
359                    Some(record) => record,
360                    None => return SnapshotApplyResult::Failure,
361                }
362            };
363
364            if Rc::ptr_eq(&merged, &applied) {
365                operations.push(ApplyOperation::PromoteChild {
366                    object_id: *obj_id,
367                    state: state.clone(),
368                    writer_id: *writer_id,
369                });
370            } else if Rc::ptr_eq(&merged, &current) {
371                operations.push(ApplyOperation::PromoteExisting {
372                    object_id: *obj_id,
373                    state: state.clone(),
374                    source_id: current.snapshot_id(),
375                    applied: applied.clone(),
376                });
377            } else {
378                operations.push(ApplyOperation::CommitMerged {
379                    object_id: *obj_id,
380                    state: state.clone(),
381                    merged: merged.clone(),
382                    applied: applied.clone(),
383                });
384            }
385        }
386
387        let mut applied_info: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
388            Vec::with_capacity(operations.len());
389
390        for operation in operations {
391            match operation {
392                ApplyOperation::PromoteChild {
393                    object_id,
394                    state,
395                    writer_id,
396                } => {
397                    if state.promote_record(writer_id).is_err() {
398                        return SnapshotApplyResult::Failure;
399                    }
400                    let new_head_id = state.first_record().snapshot_id();
401                    applied_info.push((object_id, state, new_head_id));
402                }
403                ApplyOperation::PromoteExisting {
404                    object_id,
405                    state,
406                    source_id,
407                    applied,
408                } => {
409                    if state.promote_record(source_id).is_err() {
410                        return SnapshotApplyResult::Failure;
411                    }
412                    applied.set_tombstone(true);
413                    applied.clear_value();
414                    let new_head_id = state.first_record().snapshot_id();
415                    applied_info.push((object_id, state, new_head_id));
416                }
417                ApplyOperation::CommitMerged {
418                    object_id,
419                    state,
420                    merged,
421                    applied,
422                } => {
423                    let Ok(new_head_id) = state.commit_merged_record(merged) else {
424                        return SnapshotApplyResult::Failure;
425                    };
426                    applied.set_tombstone(true);
427                    applied.clear_value();
428                    applied_info.push((object_id, state, new_head_id));
429                }
430            }
431        }
432
433        for (obj_id, _, head_id) in &applied_info {
434            super::set_last_write(*obj_id, *head_id);
435        }
436
437        self.applied.set(true);
438        self.state.dispose();
439
440        // Track modified states for future cleanup instead of cleaning immediately.
441        // This defers the O(record_chain) work to check_and_overwrite_unused_records_locked,
442        // which runs periodically (e.g., on global snapshot advance) rather than on every apply.
443        // This prevents performance regression during rapid scrolling while still preventing leaks.
444        for (_, state, _) in &applied_info {
445            super::EXTRA_STATE_OBJECTS.with(|cell| {
446                cell.borrow_mut().add_trait_object(state);
447            });
448        }
449
450        let observer_states: Vec<Arc<dyn StateObject>> = applied_info
451            .iter()
452            .map(|(_, state, _)| state.clone())
453            .collect();
454        super::notify_apply_observers(&observer_states, this_id);
455        SnapshotApplyResult::Success
456    }
457
458    pub fn take_nested_mutable_snapshot(
459        self: &Arc<Self>,
460        read_observer: Option<ReadObserver>,
461        write_observer: Option<WriteObserver>,
462    ) -> Arc<NestedMutableSnapshot> {
463        self.validate_not_disposed();
464        self.validate_not_applied();
465
466        let merged_read = merge_read_observers(read_observer, self.state.read_observer.clone());
467        let merged_write = merge_write_observers(write_observer, self.state.write_observer.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::cell::Cell;
569    use std::sync::Arc;
570
571    fn reset_runtime() -> TestRuntimeGuard {
572        reset_runtime_for_tests()
573    }
574
575    fn new_state(initial: i32) -> Arc<SnapshotMutableState<i32>> {
576        SnapshotMutableState::new_in_arc(initial, Arc::new(NeverEqual))
577    }
578
579    // Mock StateObject for testing
580    #[allow(dead_code)]
581    struct MockStateObject {
582        value: Cell<i32>,
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            unimplemented!("Not needed for tests")
592        }
593
594        fn readable_record(
595            &self,
596            _snapshot_id: crate::snapshot_id_set::SnapshotId,
597            _invalid: &SnapshotIdSet,
598        ) -> Rc<crate::state::StateRecord> {
599            unimplemented!("Not needed for tests")
600        }
601
602        fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {
603            unimplemented!("Not needed for tests")
604        }
605
606        fn promote_record(
607            &self,
608            _child_id: crate::snapshot_id_set::SnapshotId,
609        ) -> Result<(), &'static str> {
610            unimplemented!("Not needed for tests")
611        }
612
613        fn as_any(&self) -> &dyn std::any::Any {
614            self
615        }
616    }
617
618    #[test]
619    fn test_mutable_snapshot_creation() {
620        let _guard = reset_runtime();
621        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
622        assert_eq!(snapshot.snapshot_id(), 1);
623        assert!(!snapshot.read_only());
624        assert!(!snapshot.is_disposed());
625        assert!(!snapshot.applied.get());
626    }
627
628    #[test]
629    fn test_mutable_snapshot_no_pending_changes_initially() {
630        let _guard = reset_runtime();
631        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
632        assert!(!snapshot.has_pending_changes());
633    }
634
635    #[test]
636    fn test_mutable_snapshot_enter() {
637        let _guard = reset_runtime();
638        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
639
640        set_current_snapshot(None);
641        assert!(current_snapshot().is_none());
642
643        snapshot.enter(|| {
644            let current = current_snapshot();
645            assert!(current.is_some());
646            assert_eq!(current.unwrap().snapshot_id(), 1);
647        });
648
649        assert!(current_snapshot().is_none());
650    }
651
652    #[test]
653    fn test_mutable_snapshot_read_observer() {
654        let _guard = reset_runtime();
655        use std::sync::{Arc as StdArc, Mutex};
656
657        let read_count = StdArc::new(Mutex::new(0));
658        let read_count_clone = read_count.clone();
659
660        let observer = Arc::new(move |_: &dyn StateObject| {
661            *read_count_clone.lock().unwrap() += 1;
662        });
663
664        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), Some(observer), None, 0);
665        let mock_state = MockStateObject {
666            value: Cell::new(42),
667        };
668
669        snapshot.record_read(&mock_state);
670        snapshot.record_read(&mock_state);
671
672        assert_eq!(*read_count.lock().unwrap(), 2);
673    }
674
675    #[test]
676    fn test_mutable_snapshot_write_observer() {
677        let _guard = reset_runtime();
678        use std::sync::{Arc as StdArc, Mutex};
679
680        let write_count = StdArc::new(Mutex::new(0));
681        let write_count_clone = write_count.clone();
682
683        let observer = Arc::new(move |_: &dyn StateObject| {
684            *write_count_clone.lock().unwrap() += 1;
685        });
686
687        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, Some(observer), 0);
688        let mock_state = Arc::new(MockStateObject {
689            value: Cell::new(42),
690        });
691
692        snapshot.record_write(mock_state.clone());
693        snapshot.record_write(mock_state.clone()); // Second write should not call observer
694
695        // Note: Current implementation calls observer on every write
696        // In full implementation, it would only call on first write
697        assert!(*write_count.lock().unwrap() >= 1);
698    }
699
700    #[test]
701    fn test_mutable_snapshot_apply_empty() {
702        let _guard = reset_runtime();
703        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
704        let result = snapshot.apply();
705        assert!(result.is_success());
706        assert!(snapshot.applied.get());
707    }
708
709    #[test]
710    fn test_mutable_snapshot_apply_twice_fails() {
711        let _guard = reset_runtime();
712        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
713        snapshot.apply().check();
714
715        let result = snapshot.apply();
716        assert!(result.is_failure());
717    }
718
719    #[test]
720    fn test_mutable_snapshot_nested_readonly() {
721        let _guard = reset_runtime();
722        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
723        let nested = parent.take_nested_snapshot(None);
724
725        assert_eq!(nested.snapshot_id(), 1);
726        assert!(nested.read_only());
727        assert_eq!(parent.nested_count.get(), 1);
728    }
729
730    #[test]
731    fn test_mutable_snapshot_nested_mutable() {
732        let _guard = reset_runtime();
733        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
734        let nested = parent.take_nested_mutable_snapshot(None, None);
735
736        assert!(nested.snapshot_id() > parent.snapshot_id());
737        assert!(!nested.read_only());
738        assert_eq!(parent.nested_count.get(), 1);
739    }
740
741    #[test]
742    fn test_mutable_snapshot_nested_mutable_dispose_clears_invalid() {
743        let _guard = reset_runtime();
744        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
745        let nested = parent.take_nested_mutable_snapshot(None, None);
746
747        let child_id = nested.snapshot_id();
748        assert!(parent.state.invalid.borrow().get(child_id));
749
750        nested.dispose();
751
752        assert_eq!(parent.nested_count.get(), 0);
753        assert!(!parent.state.invalid.borrow().get(child_id));
754    }
755
756    #[test]
757    fn test_mutable_snapshot_nested_dispose() {
758        let _guard = reset_runtime();
759        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
760        let nested = parent.take_nested_snapshot(None);
761
762        assert_eq!(parent.nested_count.get(), 1);
763
764        nested.dispose();
765        assert_eq!(parent.nested_count.get(), 0);
766    }
767
768    #[test]
769    #[should_panic(expected = "Snapshot has already been applied")]
770    fn test_mutable_snapshot_write_after_apply_panics() {
771        let _guard = reset_runtime();
772        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
773        snapshot.apply().check();
774
775        let mock_state = Arc::new(MockStateObject {
776            value: Cell::new(42),
777        });
778        snapshot.record_write(mock_state);
779    }
780
781    #[test]
782    #[should_panic(expected = "Snapshot has been disposed")]
783    fn test_mutable_snapshot_write_after_dispose_panics() {
784        let _guard = reset_runtime();
785        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
786        snapshot.dispose();
787
788        let mock_state = Arc::new(MockStateObject {
789            value: Cell::new(42),
790        });
791        snapshot.record_write(mock_state);
792    }
793
794    #[test]
795    fn test_mutable_snapshot_dispose() {
796        let _guard = reset_runtime();
797        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
798        assert!(!snapshot.is_disposed());
799
800        snapshot.dispose();
801        assert!(snapshot.is_disposed());
802    }
803
804    #[test]
805    fn test_mutable_snapshot_apply_observer() {
806        let _guard = reset_runtime();
807        use std::sync::{Arc as StdArc, Mutex};
808
809        let applied_count = StdArc::new(Mutex::new(0));
810        let applied_count_clone = applied_count.clone();
811
812        let observer = Arc::new(
813            move |_modified: &[Arc<dyn StateObject>], _snapshot_id: SnapshotId| {
814                *applied_count_clone.lock().unwrap() += 1;
815            },
816        );
817
818        let _handle = register_apply_observer(observer);
819
820        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
821        let state = new_state(0);
822
823        snapshot.enter(|| state.set(10));
824        snapshot.apply().check();
825
826        assert_eq!(*applied_count.lock().unwrap(), 1);
827    }
828
829    #[test]
830    fn test_mutable_conflict_detection_same_object() {
831        let _guard = reset_runtime();
832        let global = GlobalSnapshot::get_or_create();
833        let state = new_state(0);
834
835        let s1 = global.take_nested_mutable_snapshot(None, None);
836        s1.enter(|| state.set(1));
837
838        let s2 = global.take_nested_mutable_snapshot(None, None);
839        s2.enter(|| state.set(2));
840
841        assert!(s1.apply().is_success());
842        assert!(s2.apply().is_failure());
843    }
844
845    #[test]
846    fn test_mutable_no_conflict_different_objects() {
847        let _guard = reset_runtime();
848        let global = GlobalSnapshot::get_or_create();
849        let state1 = new_state(0);
850        let state2 = new_state(0);
851
852        let s1 = global.take_nested_mutable_snapshot(None, None);
853        s1.enter(|| state1.set(10));
854
855        let s2 = global.take_nested_mutable_snapshot(None, None);
856        s2.enter(|| state2.set(20));
857
858        assert!(s1.apply().is_success());
859        assert!(s2.apply().is_success());
860    }
861}