1use std::{
24 cell::{Cell, RefCell},
25 hash::{Hash, Hasher},
26 rc::{Rc, Weak},
27};
28
29use crate::{
30 collections::map::{HashMap, HashSet},
31 snapshot_id_set::{SnapshotId, SnapshotIdSet},
32 snapshot_pinning::{self, PinHandle},
33 snapshot_weak_set::SnapshotWeakSetDebugStats,
34 state::{StateObject, StateRecord},
35};
36
37mod global;
38mod mutable;
39mod nested;
40mod readonly;
41mod runtime;
42mod transparent;
43
44#[cfg(test)]
45#[path = "tests/integration_tests.rs"]
46mod integration_tests;
47
48pub use global::{GlobalSnapshot, advance_global_snapshot};
49pub use mutable::MutableSnapshot;
50pub use nested::{NestedMutableSnapshot, NestedReadonlySnapshot};
51pub use readonly::ReadonlySnapshot;
52pub(crate) use runtime::{SnapshotRuntime, allocate_snapshot, close_snapshot, with_runtime};
53#[cfg(test)]
54pub(crate) use runtime::{TestRuntimeGuard, reset_runtime_for_tests};
55pub use transparent::{TransparentObserverMutableSnapshot, TransparentObserverSnapshot};
56
57pub type ReadObserver = Rc<dyn Fn(&dyn StateObject) + 'static>;
59
60pub type WriteObserver = Rc<dyn Fn(&dyn StateObject) + 'static>;
62
63pub type ApplyObserver = Rc<dyn Fn(&[Rc<dyn StateObject>], SnapshotId) + 'static>;
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum SnapshotApplyResult {
69 Success,
71 Failure,
73}
74
75impl SnapshotApplyResult {
76 pub fn is_success(&self) -> bool {
78 matches!(self, SnapshotApplyResult::Success)
79 }
80
81 pub fn is_failure(&self) -> bool {
83 matches!(self, SnapshotApplyResult::Failure)
84 }
85
86 #[track_caller]
88 pub fn check(&self) {
89 assert!(!self.is_failure(), "Snapshot apply failed");
90 }
91}
92
93pub type StateObjectId = usize;
95
96#[derive(Clone)]
101pub enum AnySnapshot {
102 Readonly(Rc<ReadonlySnapshot>),
103 Mutable(Rc<MutableSnapshot>),
104 NestedReadonly(Rc<NestedReadonlySnapshot>),
105 NestedMutable(Rc<NestedMutableSnapshot>),
106 Global(Rc<GlobalSnapshot>),
107 TransparentMutable(Rc<TransparentObserverMutableSnapshot>),
108 TransparentReadonly(Rc<TransparentObserverSnapshot>),
109}
110
111#[derive(Clone)]
118pub enum AnyMutableSnapshot {
119 Root(Rc<MutableSnapshot>),
120 Nested(Rc<NestedMutableSnapshot>),
121}
122
123impl AnyMutableSnapshot {
124 pub fn snapshot_id(&self) -> SnapshotId {
126 match self {
127 AnyMutableSnapshot::Root(s) => s.snapshot_id(),
128 AnyMutableSnapshot::Nested(s) => s.snapshot_id(),
129 }
130 }
131
132 pub fn invalid(&self) -> SnapshotIdSet {
134 match self {
135 AnyMutableSnapshot::Root(s) => s.invalid(),
136 AnyMutableSnapshot::Nested(s) => s.invalid(),
137 }
138 }
139
140 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
142 match self {
143 AnyMutableSnapshot::Root(s) => s.enter(f),
144 AnyMutableSnapshot::Nested(s) => s.enter(f),
145 }
146 }
147
148 pub fn apply(&self) -> SnapshotApplyResult {
150 match self {
151 AnyMutableSnapshot::Root(s) => s.apply(),
152 AnyMutableSnapshot::Nested(s) => s.apply(),
153 }
154 }
155
156 pub fn dispose(&self) {
158 match self {
159 AnyMutableSnapshot::Root(s) => s.dispose(),
160 AnyMutableSnapshot::Nested(s) => s.dispose(),
161 }
162 }
163}
164
165impl AnySnapshot {
166 pub fn snapshot_id(&self) -> SnapshotId {
168 match self {
169 AnySnapshot::Readonly(s) => s.snapshot_id(),
170 AnySnapshot::Mutable(s) => s.snapshot_id(),
171 AnySnapshot::NestedReadonly(s) => s.snapshot_id(),
172 AnySnapshot::NestedMutable(s) => s.snapshot_id(),
173 AnySnapshot::Global(s) => s.snapshot_id(),
174 AnySnapshot::TransparentMutable(s) => s.snapshot_id(),
175 AnySnapshot::TransparentReadonly(s) => s.snapshot_id(),
176 }
177 }
178
179 fn state(&self) -> &SnapshotState {
181 match self {
182 AnySnapshot::Readonly(s) => &s.state,
183 AnySnapshot::Mutable(s) => &s.state,
184 AnySnapshot::NestedReadonly(s) => &s.state,
185 AnySnapshot::NestedMutable(s) => &s.state,
186 AnySnapshot::Global(s) => &s.state,
187 AnySnapshot::TransparentMutable(s) => &s.state,
188 AnySnapshot::TransparentReadonly(s) => &s.state,
189 }
190 }
191
192 pub(crate) fn with_invalid<R>(&self, f: impl FnOnce(&SnapshotIdSet) -> R) -> R {
193 f(&self.state().invalid.borrow())
194 }
195
196 pub fn invalid(&self) -> SnapshotIdSet {
197 match self {
198 AnySnapshot::Readonly(s) => s.invalid(),
199 AnySnapshot::Mutable(s) => s.invalid(),
200 AnySnapshot::NestedReadonly(s) => s.invalid(),
201 AnySnapshot::NestedMutable(s) => s.invalid(),
202 AnySnapshot::Global(s) => s.invalid(),
203 AnySnapshot::TransparentMutable(s) => s.invalid(),
204 AnySnapshot::TransparentReadonly(s) => s.invalid(),
205 }
206 }
207
208 pub fn is_valid(&self, id: SnapshotId) -> bool {
210 let snapshot_id = self.snapshot_id();
211 id <= snapshot_id && !self.invalid().get(id)
212 }
213
214 pub fn read_only(&self) -> bool {
216 match self {
217 AnySnapshot::Readonly(_) => true,
218 AnySnapshot::Mutable(_) => false,
219 AnySnapshot::NestedReadonly(_) => true,
220 AnySnapshot::NestedMutable(_) => false,
221 AnySnapshot::Global(_) => false,
222 AnySnapshot::TransparentMutable(_) => false,
223 AnySnapshot::TransparentReadonly(_) => true,
224 }
225 }
226
227 pub fn root(&self) -> AnySnapshot {
229 match self {
230 AnySnapshot::Readonly(s) => AnySnapshot::Readonly(s.root_readonly()),
231 AnySnapshot::Mutable(s) => AnySnapshot::Mutable(s.root_mutable()),
232 AnySnapshot::NestedReadonly(s) => AnySnapshot::NestedReadonly(s.root_nested_readonly()),
233 AnySnapshot::NestedMutable(s) => AnySnapshot::Mutable(s.root_mutable()),
234 AnySnapshot::Global(_) => AnySnapshot::Global(GlobalSnapshot::get_or_create()),
235 AnySnapshot::TransparentMutable(s) => {
236 AnySnapshot::TransparentMutable(s.root_transparent_mutable())
237 }
238 AnySnapshot::TransparentReadonly(s) => {
239 AnySnapshot::TransparentReadonly(s.root_transparent_readonly())
240 }
241 }
242 }
243
244 pub fn is_same_transparent(&self, other: &Rc<TransparentObserverMutableSnapshot>) -> bool {
246 matches!(self, AnySnapshot::TransparentMutable(snapshot) if Rc::ptr_eq(snapshot, other))
247 }
248
249 pub fn is_same_transparent_mutable(
251 &self,
252 other: &Rc<TransparentObserverMutableSnapshot>,
253 ) -> bool {
254 self.is_same_transparent(other)
255 }
256
257 pub fn is_same_transparent_readonly(&self, other: &Rc<TransparentObserverSnapshot>) -> bool {
259 matches!(self, AnySnapshot::TransparentReadonly(snapshot) if Rc::ptr_eq(snapshot, other))
260 }
261
262 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
264 match self {
265 AnySnapshot::Readonly(s) => s.enter(f),
266 AnySnapshot::Mutable(s) => s.enter(f),
267 AnySnapshot::NestedReadonly(s) => s.enter(f),
268 AnySnapshot::NestedMutable(s) => s.enter(f),
269 AnySnapshot::Global(s) => s.enter(f),
270 AnySnapshot::TransparentMutable(s) => s.enter(f),
271 AnySnapshot::TransparentReadonly(s) => s.enter(f),
272 }
273 }
274
275 pub fn take_nested_snapshot(&self, read_observer: Option<ReadObserver>) -> AnySnapshot {
277 match self {
278 AnySnapshot::Readonly(s) => {
279 AnySnapshot::Readonly(s.take_nested_snapshot(read_observer))
280 }
281 AnySnapshot::Mutable(s) => AnySnapshot::Readonly(s.take_nested_snapshot(read_observer)),
282 AnySnapshot::NestedReadonly(s) => {
283 AnySnapshot::NestedReadonly(s.take_nested_snapshot(read_observer))
284 }
285 AnySnapshot::NestedMutable(s) => {
286 AnySnapshot::Readonly(s.take_nested_snapshot(read_observer))
287 }
288 AnySnapshot::Global(s) => AnySnapshot::Readonly(s.take_nested_snapshot(read_observer)),
289 AnySnapshot::TransparentMutable(s) => {
290 AnySnapshot::Readonly(s.take_nested_snapshot(read_observer))
291 }
292 AnySnapshot::TransparentReadonly(s) => {
293 AnySnapshot::TransparentReadonly(s.take_nested_snapshot(read_observer))
294 }
295 }
296 }
297
298 pub fn has_pending_changes(&self) -> bool {
300 match self {
301 AnySnapshot::Readonly(s) => s.has_pending_changes(),
302 AnySnapshot::Mutable(s) => s.has_pending_changes(),
303 AnySnapshot::NestedReadonly(s) => s.has_pending_changes(),
304 AnySnapshot::NestedMutable(s) => s.has_pending_changes(),
305 AnySnapshot::Global(s) => s.has_pending_changes(),
306 AnySnapshot::TransparentMutable(s) => s.has_pending_changes(),
307 AnySnapshot::TransparentReadonly(s) => s.has_pending_changes(),
308 }
309 }
310
311 pub fn dispose(&self) {
313 match self {
314 AnySnapshot::Readonly(s) => s.dispose(),
315 AnySnapshot::Mutable(s) => s.dispose(),
316 AnySnapshot::NestedReadonly(s) => s.dispose(),
317 AnySnapshot::NestedMutable(s) => s.dispose(),
318 AnySnapshot::Global(s) => s.dispose(),
319 AnySnapshot::TransparentMutable(s) => s.dispose(),
320 AnySnapshot::TransparentReadonly(s) => s.dispose(),
321 }
322 }
323
324 pub fn is_disposed(&self) -> bool {
326 match self {
327 AnySnapshot::Readonly(s) => s.is_disposed(),
328 AnySnapshot::Mutable(s) => s.is_disposed(),
329 AnySnapshot::NestedReadonly(s) => s.is_disposed(),
330 AnySnapshot::NestedMutable(s) => s.is_disposed(),
331 AnySnapshot::Global(s) => s.is_disposed(),
332 AnySnapshot::TransparentMutable(s) => s.is_disposed(),
333 AnySnapshot::TransparentReadonly(s) => s.is_disposed(),
334 }
335 }
336
337 pub fn record_read(&self, state: &dyn StateObject) {
339 match self {
340 AnySnapshot::Readonly(s) => s.record_read(state),
341 AnySnapshot::Mutable(s) => s.record_read(state),
342 AnySnapshot::NestedReadonly(s) => s.record_read(state),
343 AnySnapshot::NestedMutable(s) => s.record_read(state),
344 AnySnapshot::Global(s) => s.record_read(state),
345 AnySnapshot::TransparentMutable(s) => s.record_read(state),
346 AnySnapshot::TransparentReadonly(s) => s.record_read(state),
347 }
348 }
349
350 pub fn record_write(&self, state: Rc<dyn StateObject>) {
352 match self {
353 AnySnapshot::Readonly(s) => s.record_write(state),
354 AnySnapshot::Mutable(s) => s.record_write(state),
355 AnySnapshot::NestedReadonly(s) => s.record_write(state),
356 AnySnapshot::NestedMutable(s) => s.record_write(state),
357 AnySnapshot::Global(s) => s.record_write(state),
358 AnySnapshot::TransparentMutable(s) => s.record_write(state),
359 AnySnapshot::TransparentReadonly(s) => s.record_write(state),
360 }
361 }
362
363 pub fn apply(&self) -> SnapshotApplyResult {
365 match self {
366 AnySnapshot::Mutable(s) => s.apply(),
367 AnySnapshot::NestedMutable(s) => s.apply(),
368 AnySnapshot::Global(s) => s.apply(),
369 AnySnapshot::TransparentMutable(s) => s.apply(),
370 _ => panic!("Cannot apply a read-only snapshot"),
371 }
372 }
373
374 pub fn take_nested_mutable_snapshot(
376 &self,
377 read_observer: Option<ReadObserver>,
378 write_observer: Option<WriteObserver>,
379 ) -> AnySnapshot {
380 match self {
381 AnySnapshot::Mutable(s) => AnySnapshot::NestedMutable(
382 s.take_nested_mutable_snapshot(read_observer, write_observer),
383 ),
384 AnySnapshot::NestedMutable(s) => AnySnapshot::NestedMutable(
385 s.take_nested_mutable_snapshot(read_observer, write_observer),
386 ),
387 AnySnapshot::Global(s) => {
388 AnySnapshot::Mutable(s.take_nested_mutable_snapshot(read_observer, write_observer))
389 }
390 AnySnapshot::TransparentMutable(s) => AnySnapshot::TransparentMutable(
391 s.take_nested_mutable_snapshot(read_observer, write_observer),
392 ),
393 _ => panic!("Cannot take nested mutable snapshot from read-only snapshot"),
394 }
395 }
396}
397
398pub fn current_snapshot() -> Option<AnySnapshot> {
400 crate::thread_state::try_snapshots(|state| state.current.borrow().clone()).flatten()
401}
402
403pub(crate) fn current_snapshot_reads_into(observer: &ReadObserver) -> bool {
404 crate::thread_state::try_snapshots(|state| match &*state.current.borrow() {
405 Some(AnySnapshot::TransparentMutable(snapshot)) => {
406 snapshot.can_reuse() && snapshot.reads_into(observer)
407 }
408 _ => false,
409 })
410 .unwrap_or(false)
411}
412
413pub(crate) fn set_current_snapshot(snapshot: Option<AnySnapshot>) {
414 crate::thread_state::try_snapshots(|state| {
415 *state.current.borrow_mut() = snapshot;
416 });
417}
418
419struct CurrentSnapshotGuard {
420 previous: Option<AnySnapshot>,
421}
422
423impl CurrentSnapshotGuard {
424 fn enter(snapshot: AnySnapshot) -> Self {
425 let previous =
426 crate::thread_state::try_snapshots(|state| state.current.replace(Some(snapshot)))
427 .flatten();
428 Self { previous }
429 }
430}
431
432impl Drop for CurrentSnapshotGuard {
433 fn drop(&mut self) {
434 set_current_snapshot(self.previous.take());
435 }
436}
437
438pub(crate) fn enter_snapshot_scope<T>(snapshot: AnySnapshot, f: impl FnOnce() -> T) -> T {
439 let _guard = CurrentSnapshotGuard::enter(snapshot);
440 f()
441}
442
443pub fn take_mutable_snapshot(
452 read_observer: Option<ReadObserver>,
453 write_observer: Option<WriteObserver>,
454) -> AnyMutableSnapshot {
455 match current_snapshot() {
456 Some(AnySnapshot::Mutable(parent)) => AnyMutableSnapshot::Nested(
457 parent.take_nested_mutable_snapshot(read_observer, write_observer),
458 ),
459 Some(AnySnapshot::NestedMutable(parent)) => AnyMutableSnapshot::Nested(
460 parent.take_nested_mutable_snapshot(read_observer, write_observer),
461 ),
462 _ => AnyMutableSnapshot::Root(
463 GlobalSnapshot::get_or_create()
464 .take_nested_mutable_snapshot(read_observer, write_observer),
465 ),
466 }
467}
468
469pub fn take_transparent_observer_mutable_snapshot(
478 read_observer: Option<ReadObserver>,
479 write_observer: Option<WriteObserver>,
480) -> Rc<TransparentObserverMutableSnapshot> {
481 take_transparent_observer_mutable_snapshot_reusing(read_observer, write_observer, None)
482}
483
484pub(crate) fn take_transparent_observer_mutable_snapshot_reusing(
485 read_observer: Option<ReadObserver>,
486 write_observer: Option<WriteObserver>,
487 recycled: Option<Rc<TransparentObserverMutableSnapshot>>,
488) -> Rc<TransparentObserverMutableSnapshot> {
489 let parent = current_snapshot();
490 match parent {
491 Some(AnySnapshot::TransparentMutable(transparent)) if transparent.can_reuse() => {
492 let (parent_read, parent_write) = transparent.observers();
493 if already_observes(&read_observer, &parent_read)
494 && already_observes(&write_observer, &parent_write)
495 {
496 return transparent;
497 }
498 TransparentObserverMutableSnapshot::new_reusing(
499 recycled,
500 transparent.snapshot_id(),
501 transparent.invalid(),
502 merge_read_observers(read_observer, parent_read),
503 merge_write_observers(write_observer, parent_write),
504 Some(Rc::downgrade(&transparent)),
505 )
506 }
507 _ => {
508 let current = current_snapshot()
509 .unwrap_or_else(|| AnySnapshot::Global(GlobalSnapshot::get_or_create()));
510 let id = current.snapshot_id();
511 let invalid = current.invalid();
512 TransparentObserverMutableSnapshot::new_reusing(
513 recycled,
514 id,
515 invalid,
516 read_observer,
517 write_observer,
518 None,
519 )
520 }
521 }
522}
523
524fn already_observes(requested: &Option<ReadObserver>, installed: &Option<ReadObserver>) -> bool {
525 match (requested, installed) {
526 (None, _) => true,
527 (Some(requested), Some(installed)) => Rc::ptr_eq(requested, installed),
528 (Some(_), None) => false,
529 }
530}
531
532pub fn allocate_record_id() -> SnapshotId {
534 runtime::allocate_record_id()
535}
536
537pub(crate) fn peek_next_snapshot_id() -> SnapshotId {
538 runtime::peek_next_snapshot_id()
539}
540
541#[derive(Clone)]
542pub(crate) struct ObserverId(Rc<()>);
543
544impl ObserverId {
545 fn new() -> Self {
546 Self(Rc::new(()))
547 }
548}
549
550impl PartialEq for ObserverId {
551 fn eq(&self, other: &Self) -> bool {
552 Rc::ptr_eq(&self.0, &other.0)
553 }
554}
555
556impl Eq for ObserverId {}
557
558impl Hash for ObserverId {
559 fn hash<H: Hasher>(&self, state: &mut H) {
560 Rc::as_ptr(&self.0).hash(state);
561 }
562}
563
564const UNUSED_RECORD_CLEANUP_INTERVAL: SnapshotId = 2;
565const UNUSED_RECORD_CLEANUP_BUSY_INTERVAL: SnapshotId = 1;
566const UNUSED_RECORD_CLEANUP_MIN_SIZE: usize = 64;
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
569pub struct SnapshotV2DebugStats {
570 pub apply_observers_len: usize,
571 pub apply_observers_cap: usize,
572 pub last_writes_len: usize,
573 pub last_writes_cap: usize,
574 pub extra_state_objects_len: usize,
575 pub extra_state_objects_cap: usize,
576 pub last_unused_record_cleanup: SnapshotId,
577}
578
579pub fn debug_snapshot_v2_stats() -> SnapshotV2DebugStats {
580 crate::thread_state::snapshots(|state| {
581 let observers = state.apply_observers.borrow();
582 let writes = state.last_writes.borrow();
583 let SnapshotWeakSetDebugStats {
584 len: extra_state_objects_len,
585 capacity: extra_state_objects_cap,
586 } = state.extra_state_objects.borrow().debug_stats();
587 SnapshotV2DebugStats {
588 apply_observers_len: observers.len(),
589 apply_observers_cap: observers.capacity(),
590 last_writes_len: writes.len(),
591 last_writes_cap: writes.capacity(),
592 extra_state_objects_len,
593 extra_state_objects_cap,
594 last_unused_record_cleanup: state.last_unused_record_cleanup.get(),
595 }
596 })
597}
598
599pub fn register_apply_observer(observer: ApplyObserver) -> ObserverHandle {
603 crate::thread_state::watch_thread_exit();
604 let id = ObserverId::new();
605 crate::thread_state::snapshots(|state| {
606 state
607 .apply_observers
608 .borrow_mut()
609 .insert(id.clone(), observer);
610 });
611 ObserverHandle {
612 kind: ObserverKind::Apply,
613 id,
614 }
615}
616
617pub struct ObserverHandle {
621 kind: ObserverKind,
622 id: ObserverId,
623}
624
625enum ObserverKind {
626 Apply,
627}
628
629impl Drop for ObserverHandle {
630 fn drop(&mut self) {
631 match self.kind {
632 ObserverKind::Apply => {
633 crate::thread_state::try_snapshots(|state| {
637 state.apply_observers.borrow_mut().remove(&self.id);
638 });
639 }
640 }
641 }
642}
643
644pub(crate) fn notify_apply_observers(modified: &[Rc<dyn StateObject>], snapshot_id: SnapshotId) {
645 let observers: Vec<ApplyObserver> = crate::thread_state::snapshots(|state| {
646 state.apply_observers.borrow().values().cloned().collect()
647 });
648 for observer in observers {
649 observer(modified, snapshot_id);
650 }
651}
652
653pub(crate) fn set_last_write(id: StateObjectId, snapshot_id: SnapshotId) {
654 crate::thread_state::snapshots(|state| {
655 state.last_writes.borrow_mut().insert(id, snapshot_id);
656 });
657}
658
659#[cfg(test)]
660pub(crate) fn clear_last_writes() {
661 crate::thread_state::snapshots(|state| state.last_writes.borrow_mut().clear());
662}
663
664pub(crate) fn check_and_overwrite_unused_records_locked() {
665 let _retiring = super::state::RetiredValuesScope::enter();
666 crate::thread_state::snapshots(|state| {
667 state
668 .extra_state_objects
669 .borrow_mut()
670 .remove_if(super::state::StateObject::overwrite_unused_records);
671 });
672}
673
674pub(crate) fn maybe_check_and_overwrite_unused_records_locked(current_snapshot_id: SnapshotId) {
675 let should_run = crate::thread_state::snapshots(|state| {
676 let set = state.extra_state_objects.borrow();
677 if set.is_empty() {
678 return false;
679 }
680 let interval = if set.len() >= UNUSED_RECORD_CLEANUP_MIN_SIZE {
681 UNUSED_RECORD_CLEANUP_BUSY_INTERVAL
682 } else {
683 UNUSED_RECORD_CLEANUP_INTERVAL
684 };
685 let due =
686 current_snapshot_id.saturating_sub(state.last_unused_record_cleanup.get()) >= interval;
687 if due {
688 state.last_unused_record_cleanup.set(current_snapshot_id);
689 }
690 due
691 });
692
693 if should_run {
694 check_and_overwrite_unused_records_locked();
695 }
696}
697
698#[cfg(test)]
699pub(crate) fn clear_unused_record_cleanup_for_tests() {
700 crate::thread_state::snapshots(|state| state.last_unused_record_cleanup.set(0));
701}
702
703pub(crate) fn optimistic_merges(
704 current_snapshot_id: SnapshotId,
705 base_parent_id: SnapshotId,
706 modified_objects: &[(StateObjectId, Rc<dyn StateObject>, SnapshotId)],
707 invalid_snapshots: &SnapshotIdSet,
708 applying_invalid: &SnapshotIdSet,
709) -> Option<HashMap<usize, Rc<StateRecord>>> {
710 if modified_objects.is_empty() {
711 return None;
712 }
713
714 let mut result: Option<HashMap<usize, Rc<StateRecord>>> = None;
715
716 for (_, state, writer_id) in modified_objects {
717 let head = state.first_record();
718
719 let Some(current) =
720 crate::state::readable_record_for(&head, current_snapshot_id, invalid_snapshots)
721 else {
722 continue;
723 };
724
725 let (previous_opt, found_base) =
726 mutable::find_previous_record(&head, base_parent_id, applying_invalid);
727 let previous = previous_opt?;
728
729 if !found_base || previous.snapshot_id() == crate::state::PREEXISTING_SNAPSHOT_ID {
730 continue;
731 }
732
733 if Rc::ptr_eq(¤t, &previous) {
734 continue;
735 }
736
737 let applied = mutable::find_record_by_id(&head, *writer_id)?;
738
739 let merged = state.merge_records(
740 Rc::clone(&previous),
741 Rc::clone(¤t),
742 Rc::clone(&applied),
743 )?;
744
745 result
746 .get_or_insert_with(HashMap::default)
747 .insert(Rc::as_ptr(¤t) as usize, merged);
748 }
749
750 result
751}
752
753fn merge_observers(a: Option<ReadObserver>, b: Option<ReadObserver>) -> Option<ReadObserver> {
754 match (a, b) {
755 (None, None) => None,
756 (Some(a), None) => Some(a),
757 (None, Some(b)) => Some(b),
758 (Some(a), Some(b)) => Some(Rc::new(move |state: &dyn StateObject| {
759 a(state);
760 b(state);
761 })),
762 }
763}
764
765pub fn merge_read_observers(
771 a: Option<ReadObserver>,
772 b: Option<ReadObserver>,
773) -> Option<ReadObserver> {
774 merge_observers(a, b)
775}
776
777pub fn merge_write_observers(
783 a: Option<WriteObserver>,
784 b: Option<WriteObserver>,
785) -> Option<WriteObserver> {
786 merge_observers(a, b)
787}
788
789pub(crate) struct SnapshotState {
790 pub(crate) id: Cell<SnapshotId>,
791 pub(crate) invalid: RefCell<SnapshotIdSet>,
792 pub(crate) pin_handle: Cell<PinHandle>,
793 pub(crate) disposed: Cell<bool>,
794 pub(crate) read_observer: RefCell<Option<ReadObserver>>,
795 pub(crate) write_observer: RefCell<Option<WriteObserver>>,
796 #[expect(clippy::type_complexity)]
797 pub(crate) modified: RefCell<HashMap<StateObjectId, (Rc<dyn StateObject>, SnapshotId)>>,
798 on_dispose: RefCell<Option<Box<dyn FnOnce()>>>,
799 runtime_tracked: bool,
800 pending_children: RefCell<HashSet<SnapshotId>>,
801}
802
803impl SnapshotState {
804 pub(crate) fn new(
805 id: SnapshotId,
806 invalid: SnapshotIdSet,
807 read_observer: Option<ReadObserver>,
808 write_observer: Option<WriteObserver>,
809 runtime_tracked: bool,
810 ) -> Self {
811 Self::new_with_pinning(
812 id,
813 invalid,
814 read_observer,
815 write_observer,
816 runtime_tracked,
817 true,
818 )
819 }
820
821 pub(crate) fn new_with_pinning(
822 id: SnapshotId,
823 invalid: SnapshotIdSet,
824 read_observer: Option<ReadObserver>,
825 write_observer: Option<WriteObserver>,
826 runtime_tracked: bool,
827 should_pin: bool,
828 ) -> Self {
829 let pin_handle = if should_pin {
830 snapshot_pinning::track_pinning(id, &invalid)
831 } else {
832 snapshot_pinning::PinHandle::INVALID
833 };
834 Self {
835 id: Cell::new(id),
836 invalid: RefCell::new(invalid),
837 pin_handle: Cell::new(pin_handle),
838 disposed: Cell::new(false),
839 read_observer: RefCell::new(read_observer),
840 write_observer: RefCell::new(write_observer),
841 modified: RefCell::new(HashMap::default()),
842 on_dispose: RefCell::new(None),
843 runtime_tracked,
844 pending_children: RefCell::new(HashSet::default()),
845 }
846 }
847
848 pub(crate) fn record_read(&self, state: &dyn StateObject) {
849 if let Some(observer) = self.read_observer.borrow().as_ref() {
850 observer(state);
851 }
852 }
853
854 pub(crate) fn record_write(&self, state: Rc<dyn StateObject>, writer_id: SnapshotId) {
855 let state_id = state.object_id().as_usize();
856
857 let mut modified = self.modified.borrow_mut();
858
859 match modified.entry(state_id) {
860 std::collections::hash_map::Entry::Vacant(e) => {
861 if let Some(observer) = self.write_observer.borrow().as_ref() {
862 observer(&*state);
863 }
864 e.insert((state, writer_id));
865 }
866 std::collections::hash_map::Entry::Occupied(mut e) => {
867 e.insert((state, writer_id));
868 }
869 }
870 }
871
872 pub(crate) fn dispose(&self) {
873 if !self.disposed.replace(true) {
874 let pin_handle = self.pin_handle.get();
875 snapshot_pinning::release_pinning(pin_handle);
876 if let Some(cb) = self.on_dispose.borrow_mut().take() {
877 cb();
878 }
879 if self.runtime_tracked {
880 close_snapshot(self.id.get());
881 }
882 }
883 }
884
885 pub(crate) fn add_pending_child(&self, id: SnapshotId) {
886 self.pending_children.borrow_mut().insert(id);
887 }
888
889 pub(crate) fn remove_pending_child(&self, id: SnapshotId) {
890 self.pending_children.borrow_mut().remove(&id);
891 }
892
893 pub(crate) fn has_pending_children(&self) -> bool {
894 !self.pending_children.borrow().is_empty()
895 }
896
897 pub(crate) fn pending_children(&self) -> Vec<SnapshotId> {
898 self.pending_children.borrow().iter().copied().collect()
899 }
900
901 pub(crate) fn set_on_dispose<F>(&self, f: F)
902 where
903 F: FnOnce() + 'static,
904 {
905 *self.on_dispose.borrow_mut() = Some(Box::new(f));
906 }
907}
908
909pub(crate) trait NestedMutableHost {
910 fn snapshot_state(&self) -> &SnapshotState;
911 fn nested_count(&self) -> &Cell<usize>;
912}
913
914pub(crate) fn clear_nested_child_on_dispose<P>(
915 parent: &Rc<P>,
916 child_id: SnapshotId,
917) -> impl FnOnce() + 'static
918where
919 P: NestedMutableHost + 'static,
920{
921 let weak = Rc::downgrade(parent);
922 move || {
923 if let Some(parent) = weak.upgrade() {
924 let nested_count = parent.nested_count();
925 if nested_count.get() > 0 {
926 nested_count.set(nested_count.get().saturating_sub(1));
927 }
928 let state = parent.snapshot_state();
929 let new_invalid = state.invalid.borrow().clone().clear(child_id);
930 state.invalid.replace(new_invalid);
931 state.remove_pending_child(child_id);
932 }
933 }
934}
935
936pub(crate) fn allocate_nested_mutable_snapshot<P>(
937 parent: &Rc<P>,
938 root: Weak<MutableSnapshot>,
939 read_observer: Option<ReadObserver>,
940 write_observer: Option<WriteObserver>,
941) -> Rc<NestedMutableSnapshot>
942where
943 P: NestedMutableHost + 'static,
944{
945 let state = parent.snapshot_state();
946 let merged_read = merge_read_observers(read_observer, state.read_observer.borrow().clone());
947 let merged_write = merge_write_observers(write_observer, state.write_observer.borrow().clone());
948
949 let parent_id = state.id.get();
950 let current_invalid = state.invalid.borrow().clone();
951
952 let (new_id, _runtime_invalid) = allocate_snapshot();
953
954 let parent_invalid_with_child = current_invalid.set(new_id);
955 state.invalid.replace(parent_invalid_with_child);
956
957 let invalid = current_invalid.add_range(parent_id + 1, new_id);
958
959 let nested = NestedMutableSnapshot::new(
960 new_id,
961 invalid,
962 merged_read,
963 merged_write,
964 root,
965 state.id.get(),
966 );
967
968 let nested_count = parent.nested_count();
969 nested_count.set(nested_count.get() + 1);
970 state.add_pending_child(new_id);
971
972 nested.set_on_dispose(clear_nested_child_on_dispose(parent, new_id));
973
974 nested
975}
976
977#[cfg(test)]
978#[path = "tests/snapshot_v2_tests.rs"]
979mod tests;