Skip to main content

cranpose_core/
state.rs

1// StateRecord uses Rc with Cell for single-threaded shared ownership in the snapshot system.
2#![allow(clippy::arc_with_non_send_sync)]
3
4use crate::collections::map::{HashMap, HashSet};
5use crate::debug_trace::debug_record_scope_invalidation;
6use std::any::Any;
7use std::cell::{Cell, RefCell};
8use std::fmt;
9use std::hash::Hash;
10use std::marker::PhantomData;
11use std::ops::Deref;
12use std::rc::{Rc, Weak as RcWeak};
13use std::sync::{Arc, Mutex, MutexGuard, Weak};
14
15use crate::snapshot_id_set::{SnapshotId, SnapshotIdSet};
16use crate::snapshot_pinning::lowest_pinned_snapshot;
17use crate::snapshot_v2::{
18    advance_global_snapshot, allocate_record_id, current_snapshot, AnySnapshot, GlobalSnapshot,
19};
20use crate::{
21    runtime, with_current_composer_opt, RecomposeScope, RecomposeScopeInner, RuntimeHandle,
22    ScopeId, StateId,
23};
24
25pub(crate) const PREEXISTING_SNAPSHOT_ID: SnapshotId = 1;
26
27const INVALID_SNAPSHOT_ID: SnapshotId = 0;
28
29/// Maximum snapshot ID used to mark records as invisible during initialization
30const SNAPSHOT_ID_MAX: SnapshotId = usize::MAX;
31
32#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, Default)]
33pub struct ObjectId(pub(crate) usize);
34
35impl ObjectId {
36    pub(crate) fn new<T: ?Sized + 'static>(object: &Arc<T>) -> Self {
37        Self(Arc::as_ptr(object) as *const () as usize)
38    }
39
40    #[inline]
41    pub(crate) fn as_usize(self) -> usize {
42        self.0
43    }
44}
45
46/// A record in the state history chain.
47///
48/// # Thread Safety
49/// Contains `Cell<T>` which is not `Send`/`Sync`. This is safe because state records
50/// are accessed only from the UI thread via thread-local snapshot system. The `Rc`
51/// is used for cheap cloning and shared ownership within a single thread.
52pub struct StateRecord {
53    snapshot_id: Cell<SnapshotId>,
54    tombstone: Cell<bool>,
55    next: Cell<Option<Rc<StateRecord>>>,
56    value: RefCell<Option<Box<dyn Any>>>,
57}
58
59#[derive(Debug)]
60struct StateReadFailure {
61    state_id: ObjectId,
62    snapshot_id: SnapshotId,
63    fresh_snapshot_id: SnapshotId,
64    fresh_invalid: SnapshotIdSet,
65    record_chain: Vec<(SnapshotId, bool)>,
66}
67
68impl std::fmt::Display for StateReadFailure {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(
71            f,
72            "Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied\n\
73             state={:?}, snapshot_id={}, fresh_snapshot_id={}, fresh_invalid={:?}\n\
74             record_chain={:?}",
75            self.state_id,
76            self.snapshot_id,
77            self.fresh_snapshot_id,
78            self.fresh_invalid,
79            self.record_chain
80        )
81    }
82}
83
84#[derive(Debug, Clone, Copy, Eq, PartialEq)]
85pub(crate) enum StateRecordValueError {
86    MissingOrWrongType { expected: &'static str },
87}
88
89impl StateRecord {
90    pub(crate) fn new<T: Any>(
91        snapshot_id: SnapshotId,
92        value: T,
93        next: Option<Rc<StateRecord>>,
94    ) -> Rc<Self> {
95        Rc::new(Self {
96            snapshot_id: Cell::new(snapshot_id),
97            tombstone: Cell::new(false),
98            next: Cell::new(next),
99            value: RefCell::new(Some(Box::new(value))),
100        })
101    }
102
103    #[inline]
104    pub(crate) fn snapshot_id(&self) -> SnapshotId {
105        self.snapshot_id.get()
106    }
107
108    #[inline]
109    pub(crate) fn set_snapshot_id(&self, id: SnapshotId) {
110        self.snapshot_id.set(id);
111    }
112
113    #[inline]
114    pub(crate) fn next(&self) -> Option<Rc<StateRecord>> {
115        self.next.take().inspect(|record| {
116            self.next.set(Some(Rc::clone(record)));
117        })
118    }
119
120    #[inline]
121    pub(crate) fn set_next(&self, next: Option<Rc<StateRecord>>) {
122        self.next.set(next);
123    }
124
125    #[inline]
126    pub(crate) fn is_tombstone(&self) -> bool {
127        self.tombstone.get()
128    }
129
130    #[inline]
131    pub(crate) fn set_tombstone(&self, tombstone: bool) {
132        self.tombstone.set(tombstone);
133    }
134
135    pub(crate) fn clear_value(&self) {
136        self.value.borrow_mut().take();
137    }
138
139    pub(crate) fn replace_value<T: Any>(&self, new_value: T) {
140        *self.value.borrow_mut() = Some(Box::new(new_value));
141    }
142
143    pub(crate) fn with_value<T: Any, R>(&self, f: impl FnOnce(&T) -> R) -> R {
144        self.try_with_value(f)
145            .unwrap_or_else(|| panic!("StateRecord value missing or wrong type"))
146    }
147
148    pub(crate) fn try_with_value<T: Any, R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
149        let guard = self.value.borrow();
150        let value = guard.as_ref().and_then(|boxed| boxed.downcast_ref::<T>())?;
151        Some(f(value))
152    }
153
154    /// Clears the value from this record to free memory.
155    /// Used when marking records as reusable - clears the value to reduce memory usage.
156    #[cfg(test)]
157    pub(crate) fn clear_for_reuse(&self) {
158        self.clear_value();
159    }
160
161    /// Copies the value from the source record into this record.
162    ///
163    /// This is used during record reuse to copy valid data from a readable record
164    /// into a reused record, and during cleanup to preserve data in records being
165    /// marked as INVALID_SNAPSHOT.
166    ///
167    pub(crate) fn assign_value<T: Any + Clone>(
168        &self,
169        source: &StateRecord,
170    ) -> Result<(), StateRecordValueError> {
171        let cloned_value = source.try_with_value(|value: &T| value.clone()).ok_or(
172            StateRecordValueError::MissingOrWrongType {
173                expected: std::any::type_name::<T>(),
174            },
175        )?;
176        self.replace_value(cloned_value);
177        Ok(())
178    }
179}
180
181impl Drop for StateRecord {
182    fn drop(&mut self) {
183        // Prevent recursive drop of deep chains which can cause stack overflow.
184        // We iteratively detach and drop the next record if we are the sole owner.
185        let mut next = self.next.take();
186        while let Some(node) = next {
187            match Rc::try_unwrap(node) {
188                Ok(record) => {
189                    // We were the last owner. Take its next pointer to continue the loop.
190                    // The record itself will be dropped here, but since next is None,
191                    // it won't recurse.
192                    next = record.next.take();
193                }
194                Err(_) => {
195                    // Someone else holds a reference to this node.
196                    // The chain destruction stops here.
197                    break;
198                }
199            }
200        }
201    }
202}
203
204/// Owns the mutable head pointer for a state's record chain.
205///
206/// Snapshot code clones the current head, prepends new records, and swaps in
207/// replacement heads frequently. Centralizing those operations keeps the
208/// `RefCell<Rc<StateRecord>>` borrow protocol out of the higher-level state
209/// logic.
210struct CurrentRecord {
211    head: RefCell<Rc<StateRecord>>,
212}
213
214impl CurrentRecord {
215    fn new(head: Rc<StateRecord>) -> Self {
216        Self {
217            head: RefCell::new(head),
218        }
219    }
220
221    fn clone_head(&self) -> Rc<StateRecord> {
222        self.head.borrow().clone()
223    }
224
225    fn replace(&self, new_head: Rc<StateRecord>) {
226        *self.head.borrow_mut() = new_head;
227    }
228
229    fn prepend(&self, record: Rc<StateRecord>) {
230        let current_head = self.clone_head();
231        record.set_next(Some(current_head));
232        self.replace(record);
233    }
234}
235
236#[inline]
237fn record_is_valid_for(
238    record: &Rc<StateRecord>,
239    snapshot_id: SnapshotId,
240    invalid: &SnapshotIdSet,
241) -> bool {
242    if record.is_tombstone() {
243        return false;
244    }
245
246    let candidate = record.snapshot_id();
247    if candidate == INVALID_SNAPSHOT_ID || candidate > snapshot_id {
248        return false;
249    }
250
251    candidate == snapshot_id || !invalid.get(candidate)
252}
253
254pub(crate) fn readable_record_for(
255    head: &Rc<StateRecord>,
256    snapshot_id: SnapshotId,
257    invalid: &SnapshotIdSet,
258) -> Option<Rc<StateRecord>> {
259    // Find the highest valid record in the chain.
260    // We must scan the full chain because reused records may not be prepended
261    // as the head but still need to be found (e.g., after writes using record reuse).
262    let mut best: Option<Rc<StateRecord>> = None;
263    let mut cursor = Some(Rc::clone(head));
264
265    while let Some(record) = cursor {
266        if record_is_valid_for(&record, snapshot_id, invalid) {
267            let replace = best
268                .as_ref()
269                .map(|current| current.snapshot_id() < record.snapshot_id())
270                .unwrap_or(true);
271            if replace {
272                best = Some(Rc::clone(&record));
273            }
274        }
275        cursor = record.next();
276    }
277
278    best
279}
280
281/// Finds the youngest record in the chain, or the first one matching the predicate.
282///
283/// Searches the record chain starting from the given head:
284/// - If a record matches the predicate, returns it immediately
285/// - Otherwise, tracks the youngest record (highest snapshot_id) and returns it
286fn find_youngest_or<F>(head: &Rc<StateRecord>, predicate: F) -> Rc<StateRecord>
287where
288    F: Fn(&Rc<StateRecord>) -> bool,
289{
290    let mut current = Some(Rc::clone(head));
291    let mut youngest = Rc::clone(head);
292
293    while let Some(record) = current {
294        if predicate(&record) {
295            return record;
296        }
297        if youngest.snapshot_id() < record.snapshot_id() {
298            youngest = Rc::clone(&record);
299        }
300        current = record.next();
301    }
302
303    youngest
304}
305
306/// Finds a StateRecord that can be safely reused because no open snapshot can see it.
307///
308/// Returns a record that either:
309/// 1. Is marked as INVALID_SNAPSHOT (abandoned/tombstone)
310/// 2. Is obscured by a newer record (both are below the reuse limit)
311///
312/// The reuse limit is `lowest_pinned_snapshot - 1`, meaning any record with a snapshot ID
313/// at or below this value cannot be selected by any currently open snapshot.
314///
315/// Note: PREEXISTING records (snapshot_id=1) are never reused to maintain the ability
316/// for all snapshots to read the initial state.
317pub(crate) fn used_locked(head: &Rc<StateRecord>) -> Option<Rc<StateRecord>> {
318    let mut current = Some(Rc::clone(head));
319    let mut valid_record: Option<Rc<StateRecord>> = None;
320
321    // Calculate reuse limit: records below this ID are invisible to all open snapshots
322    let reuse_limit = lowest_pinned_snapshot()
323        .map(|lowest| lowest.saturating_sub(1))
324        .unwrap_or_else(|| allocate_record_id().saturating_sub(1));
325
326    let invalid = SnapshotIdSet::EMPTY;
327
328    while let Some(record) = current {
329        let current_id = record.snapshot_id();
330
331        // Never reuse PREEXISTING records - they must always be available as a fallback
332        if current_id == PREEXISTING_SNAPSHOT_ID {
333            current = record.next();
334            continue;
335        }
336
337        // Fast path: records marked INVALID_SNAPSHOT can be reused immediately
338        if current_id == INVALID_SNAPSHOT_ID {
339            return Some(record);
340        }
341
342        if record.is_tombstone() && current_id < reuse_limit {
343            return Some(record);
344        }
345
346        // Check if this record is valid for snapshots at or below the reuse limit
347        if record_is_valid_for(&record, reuse_limit, &invalid) {
348            if let Some(ref existing) = valid_record {
349                // We found two valid records below the reuse limit.
350                // This means one obscures the other - return the older one for reuse.
351                return Some(if current_id < existing.snapshot_id() {
352                    record
353                } else {
354                    Rc::clone(existing)
355                });
356            } else {
357                // First valid record below reuse limit - keep looking
358                valid_record = Some(record.clone());
359            }
360        }
361
362        current = record.next();
363    }
364
365    // No reusable record found
366    None
367}
368
369/// Creates a new overwritable record for a state object, reusing an existing record if possible.
370///
371/// The record is initially marked with SNAPSHOT_ID_MAX to make it invisible to all snapshots
372/// during initialization. The caller must:
373/// 1. Copy/set the desired value into the record
374/// 2. Set the final snapshot_id
375///
376/// Returns a record that is either:
377/// - A reused record (if `used_locked()` found one), marked with SNAPSHOT_ID_MAX
378/// - A newly created record, prepended to the state's record chain via `prepend_state_record()`
379pub(crate) fn new_overwritable_record_locked(state: &dyn StateObject) -> Rc<StateRecord> {
380    let state_head = state.first_record();
381
382    // Try to reuse an existing record
383    if let Some(reusable) = used_locked(&state_head) {
384        // Mark as invisible during initialization
385        reusable.set_snapshot_id(SNAPSHOT_ID_MAX);
386        return reusable;
387    }
388
389    // No reusable record found; create an invisible record and let the caller
390    // replace the unit initializer before publishing the final snapshot id.
391    let new_record = StateRecord::new(
392        SNAPSHOT_ID_MAX,
393        (),
394        None, // next will be set by prepend_state_record
395    );
396
397    // Prepend the new record to the state's chain
398    state.prepend_state_record(Rc::clone(&new_record));
399
400    new_record
401}
402
403/// Creates an overwritable record and ensures it is the head of the record chain.
404///
405/// This is used for global snapshot writes where the newest record must be at the head
406/// to keep tombstoning logic consistent. Reused records are unlinked from their current
407/// position before being prepended.
408pub(crate) fn new_overwritable_record_as_head_locked(state: &dyn StateObject) -> Rc<StateRecord> {
409    let head = state.first_record();
410
411    if let Some(reusable) = used_locked(&head) {
412        reusable.set_snapshot_id(SNAPSHOT_ID_MAX);
413
414        if !Rc::ptr_eq(&head, &reusable) {
415            let mut cursor = Some(Rc::clone(&head));
416            let mut unlinked = false;
417
418            while let Some(node) = cursor {
419                let next = node.next();
420                if let Some(next_record) = next {
421                    if Rc::ptr_eq(&next_record, &reusable) {
422                        node.set_next(reusable.next());
423                        unlinked = true;
424                        break;
425                    }
426                    cursor = Some(next_record);
427                } else {
428                    break;
429                }
430            }
431
432            if !unlinked {
433                debug_assert!(
434                    false,
435                    "new_overwritable_record_as_head_locked: reusable record not found in chain"
436                );
437                let new_record = StateRecord::new(SNAPSHOT_ID_MAX, (), None);
438                state.prepend_state_record(Rc::clone(&new_record));
439                return new_record;
440            }
441
442            state.prepend_state_record(Rc::clone(&reusable));
443        }
444
445        return reusable;
446    }
447
448    let new_record = StateRecord::new(SNAPSHOT_ID_MAX, (), None);
449    state.prepend_state_record(Rc::clone(&new_record));
450    new_record
451}
452
453/// Overwrites unused records in a state object's record chain with data from retained records.
454///
455/// This function implements Kotlin's `overwriteUnusedRecordsLocked` to reclaim memory by:
456/// 1. Finding records below the reuse limit (records invisible to all open snapshots)
457/// 2. Keeping the highest record below the reuse limit (so lowest pinned snapshot can see it)
458/// 3. Marking older obscured records as INVALID_SNAPSHOT and copying valid data into them
459///
460/// The valid data is copied from a "young" record (above reuse limit) to ensure that if
461/// an invalidated record is somehow accessed, it contains current valid data rather than
462/// cleared/garbage values.
463///
464/// Returns `true` if the state has multiple retained records and should stay in extraStateObjects,
465/// `false` if it can be removed from tracking.
466pub(crate) fn overwrite_unused_records_locked<T: Any + Clone>(state: &dyn StateObject) -> bool {
467    let head = state.first_record();
468    let mut current = Some(Rc::clone(&head));
469    let mut overwrite_record: Option<Rc<StateRecord>> = None;
470    let mut valid_record: Option<Rc<StateRecord>> = None;
471
472    // Calculate reuse limit: records below this ID are invisible to all open snapshots
473    // Mirrors Kotlin's: val reuseLimit = pinningTable.lowestOrDefault(nextSnapshotId)
474    let reuse_limit =
475        lowest_pinned_snapshot().unwrap_or_else(crate::snapshot_v2::peek_next_snapshot_id);
476
477    let mut retained_records = 0;
478
479    while let Some(record) = current {
480        let current_id = record.snapshot_id();
481
482        if current_id == INVALID_SNAPSHOT_ID {
483            // Already invalid, skip
484        } else if current_id < reuse_limit {
485            if valid_record.is_none() {
486                // If any records are below reuse_limit, we must keep the highest one
487                // so the lowest snapshot can select it
488                valid_record = Some(Rc::clone(&record));
489                retained_records += 1;
490            } else {
491                // We have two records below the reuse limit - one obscures the other
492                // Overwrite the older one (lower snapshot_id)
493                let Some(valid) = valid_record.as_ref() else {
494                    valid_record = Some(Rc::clone(&record));
495                    retained_records += 1;
496                    current = record.next();
497                    continue;
498                };
499                let record_to_overwrite = if current_id < valid.snapshot_id() {
500                    Rc::clone(&record)
501                } else {
502                    // Keep current as valid, overwrite the previous valid
503                    let to_overwrite = Rc::clone(valid);
504                    valid_record = Some(Rc::clone(&record));
505                    to_overwrite
506                };
507
508                // Lazily find a young record to copy data from
509                let source_record = overwrite_record.get_or_insert_with(|| {
510                    find_youngest_or(&head, |r| r.snapshot_id() >= reuse_limit)
511                });
512
513                // Mark the old record as invalid and copy valid data into it
514                record_to_overwrite.set_snapshot_id(INVALID_SNAPSHOT_ID);
515                if let Err(error) = record_to_overwrite.assign_value::<T>(source_record) {
516                    log::error!(
517                        "snapshot cleanup could not copy retained state record value for state {:?}: {:?}",
518                        state.object_id(),
519                        error
520                    );
521                }
522            }
523        } else {
524            // Record is above reuse limit - it's still visible and must be kept
525            retained_records += 1;
526        }
527
528        current = record.next();
529    }
530
531    // Return true if we have multiple records that must be retained
532    // (state should stay in extraStateObjects for future cleanup)
533    retained_records > 1
534}
535
536fn active_snapshot() -> AnySnapshot {
537    current_snapshot().unwrap_or_else(|| AnySnapshot::Global(GlobalSnapshot::get_or_create()))
538}
539
540pub(crate) trait MutationPolicy<T>: Send + Sync {
541    fn equivalent(&self, a: &T, b: &T) -> bool;
542    fn merge(&self, _previous: &T, _current: &T, _applied: &T) -> Option<T> {
543        None
544    }
545}
546
547pub(crate) struct NeverEqual;
548
549impl<T> MutationPolicy<T> for NeverEqual {
550    fn equivalent(&self, _a: &T, _b: &T) -> bool {
551        false
552    }
553}
554
555pub trait StateObject: Any {
556    fn object_id(&self) -> ObjectId;
557    fn first_record(&self) -> Rc<StateRecord>;
558    fn try_readable_record(
559        &self,
560        snapshot_id: SnapshotId,
561        invalid: &SnapshotIdSet,
562    ) -> Option<Rc<StateRecord>>;
563    fn readable_record(&self, snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> Rc<StateRecord>;
564
565    /// Prepends a record to the head of the record chain.
566    /// This is used when reusing records - the record's next pointer is updated to point to the current head,
567    /// and the head is updated to point to the new record.
568    fn prepend_state_record(&self, record: Rc<StateRecord>);
569
570    fn merge_records(
571        &self,
572        _previous: Rc<StateRecord>,
573        _current: Rc<StateRecord>,
574        _applied: Rc<StateRecord>,
575    ) -> Option<Rc<StateRecord>> {
576        None
577    }
578
579    fn commit_merged_record(&self, _merged: Rc<StateRecord>) -> Result<SnapshotId, &'static str> {
580        Err("StateObject does not support merged record commits")
581    }
582    fn promote_record(&self, child_id: SnapshotId) -> Result<(), &'static str>;
583
584    /// Overwrites unused records in this state's record chain with valid data.
585    ///
586    /// Returns `true` if the state has multiple retained records and should stay in extraStateObjects,
587    /// `false` if it can be removed from tracking.
588    fn overwrite_unused_records(&self) -> bool {
589        false // Default implementation for states that don't support cleanup
590    }
591
592    /// Downcast to Any for testing/debugging purposes.
593    fn as_any(&self) -> &dyn Any;
594}
595
596pub(crate) struct SnapshotMutableState<T> {
597    head: CurrentRecord,
598    policy: Arc<dyn MutationPolicy<T>>,
599    id: ObjectId,
600    weak_self: Mutex<Option<Weak<Self>>>,
601    apply_observers: Mutex<Vec<Box<dyn Fn() + 'static>>>,
602}
603
604impl<T> SnapshotMutableState<T> {
605    fn assert_chain_integrity(&self, caller: &str, snapshot_context: Option<SnapshotId>) {
606        if !should_check_chain_integrity() {
607            return;
608        }
609        let head = self.head.clone_head();
610        let mut cursor = Some(head);
611        let mut seen: HashSet<usize> = HashSet::default();
612        let mut ids = Vec::new();
613
614        while let Some(record) = cursor {
615            let addr = Rc::as_ptr(&record) as usize;
616            assert!(
617                seen.insert(addr),
618                "SnapshotMutableState::{} detected duplicate/cycle at record {:p} for state {:?} (snapshot_context={:?}, chain_ids={:?})",
619                caller,
620                Rc::as_ptr(&record),
621                self.id,
622                snapshot_context,
623                ids
624            );
625            ids.push(record.snapshot_id());
626            cursor = record.next();
627        }
628
629        assert!(
630            !ids.is_empty(),
631            "SnapshotMutableState::{} finished integrity scan with empty id list for state {:?} (snapshot_context={:?})",
632            caller,
633            self.id,
634            snapshot_context
635        );
636    }
637}
638
639fn should_check_chain_integrity() -> bool {
640    #[cfg(debug_assertions)]
641    {
642        true
643    }
644
645    #[cfg(not(debug_assertions))]
646    {
647        std::env::var_os("CRANPOSE_ASSERT_STATE_CHAIN").is_some()
648    }
649}
650
651impl<T: Clone + 'static> SnapshotMutableState<T> {
652    fn record_chain_debug(&self) -> Vec<(SnapshotId, bool)> {
653        let mut chain_ids = Vec::new();
654        let mut cursor = Some(self.first_record());
655        while let Some(record) = cursor {
656            chain_ids.push((record.snapshot_id(), record.is_tombstone()));
657            cursor = record.next();
658        }
659        chain_ids
660    }
661
662    fn readable_record_for_active_snapshot(&self) -> Result<Rc<StateRecord>, StateReadFailure> {
663        let snapshot = active_snapshot();
664        if let Some(state) = self.upgrade_self() {
665            snapshot.record_read(&*state);
666        }
667
668        let snapshot_id = snapshot.snapshot_id();
669        let invalid = snapshot.invalid();
670
671        if let Some(record) = self.readable_for(snapshot_id, &invalid) {
672            return Ok(record);
673        }
674
675        let fresh_snapshot = active_snapshot();
676        let fresh_id = fresh_snapshot.snapshot_id();
677        let fresh_invalid = fresh_snapshot.invalid();
678
679        if let Some(record) = self.readable_for(fresh_id, &fresh_invalid) {
680            return Ok(record);
681        }
682
683        let global = GlobalSnapshot::get_or_create();
684        let global_id = global.snapshot_id();
685        let global_invalid = global.invalid();
686
687        if let Some(record) = self.readable_for(global_id, &global_invalid) {
688            return Ok(record);
689        }
690
691        Err(StateReadFailure {
692            state_id: self.id,
693            snapshot_id,
694            fresh_snapshot_id: fresh_id,
695            fresh_invalid,
696            record_chain: self.record_chain_debug(),
697        })
698    }
699
700    fn readable_for(
701        &self,
702        snapshot_id: SnapshotId,
703        invalid: &SnapshotIdSet,
704    ) -> Option<Rc<StateRecord>> {
705        let head = self.first_record();
706        readable_record_for(&head, snapshot_id, invalid)
707    }
708
709    fn writable_record(&self, snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> Rc<StateRecord> {
710        let readable = match self.readable_for(snapshot_id, invalid) {
711            Some(record) => record,
712            None => {
713                let current_head = self.head.clone_head();
714                let refreshed = readable_record_for(&current_head, snapshot_id, invalid);
715                let source = refreshed.unwrap_or_else(|| current_head.clone());
716
717                // Create a new record
718                // Record reuse is NOT used here to preserve history for conflict detection
719                // Reuse happens during cleanup (overwrite_unused_records_locked)
720                let cloned_value = source.with_value(|value: &T| value.clone());
721                let new_head = StateRecord::new(snapshot_id, cloned_value, Some(current_head));
722                self.head.replace(new_head.clone());
723                self.assert_chain_integrity("writable_record(recover)", Some(snapshot_id));
724                return new_head;
725            }
726        };
727
728        if readable.snapshot_id() == snapshot_id {
729            return readable;
730        }
731
732        let refreshed = {
733            let current_head = self.head.clone_head();
734            let refreshed = readable_record_for(&current_head, snapshot_id, invalid).unwrap_or_else(
735                || {
736                    panic!(
737                        "SnapshotMutableState::writable_record failed to locate refreshed readable record (state {:?}, snapshot_id={}, invalid={:?})",
738                        self.id, snapshot_id, invalid
739                    )
740                },
741            );
742
743            if refreshed.snapshot_id() == snapshot_id {
744                return refreshed;
745            }
746
747            Rc::clone(&refreshed)
748        };
749
750        let overwritable = new_overwritable_record_locked(self);
751        if let Err(error) = overwritable.assign_value::<T>(&refreshed) {
752            log::error!(
753                "snapshot writable record could not copy refreshed value for state {:?}: {:?}",
754                self.id,
755                error
756            );
757        }
758        overwritable.set_snapshot_id(snapshot_id);
759        overwritable.set_tombstone(false);
760
761        self.assert_chain_integrity("writable_record(reuse)", Some(snapshot_id));
762
763        overwritable
764    }
765
766    pub(crate) fn new_in_arc(initial: T, policy: Arc<dyn MutationPolicy<T>>) -> Arc<Self> {
767        let snapshot = active_snapshot();
768        let snapshot_id = snapshot.snapshot_id();
769
770        let tail = StateRecord::new(PREEXISTING_SNAPSHOT_ID, initial.clone(), None);
771        let head = StateRecord::new(snapshot_id, initial, Some(tail));
772
773        let mut state = Arc::new(Self {
774            head: CurrentRecord::new(head),
775            policy,
776            id: ObjectId::default(),
777            weak_self: Mutex::new(None),
778            apply_observers: Mutex::new(Vec::new()),
779        });
780
781        let id = ObjectId::new(&state);
782        if let Some(state_inner) = Arc::get_mut(&mut state) {
783            state_inner.id = id;
784        }
785
786        *state.lock_weak_self() = Some(Arc::downgrade(&state));
787
788        // No need to advance the global snapshot for initial state creation
789
790        state
791    }
792
793    pub(crate) fn add_apply_observer(&self, observer: Box<dyn Fn() + 'static>) {
794        self.lock_apply_observers().push(observer);
795    }
796
797    fn notify_applied(&self) {
798        let observers = self.lock_apply_observers();
799        for observer in observers.iter() {
800            observer();
801        }
802    }
803
804    fn lock_weak_self(&self) -> MutexGuard<'_, Option<Weak<Self>>> {
805        self.weak_self
806            .lock()
807            .unwrap_or_else(|poisoned| poisoned.into_inner())
808    }
809
810    fn lock_apply_observers(&self) -> MutexGuard<'_, Vec<Box<dyn Fn() + 'static>>> {
811        self.apply_observers
812            .lock()
813            .unwrap_or_else(|poisoned| poisoned.into_inner())
814    }
815
816    fn upgrade_self(&self) -> Option<Arc<Self>> {
817        self.lock_weak_self()
818            .as_ref()
819            .and_then(|weak| weak.upgrade())
820    }
821
822    #[inline]
823    pub(crate) fn id(&self) -> ObjectId {
824        self.id
825    }
826
827    pub(crate) fn try_with_value<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
828        let record = self.readable_record_for_active_snapshot().ok()?;
829        record.try_with_value(f)
830    }
831
832    pub(crate) fn try_get(&self) -> Option<T> {
833        self.try_with_value(Clone::clone)
834    }
835
836    pub(crate) fn get(&self) -> T {
837        let record = self
838            .readable_record_for_active_snapshot()
839            .unwrap_or_else(|failure| panic!("{failure}"));
840        record.with_value(|value: &T| value.clone())
841    }
842
843    pub(crate) fn set(&self, new_value: T) -> bool {
844        // Debug-only check: warn if modifying state in event handler without proper snapshot
845        #[cfg(debug_assertions)]
846        {
847            let in_handler = crate::in_event_handler();
848            let in_snapshot = crate::in_applied_snapshot();
849            if in_handler && !in_snapshot {
850                log::warn!(
851                    target: "cranpose::state",
852                    "State modified in event handler without run_in_mutable_snapshot; \
853                     this can make updates invisible to other contexts. Wrap the handler \
854                     in run_in_mutable_snapshot() or dispatch_ui_event(). State: {:?}",
855                    self.id
856                );
857            }
858        }
859
860        let snapshot = active_snapshot();
861        let snapshot_id = snapshot.snapshot_id();
862
863        match &snapshot {
864            AnySnapshot::Global(global) => {
865                let invalid = snapshot.invalid();
866                let equivalent = self
867                    .readable_for(snapshot_id, &invalid)
868                    .map(|record| {
869                        record.with_value(|current: &T| self.policy.equivalent(current, &new_value))
870                    })
871                    .unwrap_or(false);
872                if equivalent {
873                    return false;
874                }
875
876                if global.has_pending_children() {
877                    panic!(
878                        "SnapshotMutableState::set attempted global write while pending children {:?} exist (state {:?}, snapshot_id={})",
879                        global.pending_children(),
880                        self.id,
881                        snapshot_id
882                    );
883                }
884
885                let mut written_state: Option<Arc<dyn StateObject>> = None;
886                if let Some(state) = self.upgrade_self() {
887                    let trait_object: Arc<dyn StateObject> = state.clone();
888                    snapshot.record_write(trait_object.clone());
889                    written_state = Some(trait_object);
890                }
891                mark_update_write(self.id);
892
893                let new_id = allocate_record_id();
894                let record = new_overwritable_record_as_head_locked(self);
895                record.replace_value(new_value);
896                record.set_snapshot_id(new_id);
897                record.set_tombstone(false);
898                advance_global_snapshot(new_id);
899                self.assert_chain_integrity("set(global-push)", Some(snapshot_id));
900
901                if !global.has_pending_children() {
902                    let mut cursor = record.next();
903                    while let Some(node) = cursor {
904                        if !node.is_tombstone() && node.snapshot_id() != PREEXISTING_SNAPSHOT_ID {
905                            node.clear_value();
906                            node.set_tombstone(true);
907                        }
908                        cursor = node.next();
909                    }
910                    self.assert_chain_integrity("set(global-tombstone)", Some(snapshot_id));
911                }
912
913                if let Some(modified) = written_state.as_ref() {
914                    crate::snapshot_v2::notify_apply_observers(
915                        std::slice::from_ref(modified),
916                        new_id,
917                    );
918                }
919            }
920            AnySnapshot::Mutable(_)
921            | AnySnapshot::NestedMutable(_)
922            | AnySnapshot::TransparentMutable(_) => {
923                let invalid = snapshot.invalid();
924                let equivalent = self
925                    .readable_for(snapshot_id, &invalid)
926                    .map(|record| {
927                        record.with_value(|current: &T| self.policy.equivalent(current, &new_value))
928                    })
929                    .unwrap_or(false);
930                if equivalent {
931                    return false;
932                }
933
934                if let Some(state) = self.upgrade_self() {
935                    let trait_object: Arc<dyn StateObject> = state.clone();
936                    snapshot.record_write(trait_object);
937                }
938                mark_update_write(self.id);
939
940                let record = self.writable_record(snapshot_id, &invalid);
941                record.replace_value(new_value);
942                self.assert_chain_integrity("set(child-writable)", Some(snapshot_id));
943            }
944            AnySnapshot::Readonly(_)
945            | AnySnapshot::NestedReadonly(_)
946            | AnySnapshot::TransparentReadonly(_) => {
947                panic!("Cannot write to a read-only snapshot");
948            }
949        }
950
951        // Retain the prior record chain so concurrent readers never observe freed nodes.
952        // Compose proper prunes when it can prove no readers exist; for now we keep
953        // the historical chain with tombstoned values to avoid use-after-free crashes
954        // under heavy UI load.
955        true
956    }
957}
958
959thread_local! {
960    static ACTIVE_UPDATES: RefCell<HashSet<ObjectId>> = RefCell::new(HashSet::default());
961    static PENDING_WRITES: RefCell<HashSet<ObjectId>> = RefCell::new(HashSet::default());
962}
963
964pub(crate) struct UpdateScope {
965    id: ObjectId,
966    finished: bool,
967}
968
969impl UpdateScope {
970    pub(crate) fn new(id: ObjectId) -> Self {
971        ACTIVE_UPDATES.with(|active| {
972            active.borrow_mut().insert(id);
973        });
974        PENDING_WRITES.with(|pending| {
975            pending.borrow_mut().remove(&id);
976        });
977        Self {
978            id,
979            finished: false,
980        }
981    }
982
983    pub(crate) fn finish(mut self) -> bool {
984        self.finished = true;
985        ACTIVE_UPDATES.with(|active| {
986            active.borrow_mut().remove(&self.id);
987        });
988        PENDING_WRITES.with(|pending| pending.borrow_mut().remove(&self.id))
989    }
990}
991
992impl Drop for UpdateScope {
993    fn drop(&mut self) {
994        if self.finished {
995            return;
996        }
997        ACTIVE_UPDATES.with(|active| {
998            active.borrow_mut().remove(&self.id);
999        });
1000        PENDING_WRITES.with(|pending| {
1001            pending.borrow_mut().remove(&self.id);
1002        });
1003    }
1004}
1005
1006fn mark_update_write(id: ObjectId) {
1007    ACTIVE_UPDATES.with(|active| {
1008        if active.borrow().contains(&id) {
1009            PENDING_WRITES.with(|pending| {
1010                pending.borrow_mut().insert(id);
1011            });
1012        }
1013    });
1014}
1015
1016impl<T: Clone + 'static> SnapshotMutableState<T> {
1017    /// Try to find a readable record, returning None if no valid record exists.
1018    fn try_readable_record(
1019        &self,
1020        snapshot_id: SnapshotId,
1021        invalid: &SnapshotIdSet,
1022    ) -> Option<Rc<StateRecord>> {
1023        self.readable_for(snapshot_id, invalid)
1024    }
1025}
1026
1027impl<T: Clone + 'static> StateObject for SnapshotMutableState<T> {
1028    fn object_id(&self) -> ObjectId {
1029        self.id
1030    }
1031
1032    fn first_record(&self) -> Rc<StateRecord> {
1033        self.head.clone_head()
1034    }
1035
1036    fn try_readable_record(
1037        &self,
1038        snapshot_id: SnapshotId,
1039        invalid: &SnapshotIdSet,
1040    ) -> Option<Rc<StateRecord>> {
1041        self.try_readable_record(snapshot_id, invalid)
1042    }
1043
1044    fn readable_record(&self, snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> Rc<StateRecord> {
1045        self.try_readable_record(snapshot_id, invalid)
1046            .unwrap_or_else(|| {
1047                panic!(
1048                    "SnapshotMutableState::readable_record returned null (state={:?}, snapshot_id={})",
1049                    self.id, snapshot_id
1050                )
1051            })
1052    }
1053
1054    fn prepend_state_record(&self, record: Rc<StateRecord>) {
1055        self.head.prepend(record);
1056    }
1057
1058    fn merge_records(
1059        &self,
1060        previous: Rc<StateRecord>,
1061        current: Rc<StateRecord>,
1062        applied: Rc<StateRecord>,
1063    ) -> Option<Rc<StateRecord>> {
1064        let Some(current_value) = current.try_with_value(|value: &T| value.clone()) else {
1065            log::error!(
1066                "SnapshotMutableState::merge_records current record value missing or wrong type (state {:?}, current_id={})",
1067                self.id,
1068                current.snapshot_id()
1069            );
1070            return None;
1071        };
1072        let Some(applied_value) = applied.try_with_value(|value: &T| value.clone()) else {
1073            log::error!(
1074                "SnapshotMutableState::merge_records applied record value missing or wrong type (state {:?}, applied_id={})",
1075                self.id,
1076                applied.snapshot_id()
1077            );
1078            return None;
1079        };
1080        if self.policy.equivalent(&current_value, &applied_value) {
1081            return Some(current);
1082        }
1083
1084        let Some(previous_value) = previous.try_with_value(|value: &T| value.clone()) else {
1085            log::error!(
1086                "SnapshotMutableState::merge_records previous record value missing or wrong type (state {:?}, previous_id={})",
1087                self.id,
1088                previous.snapshot_id()
1089            );
1090            return None;
1091        };
1092        let merged = self
1093            .policy
1094            .merge(&previous_value, &current_value, &applied_value)?;
1095
1096        Some(StateRecord::new(applied.snapshot_id(), merged, None))
1097    }
1098
1099    fn promote_record(&self, child_id: SnapshotId) -> Result<(), &'static str> {
1100        let head = self.first_record();
1101        let mut cursor = Some(head);
1102        while let Some(record) = cursor {
1103            if record.snapshot_id() == child_id {
1104                let Some(cloned) = record.try_with_value(|value: &T| value.clone()) else {
1105                    log::error!(
1106                        "SnapshotMutableState::promote_record child record value missing or wrong type (state {:?}, child_id={})",
1107                        self.id,
1108                        child_id
1109                    );
1110                    return Err("child record value missing or wrong type");
1111                };
1112                let new_id = allocate_record_id();
1113                let current_head = self.head.clone_head();
1114                let new_head = StateRecord::new(new_id, cloned, Some(current_head));
1115                self.head.replace(new_head);
1116                advance_global_snapshot(new_id);
1117                self.notify_applied();
1118                self.assert_chain_integrity("promote_record", Some(child_id));
1119                return Ok(());
1120            }
1121            cursor = record.next();
1122        }
1123        log::error!(
1124            "SnapshotMutableState::promote_record missing child record (state {:?}, child_id={})",
1125            self.id,
1126            child_id
1127        );
1128        Err("missing child record")
1129    }
1130
1131    fn commit_merged_record(&self, merged: Rc<StateRecord>) -> Result<SnapshotId, &'static str> {
1132        let Some(value) = merged.try_with_value(|value: &T| value.clone()) else {
1133            log::error!(
1134                "SnapshotMutableState::commit_merged_record merged record value missing or wrong type (state {:?}, merged_id={})",
1135                self.id,
1136                merged.snapshot_id()
1137            );
1138            return Err("merged record value missing or wrong type");
1139        };
1140        let new_id = allocate_record_id();
1141        let current_head = self.head.clone_head();
1142        let new_head = StateRecord::new(new_id, value, Some(current_head));
1143        self.head.replace(new_head);
1144        advance_global_snapshot(new_id);
1145        self.notify_applied();
1146        self.assert_chain_integrity("commit_merged_record", Some(new_id));
1147        Ok(new_id)
1148    }
1149
1150    fn overwrite_unused_records(&self) -> bool {
1151        overwrite_unused_records_locked::<T>(self)
1152    }
1153
1154    fn as_any(&self) -> &dyn Any {
1155        self
1156    }
1157}
1158
1159pub(crate) struct MutableStateInner<T: Clone + 'static> {
1160    pub(crate) state: Arc<SnapshotMutableState<T>>,
1161    pub(crate) watchers: RefCell<HashMap<ScopeId, RcWeak<RecomposeScopeInner>>>,
1162    runtime: RuntimeHandle,
1163    state_id: Cell<Option<StateId>>,
1164}
1165
1166fn shrink_watchers_if_sparse(watchers: &mut HashMap<ScopeId, RcWeak<RecomposeScopeInner>>) {
1167    let len = watchers.len();
1168    let capacity = watchers.capacity();
1169    if capacity > len.saturating_mul(4).max(32) {
1170        watchers.shrink_to_fit();
1171    }
1172}
1173
1174impl<T: Clone + 'static> MutableStateInner<T> {
1175    pub(crate) fn new_with_policy(
1176        value: T,
1177        runtime: RuntimeHandle,
1178        policy: Arc<dyn MutationPolicy<T>>,
1179    ) -> Self {
1180        Self {
1181            state: SnapshotMutableState::new_in_arc(value, policy),
1182            watchers: RefCell::new(HashMap::default()),
1183            runtime,
1184            state_id: Cell::new(None),
1185        }
1186    }
1187
1188    pub(crate) fn install_snapshot_observer(&self, state_id: StateId) {
1189        self.state_id.set(Some(state_id));
1190        let runtime_handle = self.runtime.clone();
1191        self.state.add_apply_observer(Box::new(move || {
1192            let runtime = runtime_handle.clone();
1193            runtime_handle.enqueue_ui_task(Box::new(move || {
1194                runtime.with_state_arena(|arena| {
1195                    let _ = arena.with_typed_opt::<T, _>(state_id, |inner| {
1196                        inner.invalidate_watchers();
1197                    });
1198                });
1199            }));
1200        }));
1201    }
1202
1203    fn with_value<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1204        let value = self.state.get();
1205        f(&value)
1206    }
1207
1208    fn register_scope(&self, scope: &RecomposeScope) -> bool {
1209        let mut watchers = self.watchers.borrow_mut();
1210        match watchers.get(&scope.id()) {
1211            Some(existing) if existing.upgrade().is_some() => false,
1212            _ => {
1213                watchers.insert(scope.id(), scope.downgrade());
1214                true
1215            }
1216        }
1217    }
1218
1219    pub(crate) fn unregister_scope(&self, scope_id: ScopeId) {
1220        let mut watchers = self.watchers.borrow_mut();
1221        watchers.remove(&scope_id);
1222        shrink_watchers_if_sparse(&mut watchers);
1223    }
1224
1225    fn state_id(&self) -> Option<StateId> {
1226        self.state_id.get()
1227    }
1228
1229    fn invalidate_watchers(&self) {
1230        let watchers: Vec<RecomposeScope> = {
1231            let mut watchers = self.watchers.borrow_mut();
1232            let mut live = Vec::with_capacity(watchers.len());
1233            watchers.retain(|_, scope| {
1234                if let Some(inner) = scope.upgrade() {
1235                    live.push(RecomposeScope { inner });
1236                    true
1237                } else {
1238                    false
1239                }
1240            });
1241            shrink_watchers_if_sparse(&mut watchers);
1242            live
1243        };
1244
1245        for watcher in watchers {
1246            debug_record_scope_invalidation::<T>(watcher.id(), self.state_id.get());
1247            if let Some(state_id) = self.state_id.get() {
1248                watcher.invalidate_from_state(state_id);
1249            } else {
1250                watcher.invalidate();
1251            }
1252        }
1253    }
1254}
1255
1256fn register_current_state_scope<T: Clone + 'static>(inner: &MutableStateInner<T>) {
1257    let Some(Some(scope)) =
1258        with_current_composer_opt(|composer| composer.current_state_invalidation_scope())
1259    else {
1260        return;
1261    };
1262    if inner.register_scope(&scope) {
1263        if let Some(state_id) = inner.state_id() {
1264            scope.record_state_subscription(state_id);
1265        }
1266    }
1267}
1268
1269/// Cheap copyable read-only view of a state cell.
1270pub struct State<T: Clone + 'static> {
1271    id: StateId,
1272    runtime_id: runtime::RuntimeId,
1273    _marker: PhantomData<fn() -> T>,
1274}
1275
1276/// Cheap copyable mutable view of a state cell.
1277///
1278/// Ownership lives elsewhere: a composition slot, an [`OwnedMutableState`], or
1279/// the runtime for states created with [`crate::mutableStateOf`] /
1280/// [`MutableState::with_runtime`].
1281pub struct MutableState<T: Clone + 'static> {
1282    id: StateId,
1283    runtime_id: runtime::RuntimeId,
1284    _marker: PhantomData<fn() -> T>,
1285}
1286
1287/// Owning state handle for reclaimable state cells.
1288#[derive(Clone)]
1289pub struct OwnedMutableState<T: Clone + 'static> {
1290    state: MutableState<T>,
1291    _lease: Rc<runtime::StateHandleLease>,
1292    _marker: PhantomData<fn() -> T>,
1293}
1294
1295impl<T: Clone + 'static> PartialEq for State<T> {
1296    fn eq(&self, other: &Self) -> bool {
1297        self.state_id() == other.state_id() && self.runtime_id() == other.runtime_id()
1298    }
1299}
1300
1301impl<T: Clone + 'static> Eq for State<T> {}
1302
1303impl<T: Clone + 'static> PartialEq for MutableState<T> {
1304    fn eq(&self, other: &Self) -> bool {
1305        self.state_id() == other.state_id() && self.runtime_id() == other.runtime_id()
1306    }
1307}
1308
1309impl<T: Clone + 'static> Eq for MutableState<T> {}
1310
1311impl<T: Clone + 'static> Copy for State<T> {}
1312
1313impl<T: Clone + 'static> Clone for State<T> {
1314    fn clone(&self) -> Self {
1315        *self
1316    }
1317}
1318
1319impl<T: Clone + 'static> Copy for MutableState<T> {}
1320
1321impl<T: Clone + 'static> Clone for MutableState<T> {
1322    fn clone(&self) -> Self {
1323        *self
1324    }
1325}
1326
1327impl<T: Clone + 'static> State<T> {
1328    fn state_id(&self) -> StateId {
1329        self.id
1330    }
1331
1332    fn runtime_id(&self) -> runtime::RuntimeId {
1333        self.runtime_id
1334    }
1335
1336    fn runtime_handle(&self) -> RuntimeHandle {
1337        runtime::runtime_handle_by_id(self.runtime_id())
1338            .unwrap_or_else(|| panic!("runtime {:?} dropped", self.runtime_id()))
1339    }
1340
1341    fn runtime_handle_opt(&self) -> Option<RuntimeHandle> {
1342        runtime::runtime_handle_by_id(self.runtime_id())
1343    }
1344
1345    fn with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> R {
1346        self.runtime_handle()
1347            .with_state_arena(|arena| arena.with_typed::<T, R>(self.state_id(), f))
1348    }
1349
1350    fn try_with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> Option<R> {
1351        self.runtime_handle_opt()?
1352            .try_with_state_arena(|arena| arena.with_typed_opt::<T, R>(self.state_id(), f))?
1353    }
1354
1355    fn subscribe_current_scope(&self) {
1356        self.with_inner(register_current_state_scope::<T>);
1357    }
1358
1359    pub fn is_alive(&self) -> bool {
1360        self.try_with_inner(|_| ()).is_some()
1361    }
1362
1363    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
1364        self.try_with_inner(|inner| inner.state.try_with_value(f))?
1365    }
1366
1367    pub fn try_value(&self) -> Option<T> {
1368        self.try_with_inner(|inner| inner.state.try_get())?
1369    }
1370
1371    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1372        self.subscribe_current_scope();
1373        self.with_inner(|inner| inner.with_value(f))
1374    }
1375
1376    pub fn value(&self) -> T {
1377        self.subscribe_current_scope();
1378        self.with_inner(|inner| inner.state.get())
1379    }
1380
1381    pub fn get(&self) -> T {
1382        self.value()
1383    }
1384}
1385
1386impl<T: Clone + 'static> MutableState<T> {
1387    pub fn with_runtime(value: T, runtime: RuntimeHandle) -> Self {
1388        runtime.alloc_persistent_state(value)
1389    }
1390
1391    fn from_parts(id: StateId, runtime_id: runtime::RuntimeId) -> Self {
1392        Self {
1393            id,
1394            runtime_id,
1395            _marker: PhantomData,
1396        }
1397    }
1398
1399    pub(crate) fn from_lease(lease: &Rc<runtime::StateHandleLease>) -> Self {
1400        Self::from_parts(lease.id(), lease.runtime().id())
1401    }
1402
1403    fn state_id(&self) -> StateId {
1404        self.id
1405    }
1406
1407    fn runtime_id(&self) -> runtime::RuntimeId {
1408        self.runtime_id
1409    }
1410
1411    fn runtime_handle(&self) -> RuntimeHandle {
1412        runtime::runtime_handle_by_id(self.runtime_id())
1413            .unwrap_or_else(|| panic!("runtime {:?} dropped", self.runtime_id()))
1414    }
1415
1416    fn runtime_handle_opt(&self) -> Option<RuntimeHandle> {
1417        runtime::runtime_handle_by_id(self.runtime_id())
1418    }
1419
1420    fn with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> R {
1421        self.runtime_handle()
1422            .with_state_arena(|arena| arena.with_typed::<T, R>(self.state_id(), f))
1423    }
1424
1425    fn try_with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> Option<R> {
1426        self.runtime_handle_opt()?
1427            .try_with_state_arena(|arena| arena.with_typed_opt::<T, R>(self.state_id(), f))?
1428    }
1429
1430    pub fn is_alive(&self) -> bool {
1431        self.try_with_inner(|_| ()).is_some()
1432    }
1433
1434    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
1435        self.try_with_inner(|inner| inner.state.try_with_value(f))?
1436    }
1437
1438    pub fn try_value(&self) -> Option<T> {
1439        self.try_with_inner(|inner| inner.state.try_get())?
1440    }
1441
1442    pub fn as_state(&self) -> State<T> {
1443        State {
1444            id: self.id,
1445            runtime_id: self.runtime_id,
1446            _marker: PhantomData,
1447        }
1448    }
1449
1450    pub fn try_retain(&self) -> Option<OwnedMutableState<T>> {
1451        let lease = self
1452            .runtime_handle_opt()?
1453            .retain_state_lease(self.state_id())?;
1454        Some(OwnedMutableState {
1455            state: *self,
1456            _lease: lease,
1457            _marker: PhantomData,
1458        })
1459    }
1460
1461    pub fn retain(&self) -> OwnedMutableState<T> {
1462        self.try_retain()
1463            .unwrap_or_else(|| panic!("state {:?} is no longer alive", self.state_id()))
1464    }
1465
1466    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1467        self.subscribe_current_scope();
1468        self.with_inner(|inner| inner.with_value(f))
1469    }
1470
1471    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
1472        let runtime = self.runtime_handle();
1473        runtime.assert_ui_thread();
1474        runtime.with_state_arena(|arena| {
1475            arena.with_typed::<T, R>(self.state_id(), |inner| {
1476                let mut value = inner.state.get();
1477                let tracker = UpdateScope::new(inner.state.id());
1478                let result = f(&mut value);
1479                let wrote_elsewhere = tracker.finish();
1480                if !wrote_elsewhere && inner.state.set(value) {
1481                    inner.invalidate_watchers();
1482                }
1483                result
1484            })
1485        })
1486    }
1487
1488    pub fn replace(&self, value: T) {
1489        let Some(runtime) = self.runtime_handle_opt() else {
1490            log::debug!(
1491                "MutableState::replace skipped: runtime {:?} dropped",
1492                self.runtime_id()
1493            );
1494            return;
1495        };
1496        runtime.assert_ui_thread();
1497        let replaced = runtime
1498            .try_with_state_arena(|arena| {
1499                arena.with_typed_opt::<T, ()>(self.state_id(), |inner| {
1500                    if inner.state.set(value) {
1501                        inner.invalidate_watchers();
1502                    }
1503                })
1504            })
1505            .flatten();
1506        if replaced.is_none() {
1507            log::debug!(
1508                "MutableState::replace skipped: state cell released (slot={}, gen={})",
1509                self.state_id().slot(),
1510                self.state_id().generation(),
1511            );
1512        }
1513    }
1514
1515    pub fn set_value(&self, value: T) {
1516        self.replace(value);
1517    }
1518
1519    pub fn set(&self, value: T) {
1520        self.replace(value);
1521    }
1522
1523    pub fn value(&self) -> T {
1524        self.subscribe_current_scope();
1525        self.with_inner(|inner| inner.state.get())
1526    }
1527
1528    pub fn get(&self) -> T {
1529        self.value()
1530    }
1531
1532    pub fn get_non_reactive(&self) -> T {
1533        self.with_inner(|inner| inner.state.get())
1534    }
1535
1536    #[doc(hidden)]
1537    pub fn runtime_state_id(&self) -> StateId {
1538        self.state_id()
1539    }
1540
1541    #[doc(hidden)]
1542    pub fn subscribe_current_scope_only(&self) {
1543        self.subscribe_current_scope();
1544    }
1545
1546    fn subscribe_current_scope(&self) {
1547        self.with_inner(register_current_state_scope::<T>);
1548    }
1549
1550    #[cfg(test)]
1551    pub(crate) fn watcher_count(&self) -> usize {
1552        self.with_inner(|inner| inner.watchers.borrow().len())
1553    }
1554
1555    #[cfg(test)]
1556    pub(crate) fn watcher_capacity(&self) -> usize {
1557        self.with_inner(|inner| inner.watchers.borrow().capacity())
1558    }
1559
1560    #[cfg(test)]
1561    pub(crate) fn state_id_for_test(&self) -> StateId {
1562        self.state_id()
1563    }
1564
1565    #[cfg(test)]
1566    pub(crate) fn subscribe_scope_for_test(&self, scope: &RecomposeScope) {
1567        self.as_state().subscribe_scope_for_test(scope);
1568    }
1569}
1570
1571impl<T: Clone + 'static> OwnedMutableState<T> {
1572    pub fn with_runtime(value: T, runtime: RuntimeHandle) -> Self {
1573        let lease = runtime.alloc_state(value);
1574        Self {
1575            state: MutableState::from_lease(&lease),
1576            _lease: lease,
1577            _marker: PhantomData,
1578        }
1579    }
1580
1581    pub(crate) fn with_runtime_and_policy(
1582        value: T,
1583        runtime: RuntimeHandle,
1584        policy: Arc<dyn MutationPolicy<T>>,
1585    ) -> Self {
1586        let lease = runtime.alloc_state_with_policy(value, policy);
1587        Self {
1588            state: MutableState::from_lease(&lease),
1589            _lease: lease,
1590            _marker: PhantomData,
1591        }
1592    }
1593
1594    pub fn handle(&self) -> MutableState<T> {
1595        self.state
1596    }
1597
1598    pub fn as_state(&self) -> State<T> {
1599        self.state.as_state()
1600    }
1601}
1602
1603impl<T: Clone + 'static> Deref for OwnedMutableState<T> {
1604    type Target = MutableState<T>;
1605
1606    fn deref(&self) -> &Self::Target {
1607        &self.state
1608    }
1609}
1610
1611#[cfg(test)]
1612impl<T: Clone + 'static> State<T> {
1613    pub(crate) fn subscribe_scope_for_test(&self, scope: &RecomposeScope) {
1614        self.with_inner(|inner| {
1615            if inner.register_scope(scope) {
1616                if let Some(state_id) = inner.state_id() {
1617                    scope.record_state_subscription(state_id);
1618                }
1619            }
1620        });
1621    }
1622}
1623
1624impl<T: fmt::Debug + Clone + 'static> fmt::Debug for MutableState<T> {
1625    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1626        if let Some(value) = self.try_value() {
1627            f.debug_struct("MutableState")
1628                .field("value", &value)
1629                .finish()
1630        } else {
1631            f.write_str("MutableState { value: <unavailable> }")
1632        }
1633    }
1634}
1635
1636#[derive(Clone)]
1637pub struct SnapshotStateList<T: Clone + 'static> {
1638    state: OwnedMutableState<Vec<T>>,
1639}
1640
1641impl<T: Clone + 'static> SnapshotStateList<T> {
1642    pub fn with_runtime<I>(values: I, runtime: RuntimeHandle) -> Self
1643    where
1644        I: IntoIterator<Item = T>,
1645    {
1646        let initial: Vec<T> = values.into_iter().collect();
1647        Self {
1648            state: OwnedMutableState::with_runtime(initial, runtime),
1649        }
1650    }
1651
1652    pub fn as_state(&self) -> State<Vec<T>> {
1653        self.state.as_state()
1654    }
1655
1656    pub fn as_mutable_state(&self) -> MutableState<Vec<T>> {
1657        self.state.handle()
1658    }
1659
1660    pub fn len(&self) -> usize {
1661        self.state.with(|values| values.len())
1662    }
1663
1664    pub fn is_empty(&self) -> bool {
1665        self.len() == 0
1666    }
1667
1668    pub fn to_vec(&self) -> Vec<T> {
1669        self.state.with(|values| values.clone())
1670    }
1671
1672    pub fn iter(&self) -> Vec<T> {
1673        self.to_vec()
1674    }
1675
1676    pub fn get(&self, index: usize) -> T {
1677        self.state.with(|values| values[index].clone())
1678    }
1679
1680    pub fn get_opt(&self, index: usize) -> Option<T> {
1681        self.state.with(|values| values.get(index).cloned())
1682    }
1683
1684    pub fn first(&self) -> Option<T> {
1685        self.get_opt(0)
1686    }
1687
1688    pub fn last(&self) -> Option<T> {
1689        self.state.with(|values| values.last().cloned())
1690    }
1691
1692    pub fn push(&self, value: T) {
1693        self.state.update(|values| values.push(value));
1694    }
1695
1696    pub fn extend<I>(&self, iter: I)
1697    where
1698        I: IntoIterator<Item = T>,
1699    {
1700        self.state.update(|values| values.extend(iter));
1701    }
1702
1703    pub fn insert(&self, index: usize, value: T) {
1704        self.state.update(|values| values.insert(index, value));
1705    }
1706
1707    pub fn set(&self, index: usize, value: T) -> T {
1708        self.state
1709            .update(|values| std::mem::replace(&mut values[index], value))
1710    }
1711
1712    pub fn remove(&self, index: usize) -> T {
1713        self.state.update(|values| values.remove(index))
1714    }
1715
1716    pub fn pop(&self) -> Option<T> {
1717        self.state.update(|values| values.pop())
1718    }
1719
1720    pub fn clear(&self) {
1721        self.state.replace(Vec::new());
1722    }
1723
1724    pub fn retain<F>(&self, mut predicate: F)
1725    where
1726        F: FnMut(&T) -> bool,
1727    {
1728        self.state
1729            .update(|values| values.retain(|value| predicate(value)));
1730    }
1731
1732    pub fn replace_with<I>(&self, iter: I)
1733    where
1734        I: IntoIterator<Item = T>,
1735    {
1736        self.state.replace(iter.into_iter().collect());
1737    }
1738}
1739
1740impl<T: fmt::Debug + Clone + 'static> fmt::Debug for SnapshotStateList<T> {
1741    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1742        let contents = self.to_vec();
1743        f.debug_struct("SnapshotStateList")
1744            .field("values", &contents)
1745            .finish()
1746    }
1747}
1748
1749#[derive(Clone)]
1750pub struct SnapshotStateMap<K, V>
1751where
1752    K: Clone + Eq + Hash + 'static,
1753    V: Clone + 'static,
1754{
1755    state: OwnedMutableState<HashMap<K, V>>,
1756}
1757
1758impl<K, V> SnapshotStateMap<K, V>
1759where
1760    K: Clone + Eq + Hash + 'static,
1761    V: Clone + 'static,
1762{
1763    pub fn with_runtime<I>(pairs: I, runtime: RuntimeHandle) -> Self
1764    where
1765        I: IntoIterator<Item = (K, V)>,
1766    {
1767        let map: HashMap<K, V> = pairs.into_iter().collect();
1768        Self {
1769            state: OwnedMutableState::with_runtime(map, runtime),
1770        }
1771    }
1772
1773    pub fn as_state(&self) -> State<HashMap<K, V>> {
1774        self.state.as_state()
1775    }
1776
1777    pub fn as_mutable_state(&self) -> MutableState<HashMap<K, V>> {
1778        self.state.handle()
1779    }
1780
1781    pub fn len(&self) -> usize {
1782        self.state.with(|map| map.len())
1783    }
1784
1785    pub fn is_empty(&self) -> bool {
1786        self.state.with(|map| map.is_empty())
1787    }
1788
1789    pub fn contains_key(&self, key: &K) -> bool {
1790        self.state.with(|map| map.contains_key(key))
1791    }
1792
1793    pub fn get(&self, key: &K) -> Option<V> {
1794        self.state.with(|map| map.get(key).cloned())
1795    }
1796
1797    pub fn to_hash_map(&self) -> HashMap<K, V> {
1798        self.state.with(|map| map.clone())
1799    }
1800
1801    pub fn insert(&self, key: K, value: V) -> Option<V> {
1802        self.state.update(|map| map.insert(key, value))
1803    }
1804
1805    pub fn extend<I>(&self, iter: I)
1806    where
1807        I: IntoIterator<Item = (K, V)>,
1808    {
1809        self.state.update(|map| map.extend(iter));
1810    }
1811
1812    pub fn remove(&self, key: &K) -> Option<V> {
1813        self.state.update(|map| map.remove(key))
1814    }
1815
1816    pub fn clear(&self) {
1817        self.state.replace(HashMap::default());
1818    }
1819
1820    pub fn retain<F>(&self, mut predicate: F)
1821    where
1822        F: FnMut(&K, &mut V) -> bool,
1823    {
1824        self.state.update(|map| map.retain(|k, v| predicate(k, v)));
1825    }
1826}
1827
1828impl<K, V> fmt::Debug for SnapshotStateMap<K, V>
1829where
1830    K: Clone + Eq + Hash + fmt::Debug + 'static,
1831    V: Clone + fmt::Debug + 'static,
1832{
1833    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1834        let contents = self.to_hash_map();
1835        f.debug_struct("SnapshotStateMap")
1836            .field("entries", &contents)
1837            .finish()
1838    }
1839}
1840
1841pub(crate) struct DerivedState<T: Clone + 'static> {
1842    compute: Rc<dyn Fn() -> T>,
1843    pub(crate) state: OwnedMutableState<T>,
1844}
1845
1846impl<T: Clone + 'static> DerivedState<T> {
1847    pub(crate) fn new(runtime: RuntimeHandle, compute: Rc<dyn Fn() -> T>) -> Self {
1848        let initial = compute();
1849        Self {
1850            compute,
1851            state: OwnedMutableState::with_runtime(initial, runtime),
1852        }
1853    }
1854
1855    pub(crate) fn set_compute(&mut self, compute: Rc<dyn Fn() -> T>) {
1856        self.compute = compute;
1857    }
1858
1859    pub(crate) fn recompute(&self) {
1860        let value = (self.compute)();
1861        self.state.set_value(value);
1862    }
1863}
1864
1865impl<T: fmt::Debug + Clone + 'static> fmt::Debug for State<T> {
1866    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1867        if let Some(value) = self.try_value() {
1868            f.debug_struct("State").field("value", &value).finish()
1869        } else {
1870            f.write_str("State { value: <unavailable> }")
1871        }
1872    }
1873}
1874
1875#[cfg(test)]
1876mod tests {
1877    use super::*;
1878
1879    /// Helper to create a chain of records for testing
1880    fn create_record_chain(ids: &[SnapshotId]) -> Rc<StateRecord> {
1881        let mut head: Option<Rc<StateRecord>> = None;
1882
1883        // Build chain in reverse order (last ID becomes the tail)
1884        for &id in ids.iter().rev() {
1885            head = Some(StateRecord::new(id, 0i32, head));
1886        }
1887
1888        head.expect("create_record_chain called with empty ids")
1889    }
1890
1891    struct ManualState {
1892        head: Rc<StateRecord>,
1893    }
1894
1895    impl ManualState {
1896        fn new(head: Rc<StateRecord>) -> Self {
1897            Self { head }
1898        }
1899    }
1900
1901    impl StateObject for ManualState {
1902        fn object_id(&self) -> ObjectId {
1903            ObjectId(999)
1904        }
1905
1906        fn first_record(&self) -> Rc<StateRecord> {
1907            Rc::clone(&self.head)
1908        }
1909
1910        fn try_readable_record(&self, _: SnapshotId, _: &SnapshotIdSet) -> Option<Rc<StateRecord>> {
1911            Some(Rc::clone(&self.head))
1912        }
1913
1914        fn readable_record(&self, _: SnapshotId, _: &SnapshotIdSet) -> Rc<StateRecord> {
1915            Rc::clone(&self.head)
1916        }
1917
1918        fn prepend_state_record(&self, _: Rc<StateRecord>) {}
1919
1920        fn promote_record(&self, _: SnapshotId) -> Result<(), &'static str> {
1921            Ok(())
1922        }
1923
1924        fn as_any(&self) -> &dyn Any {
1925            self
1926        }
1927    }
1928
1929    fn poison_mutex<T>(mutex: &Mutex<T>) {
1930        let poison_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1931            let _guard = mutex
1932                .lock()
1933                .unwrap_or_else(|poisoned| poisoned.into_inner());
1934            panic!("poison snapshot state mutex for recovery test");
1935        }));
1936
1937        assert!(poison_result.is_err());
1938    }
1939
1940    #[test]
1941    fn snapshot_mutable_state_recovers_poisoned_weak_self_lock() {
1942        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
1943
1944        poison_mutex(&state.weak_self);
1945
1946        assert_eq!(state.get(), 100);
1947        assert!(state.set(101));
1948        assert_eq!(state.get(), 101);
1949    }
1950
1951    #[test]
1952    fn snapshot_mutable_state_recovers_poisoned_apply_observer_lock() {
1953        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
1954        let calls = Rc::new(Cell::new(0usize));
1955        let observed_calls = Rc::clone(&calls);
1956
1957        poison_mutex(&state.apply_observers);
1958
1959        state.add_apply_observer(Box::new(move || {
1960            observed_calls.set(observed_calls.get() + 1);
1961        }));
1962        state.notify_applied();
1963
1964        assert_eq!(calls.get(), 1);
1965    }
1966
1967    #[test]
1968    fn snapshot_mutable_state_promote_missing_record_returns_error() {
1969        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
1970        let missing_snapshot = usize::MAX - 17;
1971
1972        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1973            StateObject::promote_record(&*state, missing_snapshot)
1974        }));
1975
1976        assert!(
1977            matches!(result, Ok(Err("missing child record"))),
1978            "missing child record should be reported through Result, got {result:?}"
1979        );
1980    }
1981
1982    #[test]
1983    fn snapshot_mutable_state_promote_wrong_record_type_returns_error() {
1984        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
1985        let child_snapshot = usize::MAX - 31;
1986        let wrong_record = StateRecord::new(child_snapshot, "wrong type", None);
1987        StateObject::prepend_state_record(&*state, wrong_record);
1988
1989        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1990            StateObject::promote_record(&*state, child_snapshot)
1991        }));
1992
1993        assert!(
1994            matches!(result, Ok(Err("child record value missing or wrong type"))),
1995            "wrong child record type should be reported through Result, got {result:?}"
1996        );
1997    }
1998
1999    #[test]
2000    fn snapshot_mutable_state_commit_wrong_record_type_returns_error() {
2001        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2002        let merged = StateRecord::new(usize::MAX - 43, "wrong type", None);
2003
2004        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2005            StateObject::commit_merged_record(&*state, merged)
2006        }));
2007
2008        assert!(
2009            matches!(result, Ok(Err("merged record value missing or wrong type"))),
2010            "wrong merged record type should be reported through Result, got {result:?}"
2011        );
2012    }
2013
2014    #[test]
2015    fn snapshot_mutable_state_merge_wrong_record_type_returns_none() {
2016        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2017        let previous = StateRecord::new(usize::MAX - 51, 1i32, None);
2018        let current = StateRecord::new(usize::MAX - 52, "wrong type", None);
2019        let applied = StateRecord::new(usize::MAX - 53, 2i32, None);
2020
2021        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2022            StateObject::merge_records(&*state, previous, current, applied)
2023        }));
2024
2025        match result {
2026            Ok(None) => {}
2027            Ok(Some(_)) => panic!("wrong merge record type unexpectedly produced a merged record"),
2028            Err(_) => panic!("wrong merge record type should not panic"),
2029        }
2030    }
2031
2032    #[test]
2033    fn test_used_locked_finds_invalid_snapshot() {
2034        // Create a chain with an INVALID_SNAPSHOT record
2035        let tail = StateRecord::new(PREEXISTING_SNAPSHOT_ID, 0i32, None);
2036        let invalid_rec = StateRecord::new(INVALID_SNAPSHOT_ID, 0i32, Some(tail));
2037        let head = StateRecord::new(10, 0i32, Some(invalid_rec.clone()));
2038
2039        let result = used_locked(&head);
2040        assert!(result.is_some());
2041        assert_eq!(result.unwrap().snapshot_id(), INVALID_SNAPSHOT_ID);
2042    }
2043
2044    #[test]
2045    fn test_used_locked_finds_obscured_record() {
2046        // Reset pinning state for clean test
2047        crate::snapshot_pinning::reset_pinning_table();
2048
2049        // Pin a high snapshot to set a known reuse limit
2050        // This ensures records 2 and 5 are both below (reuse_limit = 10 - 1 = 9)
2051        let pin_handle = crate::snapshot_pinning::track_pinning(10, &SnapshotIdSet::EMPTY);
2052
2053        // Create a chain with two old records below the reuse limit
2054        let oldest = StateRecord::new(2, 0i32, None);
2055        let newer = StateRecord::new(5, 0i32, Some(oldest.clone()));
2056        let head = StateRecord::new(100, 0i32, Some(newer));
2057
2058        let result = used_locked(&head);
2059
2060        // Should find the older of the two records below reuse limit
2061        assert!(result.is_some());
2062        let reused = result.unwrap();
2063        assert_eq!(
2064            reused.snapshot_id(),
2065            2,
2066            "Should return the oldest obscured record"
2067        );
2068
2069        // Clean up
2070        crate::snapshot_pinning::release_pinning(pin_handle);
2071    }
2072
2073    #[test]
2074    fn test_used_locked_no_reusable_record() {
2075        // Reset pinning state
2076        crate::snapshot_pinning::reset_pinning_table();
2077
2078        // Create a chain where all records are recent (above reuse limit)
2079        // Use very high IDs to ensure they're above any reuse limit
2080        let high_id = allocate_record_id() + 1000;
2081        let head = create_record_chain(&[high_id, high_id + 1, high_id + 2]);
2082
2083        let result = used_locked(&head);
2084        assert!(
2085            result.is_none(),
2086            "Should find no reusable records when all are recent"
2087        );
2088    }
2089
2090    #[test]
2091    fn test_used_locked_single_old_record() {
2092        // Reset pinning state
2093        crate::snapshot_pinning::reset_pinning_table();
2094
2095        // Create a chain with only one old record (should not be reused)
2096        let old = StateRecord::new(2, 0i32, None);
2097        let head = StateRecord::new(100, 0i32, Some(old));
2098
2099        let result = used_locked(&head);
2100        // With only ONE record below reuse limit, it's still valid and should not be reused
2101        assert!(result.is_none(), "Single old record should not be reused");
2102    }
2103
2104    #[test]
2105    fn test_readable_record_for_preexisting() {
2106        let head = create_record_chain(&[PREEXISTING_SNAPSHOT_ID]);
2107        let invalid = SnapshotIdSet::EMPTY;
2108
2109        let result = readable_record_for(&head, 10, &invalid);
2110        assert!(result.is_some());
2111        assert_eq!(result.unwrap().snapshot_id(), PREEXISTING_SNAPSHOT_ID);
2112    }
2113
2114    #[test]
2115    fn test_readable_record_for_picks_highest_valid() {
2116        let head = create_record_chain(&[10, 5, PREEXISTING_SNAPSHOT_ID]);
2117        let invalid = SnapshotIdSet::EMPTY;
2118
2119        // Reading at snapshot 10 should return record 10
2120        let result = readable_record_for(&head, 10, &invalid);
2121        assert!(result.is_some());
2122        assert_eq!(result.unwrap().snapshot_id(), 10);
2123
2124        // Reading at snapshot 7 should skip record 10 and return record 5
2125        let result = readable_record_for(&head, 7, &invalid);
2126        assert!(result.is_some());
2127        assert_eq!(result.unwrap().snapshot_id(), 5);
2128    }
2129
2130    #[test]
2131    fn test_new_overwritable_record_locked_reuses_invalid() {
2132        // Create a state with an INVALID record in the chain
2133        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2134
2135        // Manually insert an INVALID record into the chain
2136        let current_head = state.first_record();
2137        let invalid_rec = StateRecord::new(INVALID_SNAPSHOT_ID, 0i32, current_head.next());
2138        current_head.set_next(Some(invalid_rec.clone()));
2139
2140        let result = new_overwritable_record_locked(&*state);
2141
2142        // Should reuse the INVALID record
2143        assert!(Rc::ptr_eq(&result, &invalid_rec));
2144        assert_eq!(result.snapshot_id(), SNAPSHOT_ID_MAX);
2145    }
2146
2147    #[test]
2148    fn test_new_overwritable_record_locked_creates_new() {
2149        crate::snapshot_pinning::reset_pinning_table();
2150
2151        // Pin snapshot 1 to prevent PREEXISTING (id=1) from being reusable
2152        // This ensures the reuse limit is above 1, so PREEXISTING won't be obscured
2153        let _pin_handle = crate::snapshot_pinning::track_pinning(1, &SnapshotIdSet::EMPTY);
2154
2155        // Create a state with all recent records (no reusable ones)
2156        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2157        let old_head = state.first_record();
2158
2159        let result = new_overwritable_record_locked(&*state);
2160
2161        // Should create a new record
2162        assert_eq!(result.snapshot_id(), SNAPSHOT_ID_MAX);
2163
2164        // Should be prepended to the chain (becomes new head)
2165        let new_head = state.first_record();
2166        assert!(
2167            Rc::ptr_eq(&new_head, &result),
2168            "new_head ({:p}) should equal result ({:p})",
2169            Rc::as_ptr(&new_head),
2170            Rc::as_ptr(&result)
2171        );
2172
2173        // The new record should point to the old head
2174        assert!(result.next().is_some());
2175        assert!(Rc::ptr_eq(&result.next().unwrap(), &old_head));
2176    }
2177
2178    #[test]
2179    fn test_writable_record_reuses_invalid_record() {
2180        crate::snapshot_pinning::reset_pinning_table();
2181
2182        let state = SnapshotMutableState::new_in_arc(7i32, Arc::new(NeverEqual));
2183
2184        // Inject an INVALID record that should be reused on next write.
2185        let head = state.first_record();
2186        let invalid = StateRecord::new(INVALID_SNAPSHOT_ID, 0i32, head.next());
2187        head.set_next(Some(invalid.clone()));
2188
2189        let snapshot_id = allocate_record_id();
2190        let result = state.writable_record(snapshot_id, &SnapshotIdSet::EMPTY);
2191
2192        assert!(
2193            Rc::ptr_eq(&result, &invalid),
2194            "Expected writable_record to reuse the INVALID record"
2195        );
2196        assert_eq!(result.snapshot_id(), snapshot_id);
2197        result.with_value(|value: &i32| {
2198            assert_eq!(*value, 7, "Reused record should copy the readable value");
2199        });
2200        assert!(!result.is_tombstone());
2201    }
2202
2203    #[test]
2204    fn test_writable_record_creates_new_when_reuse_disallowed() {
2205        crate::snapshot_pinning::reset_pinning_table();
2206        let pin = crate::snapshot_pinning::track_pinning(1, &SnapshotIdSet::EMPTY);
2207
2208        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2209        let original_head = state.first_record();
2210        let preexisting = original_head
2211            .next()
2212            .expect("preexisting record should exist for newly created state");
2213
2214        let snapshot_id = allocate_record_id();
2215        let result = state.writable_record(snapshot_id, &SnapshotIdSet::EMPTY);
2216
2217        assert!(
2218            !Rc::ptr_eq(&result, &original_head),
2219            "Should not reuse the current head when reuse is disallowed"
2220        );
2221        assert!(
2222            !Rc::ptr_eq(&result, &preexisting),
2223            "Should not reuse the PREEXISTING record"
2224        );
2225        assert_eq!(result.snapshot_id(), snapshot_id);
2226        result.with_value(|value: &i32| assert_eq!(*value, 42));
2227
2228        let new_head = state.first_record();
2229        assert!(
2230            Rc::ptr_eq(&new_head, &result),
2231            "Newly created record should become the head of the chain"
2232        );
2233
2234        crate::snapshot_pinning::release_pinning(pin);
2235    }
2236
2237    #[test]
2238    fn test_state_record_clear_for_reuse() {
2239        let record = StateRecord::new(10, 42i32, None);
2240
2241        // Verify value exists before clearing
2242        record.with_value(|val: &i32| {
2243            assert_eq!(*val, 42);
2244        });
2245
2246        // Clear the record for reuse
2247        record.clear_for_reuse();
2248
2249        // Value should be cleared (will panic if we try to access it)
2250        // Just verify snapshot_id is unchanged
2251        assert_eq!(record.snapshot_id(), 10);
2252    }
2253
2254    #[test]
2255    fn test_overwrite_unused_records_no_old_records() {
2256        crate::snapshot_pinning::reset_pinning_table();
2257
2258        // Create state first to establish snapshot IDs
2259        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2260
2261        // Pin snapshot 1 so reuse limit is 1, making both initial records (1 and 2) above it
2262        // This ensures PREEXISTING won't be overwritten
2263        let _pin = crate::snapshot_pinning::track_pinning(1, &SnapshotIdSet::EMPTY);
2264
2265        let should_retain = state.overwrite_unused_records();
2266
2267        // With both records above/at reuse limit, we have 2 retained
2268        assert!(
2269            should_retain,
2270            "Should retain multiple records when none are old enough"
2271        );
2272
2273        // No records should be marked as INVALID
2274        let mut cursor = Some(state.first_record());
2275        while let Some(record) = cursor {
2276            assert_ne!(record.snapshot_id(), INVALID_SNAPSHOT_ID);
2277            cursor = record.next();
2278        }
2279    }
2280
2281    #[test]
2282    fn test_overwrite_unused_records_basic_cleanup() {
2283        // Test that old records get marked invalid when newer ones exist
2284        crate::snapshot_pinning::reset_pinning_table();
2285
2286        // Create simple manual chain to avoid snapshot ID allocation complexity
2287        let rec1 = StateRecord::new(100, 1i32, None);
2288        let rec2 = StateRecord::new(200, 2i32, Some(rec1.clone()));
2289        let rec3 = StateRecord::new(300, 3i32, Some(rec2.clone()));
2290
2291        // Mock state object for testing
2292        struct TestState {
2293            head: Rc<StateRecord>,
2294        }
2295        impl StateObject for TestState {
2296            fn object_id(&self) -> ObjectId {
2297                ObjectId(999)
2298            }
2299            fn first_record(&self) -> Rc<StateRecord> {
2300                Rc::clone(&self.head)
2301            }
2302            fn try_readable_record(
2303                &self,
2304                _: SnapshotId,
2305                _: &SnapshotIdSet,
2306            ) -> Option<Rc<StateRecord>> {
2307                Some(Rc::clone(&self.head))
2308            }
2309            fn readable_record(&self, _: SnapshotId, _: &SnapshotIdSet) -> Rc<StateRecord> {
2310                Rc::clone(&self.head)
2311            }
2312            fn prepend_state_record(&self, _: Rc<StateRecord>) {}
2313            fn promote_record(&self, _: SnapshotId) -> Result<(), &'static str> {
2314                Ok(())
2315            }
2316            fn as_any(&self) -> &dyn Any {
2317                self
2318            }
2319        }
2320
2321        let test_state = TestState { head: rec3.clone() };
2322
2323        // Pin at 1000 so all three records (100, 200, 300) are below reuse limit
2324        let _pin = crate::snapshot_pinning::track_pinning(1000, &SnapshotIdSet::EMPTY);
2325
2326        let result = overwrite_unused_records_locked::<i32>(&test_state);
2327
2328        // Should keep highest (300), mark others invalid
2329        assert_eq!(rec3.snapshot_id(), 300);
2330        assert_eq!(rec2.snapshot_id(), INVALID_SNAPSHOT_ID);
2331        assert_eq!(rec1.snapshot_id(), INVALID_SNAPSHOT_ID);
2332
2333        // Only one record retained (300), so should return false
2334        assert!(!result);
2335    }
2336
2337    #[test]
2338    fn test_overwrite_unused_records_single_record_only() {
2339        crate::snapshot_pinning::reset_pinning_table();
2340
2341        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2342
2343        // Remove the PREEXISTING record by setting next to None
2344        let head = state.first_record();
2345        head.set_next(None);
2346
2347        let should_retain = state.overwrite_unused_records();
2348
2349        // With only one record, should return false
2350        assert!(!should_retain, "Single record should return false");
2351    }
2352
2353    #[test]
2354    fn snapshot_state_try_get_reports_missing_visible_record_without_panicking() {
2355        crate::snapshot_pinning::reset_pinning_table();
2356
2357        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2358        let head = state.first_record();
2359        head.set_snapshot_id(SNAPSHOT_ID_MAX);
2360        head.set_next(None);
2361
2362        assert_eq!(state.try_get(), None);
2363    }
2364
2365    #[test]
2366    fn test_overwrite_unused_records_clears_values() {
2367        crate::snapshot_pinning::reset_pinning_table();
2368
2369        let tail = StateRecord::new(PREEXISTING_SNAPSHOT_ID, 0i32, None);
2370        let old_rec1 = StateRecord::new(2, 999i32, Some(tail.clone()));
2371        let old_rec2 = StateRecord::new(3, 888i32, Some(old_rec1.clone()));
2372        let head = StateRecord::new(150, 42i32, Some(old_rec2.clone()));
2373        let state = ManualState::new(head.clone());
2374
2375        // Verify value exists before cleanup
2376        old_rec1.with_value(|val: &i32| {
2377            assert_eq!(*val, 999);
2378        });
2379
2380        let _pin = crate::snapshot_pinning::track_pinning(100, &SnapshotIdSet::EMPTY);
2381        overwrite_unused_records_locked::<i32>(&state);
2382
2383        // The invalidated record should have its value cleared
2384        assert_eq!(old_rec1.snapshot_id(), INVALID_SNAPSHOT_ID);
2385        // Value access would panic, so we just verify it was marked invalid
2386    }
2387
2388    #[test]
2389    fn test_overwrite_unused_records_mixed_old_and_new() {
2390        crate::snapshot_pinning::reset_pinning_table();
2391
2392        // Create mixed chain: recent (50) -> old (5) -> old (2) -> PREEXISTING
2393        let preexisting = StateRecord::new(PREEXISTING_SNAPSHOT_ID, 0i32, None);
2394        let rec2 = StateRecord::new(2, 100i32, Some(preexisting.clone()));
2395        let rec5 = StateRecord::new(5, 100i32, Some(rec2.clone()));
2396        let rec50 = StateRecord::new(50, 100i32, Some(rec5.clone()));
2397        let head = StateRecord::new(120, 100i32, Some(rec50.clone()));
2398        let state = ManualState::new(head.clone());
2399
2400        // Pin snapshot 40 so reuse limit is ~40, making 2 and 5 old but 50 recent
2401        let _pin = crate::snapshot_pinning::track_pinning(40, &SnapshotIdSet::EMPTY);
2402
2403        let should_retain = overwrite_unused_records_locked::<i32>(&state);
2404        assert!(should_retain);
2405
2406        // rec50 is above reuse limit - should stay valid
2407        assert_eq!(rec50.snapshot_id(), 50);
2408        // rec5 is highest below reuse limit - should stay valid
2409        assert_eq!(rec5.snapshot_id(), 5);
2410        // rec2 is older and below reuse limit - should be invalidated
2411        assert_eq!(rec2.snapshot_id(), INVALID_SNAPSHOT_ID);
2412    }
2413
2414    #[test]
2415    fn test_readable_record_for_skips_invalid_set() {
2416        let head = create_record_chain(&[10, 5, PREEXISTING_SNAPSHOT_ID]);
2417        let invalid = SnapshotIdSet::new().set(5);
2418
2419        // Reading at snapshot 10 should skip record 5 (in invalid set)
2420        let result = readable_record_for(&head, 10, &invalid);
2421        assert!(result.is_some());
2422        assert_eq!(result.unwrap().snapshot_id(), 10);
2423
2424        // Reading at snapshot 7 should skip 5 and fall back to PREEXISTING
2425        let result = readable_record_for(&head, 7, &invalid);
2426        assert!(result.is_some());
2427        assert_eq!(result.unwrap().snapshot_id(), PREEXISTING_SNAPSHOT_ID);
2428    }
2429
2430    // ========== Tests for assign_value() ==========
2431
2432    #[test]
2433    fn test_assign_value_copies_int() {
2434        let source = StateRecord::new(10, 42i32, None);
2435        let target = StateRecord::new(20, 0i32, None);
2436
2437        target.assign_value::<i32>(&source).expect("copy int value");
2438
2439        // Verify the value was copied
2440        target.with_value(|val: &i32| {
2441            assert_eq!(*val, 42);
2442        });
2443
2444        // Verify source is unchanged
2445        source.with_value(|val: &i32| {
2446            assert_eq!(*val, 42);
2447        });
2448
2449        // Verify snapshot IDs are unchanged
2450        assert_eq!(source.snapshot_id(), 10);
2451        assert_eq!(target.snapshot_id(), 20);
2452    }
2453
2454    #[test]
2455    fn test_assign_value_copies_string() {
2456        let source = StateRecord::new(10, "hello".to_string(), None);
2457        let target = StateRecord::new(20, "world".to_string(), None);
2458
2459        target
2460            .assign_value::<String>(&source)
2461            .expect("copy string value");
2462
2463        // Verify the value was copied
2464        target.with_value(|val: &String| {
2465            assert_eq!(val, "hello");
2466        });
2467
2468        // Verify source is unchanged
2469        source.with_value(|val: &String| {
2470            assert_eq!(val, "hello");
2471        });
2472    }
2473
2474    #[test]
2475    fn test_assign_value_reports_cleared_source() {
2476        let source = StateRecord::new(10, 42i32, None);
2477        let target = StateRecord::new(20, 0i32, None);
2478
2479        source.clear_value();
2480
2481        assert_eq!(
2482            target.assign_value::<i32>(&source),
2483            Err(StateRecordValueError::MissingOrWrongType {
2484                expected: std::any::type_name::<i32>(),
2485            })
2486        );
2487        assert_eq!(target.with_value(|val: &i32| *val), 0);
2488    }
2489
2490    #[test]
2491    fn test_assign_value_overwrites_existing_value() {
2492        let source = StateRecord::new(10, 100i32, None);
2493        let target = StateRecord::new(20, 999i32, None);
2494
2495        // Verify target has initial value
2496        target.with_value(|val: &i32| {
2497            assert_eq!(*val, 999);
2498        });
2499
2500        // Assign from source
2501        target
2502            .assign_value::<i32>(&source)
2503            .expect("overwrite int value");
2504
2505        // Verify target now has source's value
2506        target.with_value(|val: &i32| {
2507            assert_eq!(*val, 100);
2508        });
2509    }
2510
2511    #[test]
2512    fn test_assign_value_with_custom_type() {
2513        #[derive(Clone, PartialEq, Debug)]
2514        struct Point {
2515            x: f64,
2516            y: f64,
2517        }
2518
2519        let source = StateRecord::new(10, Point { x: 1.5, y: 2.5 }, None);
2520        let target = StateRecord::new(20, Point { x: 0.0, y: 0.0 }, None);
2521
2522        target
2523            .assign_value::<Point>(&source)
2524            .expect("copy point value");
2525
2526        target.with_value(|val: &Point| {
2527            assert_eq!(val, &Point { x: 1.5, y: 2.5 });
2528        });
2529    }
2530
2531    #[test]
2532    fn test_assign_value_self_assignment() {
2533        let record = StateRecord::new(10, 42i32, None);
2534
2535        // Self-assignment should work (though not useful in practice)
2536        record
2537            .assign_value::<i32>(&record)
2538            .expect("self-assign int value");
2539
2540        record.with_value(|val: &i32| {
2541            assert_eq!(*val, 42);
2542        });
2543    }
2544
2545    #[test]
2546    fn test_assign_value_with_vec() {
2547        let source = StateRecord::new(10, vec![1, 2, 3, 4, 5], None);
2548        let target = StateRecord::new(20, Vec::<i32>::new(), None);
2549
2550        target
2551            .assign_value::<Vec<i32>>(&source)
2552            .expect("copy vec value");
2553
2554        target.with_value(|val: &Vec<i32>| {
2555            assert_eq!(val, &vec![1, 2, 3, 4, 5]);
2556        });
2557
2558        // Verify it's a deep copy (modifying source won't affect target)
2559        source.replace_value(vec![10, 20]);
2560        target.with_value(|val: &Vec<i32>| {
2561            assert_eq!(val, &vec![1, 2, 3, 4, 5]);
2562        });
2563    }
2564}