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(crate) struct StructuralEqual;
556
557impl<T: PartialEq> MutationPolicy<T> for StructuralEqual {
558    fn equivalent(&self, a: &T, b: &T) -> bool {
559        a == b
560    }
561}
562
563pub trait StateObject: Any {
564    fn object_id(&self) -> ObjectId;
565    fn first_record(&self) -> Rc<StateRecord>;
566    fn try_readable_record(
567        &self,
568        snapshot_id: SnapshotId,
569        invalid: &SnapshotIdSet,
570    ) -> Option<Rc<StateRecord>>;
571    fn readable_record(&self, snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> Rc<StateRecord>;
572
573    /// Prepends a record to the head of the record chain.
574    /// This is used when reusing records - the record's next pointer is updated to point to the current head,
575    /// and the head is updated to point to the new record.
576    fn prepend_state_record(&self, record: Rc<StateRecord>);
577
578    fn merge_records(
579        &self,
580        _previous: Rc<StateRecord>,
581        _current: Rc<StateRecord>,
582        _applied: Rc<StateRecord>,
583    ) -> Option<Rc<StateRecord>> {
584        None
585    }
586
587    fn commit_merged_record(&self, _merged: Rc<StateRecord>) -> Result<SnapshotId, &'static str> {
588        Err("StateObject does not support merged record commits")
589    }
590    fn promote_record(&self, child_id: SnapshotId) -> Result<(), &'static str>;
591
592    /// Overwrites unused records in this state's record chain with valid data.
593    ///
594    /// Returns `true` if the state has multiple retained records and should stay in extraStateObjects,
595    /// `false` if it can be removed from tracking.
596    fn overwrite_unused_records(&self) -> bool {
597        false // Default implementation for states that don't support cleanup
598    }
599
600    /// Downcast to Any for testing/debugging purposes.
601    fn as_any(&self) -> &dyn Any;
602}
603
604pub(crate) struct SnapshotMutableState<T> {
605    head: CurrentRecord,
606    policy: Arc<dyn MutationPolicy<T>>,
607    id: ObjectId,
608    weak_self: Mutex<Option<Weak<Self>>>,
609    apply_observers: Mutex<Vec<Box<dyn Fn() + 'static>>>,
610}
611
612impl<T> SnapshotMutableState<T> {
613    fn assert_chain_integrity(&self, caller: &str, snapshot_context: Option<SnapshotId>) {
614        if !should_check_chain_integrity() {
615            return;
616        }
617        let head = self.head.clone_head();
618        let mut cursor = Some(head);
619        let mut seen: HashSet<usize> = HashSet::default();
620        let mut ids = Vec::new();
621
622        while let Some(record) = cursor {
623            let addr = Rc::as_ptr(&record) as usize;
624            assert!(
625                seen.insert(addr),
626                "SnapshotMutableState::{} detected duplicate/cycle at record {:p} for state {:?} (snapshot_context={:?}, chain_ids={:?})",
627                caller,
628                Rc::as_ptr(&record),
629                self.id,
630                snapshot_context,
631                ids
632            );
633            ids.push(record.snapshot_id());
634            cursor = record.next();
635        }
636
637        assert!(
638            !ids.is_empty(),
639            "SnapshotMutableState::{} finished integrity scan with empty id list for state {:?} (snapshot_context={:?})",
640            caller,
641            self.id,
642            snapshot_context
643        );
644    }
645}
646
647fn should_check_chain_integrity() -> bool {
648    #[cfg(debug_assertions)]
649    {
650        true
651    }
652
653    #[cfg(not(debug_assertions))]
654    {
655        crate::env_flag!("CRANPOSE_ASSERT_STATE_CHAIN")
656    }
657}
658
659impl<T: Clone + 'static> SnapshotMutableState<T> {
660    fn record_chain_debug(&self) -> Vec<(SnapshotId, bool)> {
661        let mut chain_ids = Vec::new();
662        let mut cursor = Some(self.first_record());
663        while let Some(record) = cursor {
664            chain_ids.push((record.snapshot_id(), record.is_tombstone()));
665            cursor = record.next();
666        }
667        chain_ids
668    }
669
670    fn readable_record_for_active_snapshot(&self) -> Result<Rc<StateRecord>, StateReadFailure> {
671        let snapshot = active_snapshot();
672        if let Some(state) = self.upgrade_self() {
673            snapshot.record_read(&*state);
674        }
675
676        let snapshot_id = snapshot.snapshot_id();
677        let invalid = snapshot.invalid();
678
679        if let Some(record) = self.readable_for(snapshot_id, &invalid) {
680            return Ok(record);
681        }
682
683        let fresh_snapshot = active_snapshot();
684        let fresh_id = fresh_snapshot.snapshot_id();
685        let fresh_invalid = fresh_snapshot.invalid();
686
687        if let Some(record) = self.readable_for(fresh_id, &fresh_invalid) {
688            return Ok(record);
689        }
690
691        let global = GlobalSnapshot::get_or_create();
692        let global_id = global.snapshot_id();
693        let global_invalid = global.invalid();
694
695        if let Some(record) = self.readable_for(global_id, &global_invalid) {
696            return Ok(record);
697        }
698
699        Err(StateReadFailure {
700            state_id: self.id,
701            snapshot_id,
702            fresh_snapshot_id: fresh_id,
703            fresh_invalid,
704            record_chain: self.record_chain_debug(),
705        })
706    }
707
708    fn readable_for(
709        &self,
710        snapshot_id: SnapshotId,
711        invalid: &SnapshotIdSet,
712    ) -> Option<Rc<StateRecord>> {
713        let head = self.first_record();
714        readable_record_for(&head, snapshot_id, invalid)
715    }
716
717    fn writable_record(&self, snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> Rc<StateRecord> {
718        let readable = match self.readable_for(snapshot_id, invalid) {
719            Some(record) => record,
720            None => {
721                let current_head = self.head.clone_head();
722                let refreshed = readable_record_for(&current_head, snapshot_id, invalid);
723                let source = refreshed.unwrap_or_else(|| current_head.clone());
724
725                // Create a new record
726                // Record reuse is NOT used here to preserve history for conflict detection
727                // Reuse happens during cleanup (overwrite_unused_records_locked)
728                let cloned_value = source.with_value(|value: &T| value.clone());
729                let new_head = StateRecord::new(snapshot_id, cloned_value, Some(current_head));
730                self.head.replace(new_head.clone());
731                self.assert_chain_integrity("writable_record(recover)", Some(snapshot_id));
732                return new_head;
733            }
734        };
735
736        if readable.snapshot_id() == snapshot_id {
737            return readable;
738        }
739
740        let refreshed = {
741            let current_head = self.head.clone_head();
742            let refreshed = readable_record_for(&current_head, snapshot_id, invalid).unwrap_or_else(
743                || {
744                    panic!(
745                        "SnapshotMutableState::writable_record failed to locate refreshed readable record (state {:?}, snapshot_id={}, invalid={:?})",
746                        self.id, snapshot_id, invalid
747                    )
748                },
749            );
750
751            if refreshed.snapshot_id() == snapshot_id {
752                return refreshed;
753            }
754
755            Rc::clone(&refreshed)
756        };
757
758        let overwritable = new_overwritable_record_locked(self);
759        if let Err(error) = overwritable.assign_value::<T>(&refreshed) {
760            log::error!(
761                "snapshot writable record could not copy refreshed value for state {:?}: {:?}",
762                self.id,
763                error
764            );
765        }
766        overwritable.set_snapshot_id(snapshot_id);
767        overwritable.set_tombstone(false);
768
769        self.assert_chain_integrity("writable_record(reuse)", Some(snapshot_id));
770
771        overwritable
772    }
773
774    pub(crate) fn new_in_arc(initial: T, policy: Arc<dyn MutationPolicy<T>>) -> Arc<Self> {
775        let snapshot = active_snapshot();
776        let snapshot_id = snapshot.snapshot_id();
777
778        let tail = StateRecord::new(PREEXISTING_SNAPSHOT_ID, initial.clone(), None);
779        let head = StateRecord::new(snapshot_id, initial, Some(tail));
780
781        let mut state = Arc::new(Self {
782            head: CurrentRecord::new(head),
783            policy,
784            id: ObjectId::default(),
785            weak_self: Mutex::new(None),
786            apply_observers: Mutex::new(Vec::new()),
787        });
788
789        let id = ObjectId::new(&state);
790        if let Some(state_inner) = Arc::get_mut(&mut state) {
791            state_inner.id = id;
792        }
793
794        *state.lock_weak_self() = Some(Arc::downgrade(&state));
795
796        // No need to advance the global snapshot for initial state creation
797
798        state
799    }
800
801    pub(crate) fn add_apply_observer(&self, observer: Box<dyn Fn() + 'static>) {
802        self.lock_apply_observers().push(observer);
803    }
804
805    fn notify_applied(&self) {
806        let observers = self.lock_apply_observers();
807        for observer in observers.iter() {
808            observer();
809        }
810    }
811
812    fn lock_weak_self(&self) -> MutexGuard<'_, Option<Weak<Self>>> {
813        self.weak_self
814            .lock()
815            .unwrap_or_else(|poisoned| poisoned.into_inner())
816    }
817
818    fn lock_apply_observers(&self) -> MutexGuard<'_, Vec<Box<dyn Fn() + 'static>>> {
819        self.apply_observers
820            .lock()
821            .unwrap_or_else(|poisoned| poisoned.into_inner())
822    }
823
824    fn upgrade_self(&self) -> Option<Arc<Self>> {
825        self.lock_weak_self()
826            .as_ref()
827            .and_then(|weak| weak.upgrade())
828    }
829
830    #[inline]
831    pub(crate) fn id(&self) -> ObjectId {
832        self.id
833    }
834
835    pub(crate) fn try_with_value<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
836        let record = self.readable_record_for_active_snapshot().ok()?;
837        record.try_with_value(f)
838    }
839
840    pub(crate) fn try_get(&self) -> Option<T> {
841        self.try_with_value(Clone::clone)
842    }
843
844    pub(crate) fn get(&self) -> T {
845        let record = self
846            .readable_record_for_active_snapshot()
847            .unwrap_or_else(|failure| panic!("{failure}"));
848        record.with_value(|value: &T| value.clone())
849    }
850
851    pub(crate) fn set(&self, new_value: T) -> bool {
852        // Debug-only check: warn if modifying state in event handler without proper snapshot
853        #[cfg(debug_assertions)]
854        {
855            let in_handler = crate::in_event_handler();
856            let in_snapshot = crate::in_applied_snapshot();
857            if in_handler && !in_snapshot {
858                log::warn!(
859                    target: "cranpose::state",
860                    "State modified in event handler without run_in_mutable_snapshot; \
861                     this can make updates invisible to other contexts. Wrap the handler \
862                     in run_in_mutable_snapshot() or dispatch_ui_event(). State: {:?}",
863                    self.id
864                );
865            }
866        }
867
868        let snapshot = active_snapshot();
869        let snapshot_id = snapshot.snapshot_id();
870
871        match &snapshot {
872            AnySnapshot::Global(global) => {
873                let invalid = snapshot.invalid();
874                let equivalent = self
875                    .readable_for(snapshot_id, &invalid)
876                    .map(|record| {
877                        record.with_value(|current: &T| self.policy.equivalent(current, &new_value))
878                    })
879                    .unwrap_or(false);
880                if equivalent {
881                    return false;
882                }
883
884                if global.has_pending_children() {
885                    panic!(
886                        "SnapshotMutableState::set attempted global write while pending children {:?} exist (state {:?}, snapshot_id={})",
887                        global.pending_children(),
888                        self.id,
889                        snapshot_id
890                    );
891                }
892
893                let mut written_state: Option<Arc<dyn StateObject>> = None;
894                if let Some(state) = self.upgrade_self() {
895                    let trait_object: Arc<dyn StateObject> = state.clone();
896                    snapshot.record_write(trait_object.clone());
897                    written_state = Some(trait_object);
898                }
899                mark_update_write(self.id);
900
901                let new_id = allocate_record_id();
902                let record = new_overwritable_record_as_head_locked(self);
903                record.replace_value(new_value);
904                record.set_snapshot_id(new_id);
905                record.set_tombstone(false);
906                advance_global_snapshot(new_id);
907                self.assert_chain_integrity("set(global-push)", Some(snapshot_id));
908
909                if !global.has_pending_children() {
910                    let mut cursor = record.next();
911                    while let Some(node) = cursor {
912                        if !node.is_tombstone() && node.snapshot_id() != PREEXISTING_SNAPSHOT_ID {
913                            node.clear_value();
914                            node.set_tombstone(true);
915                        }
916                        cursor = node.next();
917                    }
918                    self.assert_chain_integrity("set(global-tombstone)", Some(snapshot_id));
919                }
920
921                if let Some(modified) = written_state.as_ref() {
922                    crate::snapshot_v2::notify_apply_observers(
923                        std::slice::from_ref(modified),
924                        new_id,
925                    );
926                }
927            }
928            AnySnapshot::Mutable(_)
929            | AnySnapshot::NestedMutable(_)
930            | AnySnapshot::TransparentMutable(_) => {
931                let invalid = snapshot.invalid();
932                let equivalent = self
933                    .readable_for(snapshot_id, &invalid)
934                    .map(|record| {
935                        record.with_value(|current: &T| self.policy.equivalent(current, &new_value))
936                    })
937                    .unwrap_or(false);
938                if equivalent {
939                    return false;
940                }
941
942                if let Some(state) = self.upgrade_self() {
943                    let trait_object: Arc<dyn StateObject> = state.clone();
944                    snapshot.record_write(trait_object);
945                }
946                mark_update_write(self.id);
947
948                let record = self.writable_record(snapshot_id, &invalid);
949                record.replace_value(new_value);
950                self.assert_chain_integrity("set(child-writable)", Some(snapshot_id));
951            }
952            AnySnapshot::Readonly(_)
953            | AnySnapshot::NestedReadonly(_)
954            | AnySnapshot::TransparentReadonly(_) => {
955                panic!("Cannot write to a read-only snapshot");
956            }
957        }
958
959        // Retain the prior record chain so concurrent readers never observe freed nodes.
960        // Compose proper prunes when it can prove no readers exist; for now we keep
961        // the historical chain with tombstoned values to avoid use-after-free crashes
962        // under heavy UI load.
963        true
964    }
965}
966
967thread_local! {
968    static ACTIVE_UPDATES: RefCell<HashSet<ObjectId>> = RefCell::new(HashSet::default());
969    static PENDING_WRITES: RefCell<HashSet<ObjectId>> = RefCell::new(HashSet::default());
970}
971
972pub(crate) struct UpdateScope {
973    id: ObjectId,
974    finished: bool,
975}
976
977impl UpdateScope {
978    pub(crate) fn new(id: ObjectId) -> Self {
979        ACTIVE_UPDATES.with(|active| {
980            active.borrow_mut().insert(id);
981        });
982        PENDING_WRITES.with(|pending| {
983            pending.borrow_mut().remove(&id);
984        });
985        Self {
986            id,
987            finished: false,
988        }
989    }
990
991    pub(crate) fn finish(mut self) -> bool {
992        self.finished = true;
993        ACTIVE_UPDATES.with(|active| {
994            active.borrow_mut().remove(&self.id);
995        });
996        PENDING_WRITES.with(|pending| pending.borrow_mut().remove(&self.id))
997    }
998}
999
1000impl Drop for UpdateScope {
1001    fn drop(&mut self) {
1002        if self.finished {
1003            return;
1004        }
1005        ACTIVE_UPDATES.with(|active| {
1006            active.borrow_mut().remove(&self.id);
1007        });
1008        PENDING_WRITES.with(|pending| {
1009            pending.borrow_mut().remove(&self.id);
1010        });
1011    }
1012}
1013
1014fn mark_update_write(id: ObjectId) {
1015    ACTIVE_UPDATES.with(|active| {
1016        if active.borrow().contains(&id) {
1017            PENDING_WRITES.with(|pending| {
1018                pending.borrow_mut().insert(id);
1019            });
1020        }
1021    });
1022}
1023
1024impl<T: Clone + 'static> SnapshotMutableState<T> {
1025    /// Try to find a readable record, returning None if no valid record exists.
1026    fn try_readable_record(
1027        &self,
1028        snapshot_id: SnapshotId,
1029        invalid: &SnapshotIdSet,
1030    ) -> Option<Rc<StateRecord>> {
1031        self.readable_for(snapshot_id, invalid)
1032    }
1033}
1034
1035impl<T: Clone + 'static> StateObject for SnapshotMutableState<T> {
1036    fn object_id(&self) -> ObjectId {
1037        self.id
1038    }
1039
1040    fn first_record(&self) -> Rc<StateRecord> {
1041        self.head.clone_head()
1042    }
1043
1044    fn try_readable_record(
1045        &self,
1046        snapshot_id: SnapshotId,
1047        invalid: &SnapshotIdSet,
1048    ) -> Option<Rc<StateRecord>> {
1049        self.try_readable_record(snapshot_id, invalid)
1050    }
1051
1052    fn readable_record(&self, snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> Rc<StateRecord> {
1053        self.try_readable_record(snapshot_id, invalid)
1054            .unwrap_or_else(|| {
1055                panic!(
1056                    "SnapshotMutableState::readable_record returned null (state={:?}, snapshot_id={})",
1057                    self.id, snapshot_id
1058                )
1059            })
1060    }
1061
1062    fn prepend_state_record(&self, record: Rc<StateRecord>) {
1063        self.head.prepend(record);
1064    }
1065
1066    fn merge_records(
1067        &self,
1068        previous: Rc<StateRecord>,
1069        current: Rc<StateRecord>,
1070        applied: Rc<StateRecord>,
1071    ) -> Option<Rc<StateRecord>> {
1072        let Some(current_value) = current.try_with_value(|value: &T| value.clone()) else {
1073            log::error!(
1074                "SnapshotMutableState::merge_records current record value missing or wrong type (state {:?}, current_id={})",
1075                self.id,
1076                current.snapshot_id()
1077            );
1078            return None;
1079        };
1080        let Some(applied_value) = applied.try_with_value(|value: &T| value.clone()) else {
1081            log::error!(
1082                "SnapshotMutableState::merge_records applied record value missing or wrong type (state {:?}, applied_id={})",
1083                self.id,
1084                applied.snapshot_id()
1085            );
1086            return None;
1087        };
1088        if self.policy.equivalent(&current_value, &applied_value) {
1089            return Some(current);
1090        }
1091
1092        let Some(previous_value) = previous.try_with_value(|value: &T| value.clone()) else {
1093            log::error!(
1094                "SnapshotMutableState::merge_records previous record value missing or wrong type (state {:?}, previous_id={})",
1095                self.id,
1096                previous.snapshot_id()
1097            );
1098            return None;
1099        };
1100        let merged = self
1101            .policy
1102            .merge(&previous_value, &current_value, &applied_value)?;
1103
1104        Some(StateRecord::new(applied.snapshot_id(), merged, None))
1105    }
1106
1107    fn promote_record(&self, child_id: SnapshotId) -> Result<(), &'static str> {
1108        let head = self.first_record();
1109        let mut cursor = Some(head);
1110        while let Some(record) = cursor {
1111            if record.snapshot_id() == child_id {
1112                let Some(cloned) = record.try_with_value(|value: &T| value.clone()) else {
1113                    log::error!(
1114                        "SnapshotMutableState::promote_record child record value missing or wrong type (state {:?}, child_id={})",
1115                        self.id,
1116                        child_id
1117                    );
1118                    return Err("child record value missing or wrong type");
1119                };
1120                // Publish through the reuse primitive: every apply retires
1121                // the previous head, so an unconditional allocation here
1122                // grows the record chain by one per UI event — the chain is
1123                // scanned linearly on every read, so frames decay for the
1124                // whole session (user-visible as sinking fps under repeated
1125                // handle drags).
1126                let new_id = allocate_record_id();
1127                let promoted = new_overwritable_record_as_head_locked(self);
1128                promoted.replace_value(cloned);
1129                promoted.set_tombstone(false);
1130                promoted.set_snapshot_id(new_id);
1131                advance_global_snapshot(new_id);
1132                self.notify_applied();
1133                self.assert_chain_integrity("promote_record", Some(child_id));
1134                return Ok(());
1135            }
1136            cursor = record.next();
1137        }
1138        log::error!(
1139            "SnapshotMutableState::promote_record missing child record (state {:?}, child_id={})",
1140            self.id,
1141            child_id
1142        );
1143        Err("missing child record")
1144    }
1145
1146    fn commit_merged_record(&self, merged: Rc<StateRecord>) -> Result<SnapshotId, &'static str> {
1147        let Some(value) = merged.try_with_value(|value: &T| value.clone()) else {
1148            log::error!(
1149                "SnapshotMutableState::commit_merged_record merged record value missing or wrong type (state {:?}, merged_id={})",
1150                self.id,
1151                merged.snapshot_id()
1152            );
1153            return Err("merged record value missing or wrong type");
1154        };
1155        // Same reuse discipline as `promote_record`: merged commits happen
1156        // per conflicting apply and must not grow the chain.
1157        let new_id = allocate_record_id();
1158        let committed = new_overwritable_record_as_head_locked(self);
1159        committed.replace_value(value);
1160        committed.set_tombstone(false);
1161        committed.set_snapshot_id(new_id);
1162        advance_global_snapshot(new_id);
1163        self.notify_applied();
1164        self.assert_chain_integrity("commit_merged_record", Some(new_id));
1165        Ok(new_id)
1166    }
1167
1168    fn overwrite_unused_records(&self) -> bool {
1169        overwrite_unused_records_locked::<T>(self)
1170    }
1171
1172    fn as_any(&self) -> &dyn Any {
1173        self
1174    }
1175}
1176
1177pub(crate) struct MutableStateInner<T: Clone + 'static> {
1178    pub(crate) state: Arc<SnapshotMutableState<T>>,
1179    pub(crate) watchers: RefCell<HashMap<ScopeId, RcWeak<RecomposeScopeInner>>>,
1180    runtime: RuntimeHandle,
1181    state_id: Cell<Option<StateId>>,
1182}
1183
1184fn shrink_watchers_if_sparse(watchers: &mut HashMap<ScopeId, RcWeak<RecomposeScopeInner>>) {
1185    let len = watchers.len();
1186    let capacity = watchers.capacity();
1187    if capacity > len.saturating_mul(4).max(32) {
1188        watchers.shrink_to_fit();
1189    }
1190}
1191
1192impl<T: Clone + 'static> MutableStateInner<T> {
1193    pub(crate) fn new_with_policy(
1194        value: T,
1195        runtime: RuntimeHandle,
1196        policy: Arc<dyn MutationPolicy<T>>,
1197    ) -> Self {
1198        Self {
1199            state: SnapshotMutableState::new_in_arc(value, policy),
1200            watchers: RefCell::new(HashMap::default()),
1201            runtime,
1202            state_id: Cell::new(None),
1203        }
1204    }
1205
1206    pub(crate) fn install_snapshot_observer(&self, state_id: StateId) {
1207        self.state_id.set(Some(state_id));
1208        let runtime_handle = self.runtime.clone();
1209        self.state.add_apply_observer(Box::new(move || {
1210            let runtime = runtime_handle.clone();
1211            runtime_handle.enqueue_ui_task(Box::new(move || {
1212                runtime.with_state_arena(|arena| {
1213                    let _ = arena.with_typed_opt::<T, _>(state_id, |inner| {
1214                        inner.invalidate_watchers();
1215                    });
1216                });
1217            }));
1218        }));
1219    }
1220
1221    fn with_value<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1222        let value = self.state.get();
1223        f(&value)
1224    }
1225
1226    fn register_scope(&self, scope: &RecomposeScope) -> bool {
1227        let mut watchers = self.watchers.borrow_mut();
1228        match watchers.get(&scope.id()) {
1229            Some(existing) if existing.upgrade().is_some() => false,
1230            _ => {
1231                watchers.insert(scope.id(), scope.downgrade());
1232                true
1233            }
1234        }
1235    }
1236
1237    pub(crate) fn unregister_scope(&self, scope_id: ScopeId) {
1238        let mut watchers = self.watchers.borrow_mut();
1239        // Only prune a DEAD entry. Scope ids are derived from the scope
1240        // allocation's address, so a dropped scope's id can already belong
1241        // to a live replacement — its Drop-time unregister must not wipe
1242        // the new scope's registration.
1243        if watchers
1244            .get(&scope_id)
1245            .is_some_and(|weak| weak.upgrade().is_none())
1246        {
1247            watchers.remove(&scope_id);
1248            shrink_watchers_if_sparse(&mut watchers);
1249        }
1250    }
1251
1252    fn state_id(&self) -> Option<StateId> {
1253        self.state_id.get()
1254    }
1255
1256    fn invalidate_watchers(&self) {
1257        let watchers: Vec<RecomposeScope> = {
1258            let mut watchers = self.watchers.borrow_mut();
1259            let mut live = Vec::with_capacity(watchers.len());
1260            watchers.retain(|_, scope| {
1261                if let Some(inner) = scope.upgrade() {
1262                    live.push(RecomposeScope { inner });
1263                    true
1264                } else {
1265                    false
1266                }
1267            });
1268            shrink_watchers_if_sparse(&mut watchers);
1269            live
1270        };
1271
1272        for watcher in watchers {
1273            debug_record_scope_invalidation::<T>(watcher.id(), self.state_id.get());
1274            if let Some(state_id) = self.state_id.get() {
1275                watcher.invalidate_from_state(state_id);
1276            } else {
1277                watcher.invalidate();
1278            }
1279        }
1280    }
1281}
1282
1283fn register_current_state_scope<T: Clone + 'static>(inner: &MutableStateInner<T>) {
1284    let Some(Some(scope)) =
1285        with_current_composer_opt(|composer| composer.current_state_invalidation_scope())
1286    else {
1287        return;
1288    };
1289    if inner.register_scope(&scope) {
1290        if let Some(state_id) = inner.state_id() {
1291            scope.record_state_subscription(state_id);
1292        }
1293    }
1294}
1295
1296/// Cheap copyable read-only view of a state cell.
1297pub struct State<T: Clone + 'static> {
1298    id: StateId,
1299    runtime_id: runtime::RuntimeId,
1300    _marker: PhantomData<fn() -> T>,
1301}
1302
1303/// Cheap copyable mutable view of a state cell.
1304///
1305/// Ownership lives elsewhere: a composition slot, an [`OwnedMutableState`], or
1306/// the runtime for states created with [`crate::mutableStateOf`] /
1307/// [`MutableState::with_runtime`].
1308pub struct MutableState<T: Clone + 'static> {
1309    id: StateId,
1310    runtime_id: runtime::RuntimeId,
1311    _marker: PhantomData<fn() -> T>,
1312}
1313
1314/// Owning state handle for reclaimable state cells.
1315#[derive(Clone)]
1316pub struct OwnedMutableState<T: Clone + 'static> {
1317    state: MutableState<T>,
1318    _lease: Rc<runtime::StateHandleLease>,
1319    _marker: PhantomData<fn() -> T>,
1320}
1321
1322impl<T: Clone + 'static> PartialEq for State<T> {
1323    fn eq(&self, other: &Self) -> bool {
1324        self.state_id() == other.state_id() && self.runtime_id() == other.runtime_id()
1325    }
1326}
1327
1328impl<T: Clone + 'static> Eq for State<T> {}
1329
1330impl<T: Clone + 'static> PartialEq for MutableState<T> {
1331    fn eq(&self, other: &Self) -> bool {
1332        self.state_id() == other.state_id() && self.runtime_id() == other.runtime_id()
1333    }
1334}
1335
1336impl<T: Clone + 'static> Eq for MutableState<T> {}
1337
1338impl<T: Clone + 'static> Copy for State<T> {}
1339
1340impl<T: Clone + 'static> Clone for State<T> {
1341    fn clone(&self) -> Self {
1342        *self
1343    }
1344}
1345
1346impl<T: Clone + 'static> Copy for MutableState<T> {}
1347
1348impl<T: Clone + 'static> Clone for MutableState<T> {
1349    fn clone(&self) -> Self {
1350        *self
1351    }
1352}
1353
1354impl<T: Clone + 'static> State<T> {
1355    fn state_id(&self) -> StateId {
1356        self.id
1357    }
1358
1359    fn runtime_id(&self) -> runtime::RuntimeId {
1360        self.runtime_id
1361    }
1362
1363    fn runtime_handle(&self) -> RuntimeHandle {
1364        runtime::runtime_handle_by_id(self.runtime_id())
1365            .unwrap_or_else(|| panic!("runtime {:?} dropped", self.runtime_id()))
1366    }
1367
1368    fn runtime_handle_opt(&self) -> Option<RuntimeHandle> {
1369        runtime::runtime_handle_by_id(self.runtime_id())
1370    }
1371
1372    fn with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> R {
1373        self.runtime_handle()
1374            .with_state_arena(|arena| arena.with_typed::<T, R>(self.state_id(), f))
1375    }
1376
1377    fn try_with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> Option<R> {
1378        self.runtime_handle_opt()?
1379            .try_with_state_arena(|arena| arena.with_typed_opt::<T, R>(self.state_id(), f))?
1380    }
1381
1382    fn subscribe_current_scope(&self) {
1383        self.with_inner(register_current_state_scope::<T>);
1384    }
1385
1386    pub fn is_alive(&self) -> bool {
1387        self.try_with_inner(|_| ()).is_some()
1388    }
1389
1390    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
1391        self.try_with_inner(|inner| inner.state.try_with_value(f))?
1392    }
1393
1394    pub fn try_value(&self) -> Option<T> {
1395        self.try_with_inner(|inner| inner.state.try_get())?
1396    }
1397
1398    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1399        self.subscribe_current_scope();
1400        self.with_inner(|inner| inner.with_value(f))
1401    }
1402
1403    pub fn value(&self) -> T {
1404        self.subscribe_current_scope();
1405        self.with_inner(|inner| inner.state.get())
1406    }
1407
1408    pub fn get(&self) -> T {
1409        self.value()
1410    }
1411}
1412
1413impl<T: Clone + 'static> MutableState<T> {
1414    pub fn with_runtime(value: T, runtime: RuntimeHandle) -> Self {
1415        runtime.alloc_persistent_state(value)
1416    }
1417
1418    fn from_parts(id: StateId, runtime_id: runtime::RuntimeId) -> Self {
1419        Self {
1420            id,
1421            runtime_id,
1422            _marker: PhantomData,
1423        }
1424    }
1425
1426    pub(crate) fn from_lease(lease: &Rc<runtime::StateHandleLease>) -> Self {
1427        Self::from_parts(lease.id(), lease.runtime().id())
1428    }
1429
1430    fn state_id(&self) -> StateId {
1431        self.id
1432    }
1433
1434    fn runtime_id(&self) -> runtime::RuntimeId {
1435        self.runtime_id
1436    }
1437
1438    fn runtime_handle(&self) -> RuntimeHandle {
1439        runtime::runtime_handle_by_id(self.runtime_id())
1440            .unwrap_or_else(|| panic!("runtime {:?} dropped", self.runtime_id()))
1441    }
1442
1443    fn runtime_handle_opt(&self) -> Option<RuntimeHandle> {
1444        runtime::runtime_handle_by_id(self.runtime_id())
1445    }
1446
1447    fn with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> R {
1448        self.runtime_handle()
1449            .with_state_arena(|arena| arena.with_typed::<T, R>(self.state_id(), f))
1450    }
1451
1452    fn try_with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> Option<R> {
1453        self.runtime_handle_opt()?
1454            .try_with_state_arena(|arena| arena.with_typed_opt::<T, R>(self.state_id(), f))?
1455    }
1456
1457    pub fn is_alive(&self) -> bool {
1458        self.try_with_inner(|_| ()).is_some()
1459    }
1460
1461    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
1462        self.try_with_inner(|inner| inner.state.try_with_value(f))?
1463    }
1464
1465    pub fn try_value(&self) -> Option<T> {
1466        self.try_with_inner(|inner| inner.state.try_get())?
1467    }
1468
1469    pub fn as_state(&self) -> State<T> {
1470        State {
1471            id: self.id,
1472            runtime_id: self.runtime_id,
1473            _marker: PhantomData,
1474        }
1475    }
1476
1477    pub fn try_retain(&self) -> Option<OwnedMutableState<T>> {
1478        let lease = self
1479            .runtime_handle_opt()?
1480            .retain_state_lease(self.state_id())?;
1481        Some(OwnedMutableState {
1482            state: *self,
1483            _lease: lease,
1484            _marker: PhantomData,
1485        })
1486    }
1487
1488    pub fn retain(&self) -> OwnedMutableState<T> {
1489        self.try_retain()
1490            .unwrap_or_else(|| panic!("state {:?} is no longer alive", self.state_id()))
1491    }
1492
1493    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1494        self.subscribe_current_scope();
1495        self.with_inner(|inner| inner.with_value(f))
1496    }
1497
1498    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
1499        let runtime = self.runtime_handle();
1500        runtime.assert_ui_thread();
1501        runtime.with_state_arena(|arena| {
1502            arena.with_typed::<T, R>(self.state_id(), |inner| {
1503                let mut value = inner.state.get();
1504                let tracker = UpdateScope::new(inner.state.id());
1505                let result = f(&mut value);
1506                let wrote_elsewhere = tracker.finish();
1507                if !wrote_elsewhere && inner.state.set(value) {
1508                    inner.invalidate_watchers();
1509                }
1510                result
1511            })
1512        })
1513    }
1514
1515    pub fn replace(&self, value: T) {
1516        let Some(runtime) = self.runtime_handle_opt() else {
1517            log::debug!(
1518                "MutableState::replace skipped: runtime {:?} dropped",
1519                self.runtime_id()
1520            );
1521            return;
1522        };
1523        runtime.assert_ui_thread();
1524        let replaced = runtime
1525            .try_with_state_arena(|arena| {
1526                arena.with_typed_opt::<T, ()>(self.state_id(), |inner| {
1527                    if inner.state.set(value) {
1528                        inner.invalidate_watchers();
1529                    }
1530                })
1531            })
1532            .flatten();
1533        if replaced.is_none() {
1534            log::debug!(
1535                "MutableState::replace skipped: state cell released (slot={}, gen={})",
1536                self.state_id().slot(),
1537                self.state_id().generation(),
1538            );
1539        }
1540    }
1541
1542    pub fn set_value(&self, value: T) {
1543        self.replace(value);
1544    }
1545
1546    pub fn set(&self, value: T) {
1547        self.replace(value);
1548    }
1549
1550    pub fn value(&self) -> T {
1551        self.subscribe_current_scope();
1552        self.with_inner(|inner| inner.state.get())
1553    }
1554
1555    pub fn get(&self) -> T {
1556        self.value()
1557    }
1558
1559    pub fn get_non_reactive(&self) -> T {
1560        self.with_inner(|inner| inner.state.get())
1561    }
1562
1563    #[doc(hidden)]
1564    pub fn runtime_state_id(&self) -> StateId {
1565        self.state_id()
1566    }
1567
1568    #[doc(hidden)]
1569    pub fn subscribe_current_scope_only(&self) {
1570        self.subscribe_current_scope();
1571    }
1572
1573    fn subscribe_current_scope(&self) {
1574        self.with_inner(register_current_state_scope::<T>);
1575    }
1576
1577    #[cfg(test)]
1578    pub(crate) fn watcher_count(&self) -> usize {
1579        self.with_inner(|inner| inner.watchers.borrow().len())
1580    }
1581
1582    #[cfg(test)]
1583    pub(crate) fn watcher_capacity(&self) -> usize {
1584        self.with_inner(|inner| inner.watchers.borrow().capacity())
1585    }
1586
1587    #[cfg(test)]
1588    pub(crate) fn state_id_for_test(&self) -> StateId {
1589        self.state_id()
1590    }
1591
1592    #[cfg(test)]
1593    pub(crate) fn subscribe_scope_for_test(&self, scope: &RecomposeScope) {
1594        self.as_state().subscribe_scope_for_test(scope);
1595    }
1596}
1597
1598impl<T: Clone + 'static> OwnedMutableState<T> {
1599    pub fn with_runtime(value: T, runtime: RuntimeHandle) -> Self {
1600        let lease = runtime.alloc_state(value);
1601        Self {
1602            state: MutableState::from_lease(&lease),
1603            _lease: lease,
1604            _marker: PhantomData,
1605        }
1606    }
1607
1608    pub fn with_runtime_structural_eq(value: T, runtime: RuntimeHandle) -> Self
1609    where
1610        T: PartialEq,
1611    {
1612        Self::with_runtime_and_policy(value, runtime, Arc::new(StructuralEqual))
1613    }
1614
1615    pub(crate) fn with_runtime_and_policy(
1616        value: T,
1617        runtime: RuntimeHandle,
1618        policy: Arc<dyn MutationPolicy<T>>,
1619    ) -> Self {
1620        let lease = runtime.alloc_state_with_policy(value, policy);
1621        Self {
1622            state: MutableState::from_lease(&lease),
1623            _lease: lease,
1624            _marker: PhantomData,
1625        }
1626    }
1627
1628    pub fn handle(&self) -> MutableState<T> {
1629        self.state
1630    }
1631
1632    pub fn as_state(&self) -> State<T> {
1633        self.state.as_state()
1634    }
1635}
1636
1637impl<T: Clone + 'static> Deref for OwnedMutableState<T> {
1638    type Target = MutableState<T>;
1639
1640    fn deref(&self) -> &Self::Target {
1641        &self.state
1642    }
1643}
1644
1645#[cfg(test)]
1646impl<T: Clone + 'static> State<T> {
1647    pub(crate) fn subscribe_scope_for_test(&self, scope: &RecomposeScope) {
1648        self.with_inner(|inner| {
1649            if inner.register_scope(scope) {
1650                if let Some(state_id) = inner.state_id() {
1651                    scope.record_state_subscription(state_id);
1652                }
1653            }
1654        });
1655    }
1656}
1657
1658impl<T: fmt::Debug + Clone + 'static> fmt::Debug for MutableState<T> {
1659    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1660        if let Some(value) = self.try_value() {
1661            f.debug_struct("MutableState")
1662                .field("value", &value)
1663                .finish()
1664        } else {
1665            f.write_str("MutableState { value: <unavailable> }")
1666        }
1667    }
1668}
1669
1670#[derive(Clone)]
1671pub struct SnapshotStateList<T: Clone + 'static> {
1672    state: OwnedMutableState<Vec<T>>,
1673}
1674
1675impl<T: Clone + 'static> SnapshotStateList<T> {
1676    pub fn with_runtime<I>(values: I, runtime: RuntimeHandle) -> Self
1677    where
1678        I: IntoIterator<Item = T>,
1679    {
1680        let initial: Vec<T> = values.into_iter().collect();
1681        Self {
1682            state: OwnedMutableState::with_runtime(initial, runtime),
1683        }
1684    }
1685
1686    pub fn as_state(&self) -> State<Vec<T>> {
1687        self.state.as_state()
1688    }
1689
1690    pub fn as_mutable_state(&self) -> MutableState<Vec<T>> {
1691        self.state.handle()
1692    }
1693
1694    pub fn len(&self) -> usize {
1695        self.state.with(|values| values.len())
1696    }
1697
1698    pub fn is_empty(&self) -> bool {
1699        self.len() == 0
1700    }
1701
1702    pub fn to_vec(&self) -> Vec<T> {
1703        self.state.with(|values| values.clone())
1704    }
1705
1706    pub fn iter(&self) -> Vec<T> {
1707        self.to_vec()
1708    }
1709
1710    pub fn get(&self, index: usize) -> T {
1711        self.state.with(|values| values[index].clone())
1712    }
1713
1714    pub fn get_opt(&self, index: usize) -> Option<T> {
1715        self.state.with(|values| values.get(index).cloned())
1716    }
1717
1718    pub fn first(&self) -> Option<T> {
1719        self.get_opt(0)
1720    }
1721
1722    pub fn last(&self) -> Option<T> {
1723        self.state.with(|values| values.last().cloned())
1724    }
1725
1726    pub fn push(&self, value: T) {
1727        self.state.update(|values| values.push(value));
1728    }
1729
1730    pub fn extend<I>(&self, iter: I)
1731    where
1732        I: IntoIterator<Item = T>,
1733    {
1734        self.state.update(|values| values.extend(iter));
1735    }
1736
1737    pub fn insert(&self, index: usize, value: T) {
1738        self.state.update(|values| values.insert(index, value));
1739    }
1740
1741    pub fn set(&self, index: usize, value: T) -> T {
1742        self.state
1743            .update(|values| std::mem::replace(&mut values[index], value))
1744    }
1745
1746    pub fn remove(&self, index: usize) -> T {
1747        self.state.update(|values| values.remove(index))
1748    }
1749
1750    pub fn pop(&self) -> Option<T> {
1751        self.state.update(|values| values.pop())
1752    }
1753
1754    pub fn clear(&self) {
1755        self.state.replace(Vec::new());
1756    }
1757
1758    pub fn retain<F>(&self, mut predicate: F)
1759    where
1760        F: FnMut(&T) -> bool,
1761    {
1762        self.state
1763            .update(|values| values.retain(|value| predicate(value)));
1764    }
1765
1766    pub fn replace_with<I>(&self, iter: I)
1767    where
1768        I: IntoIterator<Item = T>,
1769    {
1770        self.state.replace(iter.into_iter().collect());
1771    }
1772}
1773
1774impl<T: fmt::Debug + Clone + 'static> fmt::Debug for SnapshotStateList<T> {
1775    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1776        let contents = self.to_vec();
1777        f.debug_struct("SnapshotStateList")
1778            .field("values", &contents)
1779            .finish()
1780    }
1781}
1782
1783#[derive(Clone)]
1784pub struct SnapshotStateMap<K, V>
1785where
1786    K: Clone + Eq + Hash + 'static,
1787    V: Clone + 'static,
1788{
1789    state: OwnedMutableState<HashMap<K, V>>,
1790}
1791
1792impl<K, V> SnapshotStateMap<K, V>
1793where
1794    K: Clone + Eq + Hash + 'static,
1795    V: Clone + 'static,
1796{
1797    pub fn with_runtime<I>(pairs: I, runtime: RuntimeHandle) -> Self
1798    where
1799        I: IntoIterator<Item = (K, V)>,
1800    {
1801        let map: HashMap<K, V> = pairs.into_iter().collect();
1802        Self {
1803            state: OwnedMutableState::with_runtime(map, runtime),
1804        }
1805    }
1806
1807    pub fn as_state(&self) -> State<HashMap<K, V>> {
1808        self.state.as_state()
1809    }
1810
1811    pub fn as_mutable_state(&self) -> MutableState<HashMap<K, V>> {
1812        self.state.handle()
1813    }
1814
1815    pub fn len(&self) -> usize {
1816        self.state.with(|map| map.len())
1817    }
1818
1819    pub fn is_empty(&self) -> bool {
1820        self.state.with(|map| map.is_empty())
1821    }
1822
1823    pub fn contains_key(&self, key: &K) -> bool {
1824        self.state.with(|map| map.contains_key(key))
1825    }
1826
1827    pub fn get(&self, key: &K) -> Option<V> {
1828        self.state.with(|map| map.get(key).cloned())
1829    }
1830
1831    pub fn to_hash_map(&self) -> HashMap<K, V> {
1832        self.state.with(|map| map.clone())
1833    }
1834
1835    pub fn insert(&self, key: K, value: V) -> Option<V> {
1836        self.state.update(|map| map.insert(key, value))
1837    }
1838
1839    pub fn extend<I>(&self, iter: I)
1840    where
1841        I: IntoIterator<Item = (K, V)>,
1842    {
1843        self.state.update(|map| map.extend(iter));
1844    }
1845
1846    pub fn remove(&self, key: &K) -> Option<V> {
1847        self.state.update(|map| map.remove(key))
1848    }
1849
1850    pub fn clear(&self) {
1851        self.state.replace(HashMap::default());
1852    }
1853
1854    pub fn retain<F>(&self, mut predicate: F)
1855    where
1856        F: FnMut(&K, &mut V) -> bool,
1857    {
1858        self.state.update(|map| map.retain(|k, v| predicate(k, v)));
1859    }
1860}
1861
1862impl<K, V> fmt::Debug for SnapshotStateMap<K, V>
1863where
1864    K: Clone + Eq + Hash + fmt::Debug + 'static,
1865    V: Clone + fmt::Debug + 'static,
1866{
1867    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1868        let contents = self.to_hash_map();
1869        f.debug_struct("SnapshotStateMap")
1870            .field("entries", &contents)
1871            .finish()
1872    }
1873}
1874
1875pub(crate) struct DerivedState<T: Clone + 'static> {
1876    compute: Rc<dyn Fn() -> T>,
1877    pub(crate) state: OwnedMutableState<T>,
1878}
1879
1880impl<T: Clone + 'static> DerivedState<T> {
1881    pub(crate) fn new(runtime: RuntimeHandle, compute: Rc<dyn Fn() -> T>) -> Self {
1882        let initial = compute();
1883        Self {
1884            compute,
1885            state: OwnedMutableState::with_runtime(initial, runtime),
1886        }
1887    }
1888
1889    pub(crate) fn set_compute(&mut self, compute: Rc<dyn Fn() -> T>) {
1890        self.compute = compute;
1891    }
1892
1893    pub(crate) fn recompute(&self) {
1894        let value = (self.compute)();
1895        self.state.set_value(value);
1896    }
1897}
1898
1899impl<T: fmt::Debug + Clone + 'static> fmt::Debug for State<T> {
1900    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1901        if let Some(value) = self.try_value() {
1902            f.debug_struct("State").field("value", &value).finish()
1903        } else {
1904            f.write_str("State { value: <unavailable> }")
1905        }
1906    }
1907}
1908
1909#[cfg(test)]
1910mod tests {
1911    use super::*;
1912
1913    /// Helper to create a chain of records for testing
1914    fn create_record_chain(ids: &[SnapshotId]) -> Rc<StateRecord> {
1915        let mut head: Option<Rc<StateRecord>> = None;
1916
1917        // Build chain in reverse order (last ID becomes the tail)
1918        for &id in ids.iter().rev() {
1919            head = Some(StateRecord::new(id, 0i32, head));
1920        }
1921
1922        head.expect("create_record_chain called with empty ids")
1923    }
1924
1925    struct ManualState {
1926        head: Rc<StateRecord>,
1927    }
1928
1929    impl ManualState {
1930        fn new(head: Rc<StateRecord>) -> Self {
1931            Self { head }
1932        }
1933    }
1934
1935    impl StateObject for ManualState {
1936        fn object_id(&self) -> ObjectId {
1937            ObjectId(999)
1938        }
1939
1940        fn first_record(&self) -> Rc<StateRecord> {
1941            Rc::clone(&self.head)
1942        }
1943
1944        fn try_readable_record(&self, _: SnapshotId, _: &SnapshotIdSet) -> Option<Rc<StateRecord>> {
1945            Some(Rc::clone(&self.head))
1946        }
1947
1948        fn readable_record(&self, _: SnapshotId, _: &SnapshotIdSet) -> Rc<StateRecord> {
1949            Rc::clone(&self.head)
1950        }
1951
1952        fn prepend_state_record(&self, _: Rc<StateRecord>) {}
1953
1954        fn promote_record(&self, _: SnapshotId) -> Result<(), &'static str> {
1955            Ok(())
1956        }
1957
1958        fn as_any(&self) -> &dyn Any {
1959            self
1960        }
1961    }
1962
1963    fn poison_mutex<T>(mutex: &Mutex<T>) {
1964        let poison_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1965            let _guard = mutex
1966                .lock()
1967                .unwrap_or_else(|poisoned| poisoned.into_inner());
1968            panic!("poison snapshot state mutex for recovery test");
1969        }));
1970
1971        assert!(poison_result.is_err());
1972    }
1973
1974    #[test]
1975    fn snapshot_mutable_state_recovers_poisoned_weak_self_lock() {
1976        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
1977
1978        poison_mutex(&state.weak_self);
1979
1980        assert_eq!(state.get(), 100);
1981        assert!(state.set(101));
1982        assert_eq!(state.get(), 101);
1983    }
1984
1985    #[test]
1986    fn snapshot_mutable_state_recovers_poisoned_apply_observer_lock() {
1987        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
1988        let calls = Rc::new(Cell::new(0usize));
1989        let observed_calls = Rc::clone(&calls);
1990
1991        poison_mutex(&state.apply_observers);
1992
1993        state.add_apply_observer(Box::new(move || {
1994            observed_calls.set(observed_calls.get() + 1);
1995        }));
1996        state.notify_applied();
1997
1998        assert_eq!(calls.get(), 1);
1999    }
2000
2001    #[test]
2002    fn snapshot_mutable_state_promote_missing_record_returns_error() {
2003        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2004        let missing_snapshot = usize::MAX - 17;
2005
2006        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2007            StateObject::promote_record(&*state, missing_snapshot)
2008        }));
2009
2010        assert!(
2011            matches!(result, Ok(Err("missing child record"))),
2012            "missing child record should be reported through Result, got {result:?}"
2013        );
2014    }
2015
2016    #[test]
2017    fn snapshot_mutable_state_promote_wrong_record_type_returns_error() {
2018        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2019        let child_snapshot = usize::MAX - 31;
2020        let wrong_record = StateRecord::new(child_snapshot, "wrong type", None);
2021        StateObject::prepend_state_record(&*state, wrong_record);
2022
2023        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2024            StateObject::promote_record(&*state, child_snapshot)
2025        }));
2026
2027        assert!(
2028            matches!(result, Ok(Err("child record value missing or wrong type"))),
2029            "wrong child record type should be reported through Result, got {result:?}"
2030        );
2031    }
2032
2033    #[test]
2034    fn snapshot_mutable_state_commit_wrong_record_type_returns_error() {
2035        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2036        let merged = StateRecord::new(usize::MAX - 43, "wrong type", None);
2037
2038        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2039            StateObject::commit_merged_record(&*state, merged)
2040        }));
2041
2042        assert!(
2043            matches!(result, Ok(Err("merged record value missing or wrong type"))),
2044            "wrong merged record type should be reported through Result, got {result:?}"
2045        );
2046    }
2047
2048    #[test]
2049    fn snapshot_mutable_state_merge_wrong_record_type_returns_none() {
2050        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2051        let previous = StateRecord::new(usize::MAX - 51, 1i32, None);
2052        let current = StateRecord::new(usize::MAX - 52, "wrong type", None);
2053        let applied = StateRecord::new(usize::MAX - 53, 2i32, None);
2054
2055        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2056            StateObject::merge_records(&*state, previous, current, applied)
2057        }));
2058
2059        match result {
2060            Ok(None) => {}
2061            Ok(Some(_)) => panic!("wrong merge record type unexpectedly produced a merged record"),
2062            Err(_) => panic!("wrong merge record type should not panic"),
2063        }
2064    }
2065
2066    #[test]
2067    fn test_used_locked_finds_invalid_snapshot() {
2068        // Create a chain with an INVALID_SNAPSHOT record
2069        let tail = StateRecord::new(PREEXISTING_SNAPSHOT_ID, 0i32, None);
2070        let invalid_rec = StateRecord::new(INVALID_SNAPSHOT_ID, 0i32, Some(tail));
2071        let head = StateRecord::new(10, 0i32, Some(invalid_rec.clone()));
2072
2073        let result = used_locked(&head);
2074        assert!(result.is_some());
2075        assert_eq!(result.unwrap().snapshot_id(), INVALID_SNAPSHOT_ID);
2076    }
2077
2078    #[test]
2079    fn test_used_locked_finds_obscured_record() {
2080        // Reset pinning state for clean test
2081        crate::snapshot_pinning::reset_pinning_table();
2082
2083        // Pin a high snapshot to set a known reuse limit
2084        // This ensures records 2 and 5 are both below (reuse_limit = 10 - 1 = 9)
2085        let pin_handle = crate::snapshot_pinning::track_pinning(10, &SnapshotIdSet::EMPTY);
2086
2087        // Create a chain with two old records below the reuse limit
2088        let oldest = StateRecord::new(2, 0i32, None);
2089        let newer = StateRecord::new(5, 0i32, Some(oldest.clone()));
2090        let head = StateRecord::new(100, 0i32, Some(newer));
2091
2092        let result = used_locked(&head);
2093
2094        // Should find the older of the two records below reuse limit
2095        assert!(result.is_some());
2096        let reused = result.unwrap();
2097        assert_eq!(
2098            reused.snapshot_id(),
2099            2,
2100            "Should return the oldest obscured record"
2101        );
2102
2103        // Clean up
2104        crate::snapshot_pinning::release_pinning(pin_handle);
2105    }
2106
2107    #[test]
2108    fn test_used_locked_no_reusable_record() {
2109        // Reset pinning state
2110        crate::snapshot_pinning::reset_pinning_table();
2111
2112        // Create a chain where all records are recent (above reuse limit)
2113        // Use very high IDs to ensure they're above any reuse limit
2114        let high_id = allocate_record_id() + 1000;
2115        let head = create_record_chain(&[high_id, high_id + 1, high_id + 2]);
2116
2117        let result = used_locked(&head);
2118        assert!(
2119            result.is_none(),
2120            "Should find no reusable records when all are recent"
2121        );
2122    }
2123
2124    #[test]
2125    fn test_used_locked_single_old_record() {
2126        // Reset pinning state
2127        crate::snapshot_pinning::reset_pinning_table();
2128
2129        // Create a chain with only one old record (should not be reused)
2130        let old = StateRecord::new(2, 0i32, None);
2131        let head = StateRecord::new(100, 0i32, Some(old));
2132
2133        let result = used_locked(&head);
2134        // With only ONE record below reuse limit, it's still valid and should not be reused
2135        assert!(result.is_none(), "Single old record should not be reused");
2136    }
2137
2138    #[test]
2139    fn test_readable_record_for_preexisting() {
2140        let head = create_record_chain(&[PREEXISTING_SNAPSHOT_ID]);
2141        let invalid = SnapshotIdSet::EMPTY;
2142
2143        let result = readable_record_for(&head, 10, &invalid);
2144        assert!(result.is_some());
2145        assert_eq!(result.unwrap().snapshot_id(), PREEXISTING_SNAPSHOT_ID);
2146    }
2147
2148    #[test]
2149    fn test_readable_record_for_picks_highest_valid() {
2150        let head = create_record_chain(&[10, 5, PREEXISTING_SNAPSHOT_ID]);
2151        let invalid = SnapshotIdSet::EMPTY;
2152
2153        // Reading at snapshot 10 should return record 10
2154        let result = readable_record_for(&head, 10, &invalid);
2155        assert!(result.is_some());
2156        assert_eq!(result.unwrap().snapshot_id(), 10);
2157
2158        // Reading at snapshot 7 should skip record 10 and return record 5
2159        let result = readable_record_for(&head, 7, &invalid);
2160        assert!(result.is_some());
2161        assert_eq!(result.unwrap().snapshot_id(), 5);
2162    }
2163
2164    #[test]
2165    fn test_new_overwritable_record_locked_reuses_invalid() {
2166        // Create a state with an INVALID record in the chain
2167        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2168
2169        // Manually insert an INVALID record into the chain
2170        let current_head = state.first_record();
2171        let invalid_rec = StateRecord::new(INVALID_SNAPSHOT_ID, 0i32, current_head.next());
2172        current_head.set_next(Some(invalid_rec.clone()));
2173
2174        let result = new_overwritable_record_locked(&*state);
2175
2176        // Should reuse the INVALID record
2177        assert!(Rc::ptr_eq(&result, &invalid_rec));
2178        assert_eq!(result.snapshot_id(), SNAPSHOT_ID_MAX);
2179    }
2180
2181    #[test]
2182    fn test_new_overwritable_record_locked_creates_new() {
2183        crate::snapshot_pinning::reset_pinning_table();
2184
2185        // Pin snapshot 1 to prevent PREEXISTING (id=1) from being reusable
2186        // This ensures the reuse limit is above 1, so PREEXISTING won't be obscured
2187        let _pin_handle = crate::snapshot_pinning::track_pinning(1, &SnapshotIdSet::EMPTY);
2188
2189        // Create a state with all recent records (no reusable ones)
2190        let state = SnapshotMutableState::new_in_arc(100i32, Arc::new(NeverEqual));
2191        let old_head = state.first_record();
2192
2193        let result = new_overwritable_record_locked(&*state);
2194
2195        // Should create a new record
2196        assert_eq!(result.snapshot_id(), SNAPSHOT_ID_MAX);
2197
2198        // Should be prepended to the chain (becomes new head)
2199        let new_head = state.first_record();
2200        assert!(
2201            Rc::ptr_eq(&new_head, &result),
2202            "new_head ({:p}) should equal result ({:p})",
2203            Rc::as_ptr(&new_head),
2204            Rc::as_ptr(&result)
2205        );
2206
2207        // The new record should point to the old head
2208        assert!(result.next().is_some());
2209        assert!(Rc::ptr_eq(&result.next().unwrap(), &old_head));
2210    }
2211
2212    #[test]
2213    fn test_writable_record_reuses_invalid_record() {
2214        crate::snapshot_pinning::reset_pinning_table();
2215
2216        let state = SnapshotMutableState::new_in_arc(7i32, Arc::new(NeverEqual));
2217
2218        // Inject an INVALID record that should be reused on next write.
2219        let head = state.first_record();
2220        let invalid = StateRecord::new(INVALID_SNAPSHOT_ID, 0i32, head.next());
2221        head.set_next(Some(invalid.clone()));
2222
2223        let snapshot_id = allocate_record_id();
2224        let result = state.writable_record(snapshot_id, &SnapshotIdSet::EMPTY);
2225
2226        assert!(
2227            Rc::ptr_eq(&result, &invalid),
2228            "Expected writable_record to reuse the INVALID record"
2229        );
2230        assert_eq!(result.snapshot_id(), snapshot_id);
2231        result.with_value(|value: &i32| {
2232            assert_eq!(*value, 7, "Reused record should copy the readable value");
2233        });
2234        assert!(!result.is_tombstone());
2235    }
2236
2237    #[test]
2238    fn test_writable_record_creates_new_when_reuse_disallowed() {
2239        crate::snapshot_pinning::reset_pinning_table();
2240        let pin = crate::snapshot_pinning::track_pinning(1, &SnapshotIdSet::EMPTY);
2241
2242        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2243        let original_head = state.first_record();
2244        let preexisting = original_head
2245            .next()
2246            .expect("preexisting record should exist for newly created state");
2247
2248        let snapshot_id = allocate_record_id();
2249        let result = state.writable_record(snapshot_id, &SnapshotIdSet::EMPTY);
2250
2251        assert!(
2252            !Rc::ptr_eq(&result, &original_head),
2253            "Should not reuse the current head when reuse is disallowed"
2254        );
2255        assert!(
2256            !Rc::ptr_eq(&result, &preexisting),
2257            "Should not reuse the PREEXISTING record"
2258        );
2259        assert_eq!(result.snapshot_id(), snapshot_id);
2260        result.with_value(|value: &i32| assert_eq!(*value, 42));
2261
2262        let new_head = state.first_record();
2263        assert!(
2264            Rc::ptr_eq(&new_head, &result),
2265            "Newly created record should become the head of the chain"
2266        );
2267
2268        crate::snapshot_pinning::release_pinning(pin);
2269    }
2270
2271    #[test]
2272    fn test_state_record_clear_for_reuse() {
2273        let record = StateRecord::new(10, 42i32, None);
2274
2275        // Verify value exists before clearing
2276        record.with_value(|val: &i32| {
2277            assert_eq!(*val, 42);
2278        });
2279
2280        // Clear the record for reuse
2281        record.clear_for_reuse();
2282
2283        // Value should be cleared (will panic if we try to access it)
2284        // Just verify snapshot_id is unchanged
2285        assert_eq!(record.snapshot_id(), 10);
2286    }
2287
2288    #[test]
2289    fn test_overwrite_unused_records_no_old_records() {
2290        crate::snapshot_pinning::reset_pinning_table();
2291
2292        // Create state first to establish snapshot IDs
2293        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2294
2295        // Pin snapshot 1 so reuse limit is 1, making both initial records (1 and 2) above it
2296        // This ensures PREEXISTING won't be overwritten
2297        let _pin = crate::snapshot_pinning::track_pinning(1, &SnapshotIdSet::EMPTY);
2298
2299        let should_retain = state.overwrite_unused_records();
2300
2301        // With both records above/at reuse limit, we have 2 retained
2302        assert!(
2303            should_retain,
2304            "Should retain multiple records when none are old enough"
2305        );
2306
2307        // No records should be marked as INVALID
2308        let mut cursor = Some(state.first_record());
2309        while let Some(record) = cursor {
2310            assert_ne!(record.snapshot_id(), INVALID_SNAPSHOT_ID);
2311            cursor = record.next();
2312        }
2313    }
2314
2315    #[test]
2316    fn test_overwrite_unused_records_basic_cleanup() {
2317        // Test that old records get marked invalid when newer ones exist
2318        crate::snapshot_pinning::reset_pinning_table();
2319
2320        // Create simple manual chain to avoid snapshot ID allocation complexity
2321        let rec1 = StateRecord::new(100, 1i32, None);
2322        let rec2 = StateRecord::new(200, 2i32, Some(rec1.clone()));
2323        let rec3 = StateRecord::new(300, 3i32, Some(rec2.clone()));
2324
2325        // Mock state object for testing
2326        struct TestState {
2327            head: Rc<StateRecord>,
2328        }
2329        impl StateObject for TestState {
2330            fn object_id(&self) -> ObjectId {
2331                ObjectId(999)
2332            }
2333            fn first_record(&self) -> Rc<StateRecord> {
2334                Rc::clone(&self.head)
2335            }
2336            fn try_readable_record(
2337                &self,
2338                _: SnapshotId,
2339                _: &SnapshotIdSet,
2340            ) -> Option<Rc<StateRecord>> {
2341                Some(Rc::clone(&self.head))
2342            }
2343            fn readable_record(&self, _: SnapshotId, _: &SnapshotIdSet) -> Rc<StateRecord> {
2344                Rc::clone(&self.head)
2345            }
2346            fn prepend_state_record(&self, _: Rc<StateRecord>) {}
2347            fn promote_record(&self, _: SnapshotId) -> Result<(), &'static str> {
2348                Ok(())
2349            }
2350            fn as_any(&self) -> &dyn Any {
2351                self
2352            }
2353        }
2354
2355        let test_state = TestState { head: rec3.clone() };
2356
2357        // Pin at 1000 so all three records (100, 200, 300) are below reuse limit
2358        let _pin = crate::snapshot_pinning::track_pinning(1000, &SnapshotIdSet::EMPTY);
2359
2360        let result = overwrite_unused_records_locked::<i32>(&test_state);
2361
2362        // Should keep highest (300), mark others invalid
2363        assert_eq!(rec3.snapshot_id(), 300);
2364        assert_eq!(rec2.snapshot_id(), INVALID_SNAPSHOT_ID);
2365        assert_eq!(rec1.snapshot_id(), INVALID_SNAPSHOT_ID);
2366
2367        // Only one record retained (300), so should return false
2368        assert!(!result);
2369    }
2370
2371    #[test]
2372    fn test_overwrite_unused_records_single_record_only() {
2373        crate::snapshot_pinning::reset_pinning_table();
2374
2375        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2376
2377        // Remove the PREEXISTING record by setting next to None
2378        let head = state.first_record();
2379        head.set_next(None);
2380
2381        let should_retain = state.overwrite_unused_records();
2382
2383        // With only one record, should return false
2384        assert!(!should_retain, "Single record should return false");
2385    }
2386
2387    #[test]
2388    fn snapshot_state_try_get_reports_missing_visible_record_without_panicking() {
2389        crate::snapshot_pinning::reset_pinning_table();
2390
2391        let state = SnapshotMutableState::new_in_arc(42i32, Arc::new(NeverEqual));
2392        let head = state.first_record();
2393        head.set_snapshot_id(SNAPSHOT_ID_MAX);
2394        head.set_next(None);
2395
2396        assert_eq!(state.try_get(), None);
2397    }
2398
2399    #[test]
2400    fn test_overwrite_unused_records_clears_values() {
2401        crate::snapshot_pinning::reset_pinning_table();
2402
2403        let tail = StateRecord::new(PREEXISTING_SNAPSHOT_ID, 0i32, None);
2404        let old_rec1 = StateRecord::new(2, 999i32, Some(tail.clone()));
2405        let old_rec2 = StateRecord::new(3, 888i32, Some(old_rec1.clone()));
2406        let head = StateRecord::new(150, 42i32, Some(old_rec2.clone()));
2407        let state = ManualState::new(head.clone());
2408
2409        // Verify value exists before cleanup
2410        old_rec1.with_value(|val: &i32| {
2411            assert_eq!(*val, 999);
2412        });
2413
2414        let _pin = crate::snapshot_pinning::track_pinning(100, &SnapshotIdSet::EMPTY);
2415        overwrite_unused_records_locked::<i32>(&state);
2416
2417        // The invalidated record should have its value cleared
2418        assert_eq!(old_rec1.snapshot_id(), INVALID_SNAPSHOT_ID);
2419        // Value access would panic, so we just verify it was marked invalid
2420    }
2421
2422    #[test]
2423    fn test_overwrite_unused_records_mixed_old_and_new() {
2424        crate::snapshot_pinning::reset_pinning_table();
2425
2426        // Create mixed chain: recent (50) -> old (5) -> old (2) -> PREEXISTING
2427        let preexisting = StateRecord::new(PREEXISTING_SNAPSHOT_ID, 0i32, None);
2428        let rec2 = StateRecord::new(2, 100i32, Some(preexisting.clone()));
2429        let rec5 = StateRecord::new(5, 100i32, Some(rec2.clone()));
2430        let rec50 = StateRecord::new(50, 100i32, Some(rec5.clone()));
2431        let head = StateRecord::new(120, 100i32, Some(rec50.clone()));
2432        let state = ManualState::new(head.clone());
2433
2434        // Pin snapshot 40 so reuse limit is ~40, making 2 and 5 old but 50 recent
2435        let _pin = crate::snapshot_pinning::track_pinning(40, &SnapshotIdSet::EMPTY);
2436
2437        let should_retain = overwrite_unused_records_locked::<i32>(&state);
2438        assert!(should_retain);
2439
2440        // rec50 is above reuse limit - should stay valid
2441        assert_eq!(rec50.snapshot_id(), 50);
2442        // rec5 is highest below reuse limit - should stay valid
2443        assert_eq!(rec5.snapshot_id(), 5);
2444        // rec2 is older and below reuse limit - should be invalidated
2445        assert_eq!(rec2.snapshot_id(), INVALID_SNAPSHOT_ID);
2446    }
2447
2448    #[test]
2449    fn test_readable_record_for_skips_invalid_set() {
2450        let head = create_record_chain(&[10, 5, PREEXISTING_SNAPSHOT_ID]);
2451        let invalid = SnapshotIdSet::new().set(5);
2452
2453        // Reading at snapshot 10 should skip record 5 (in invalid set)
2454        let result = readable_record_for(&head, 10, &invalid);
2455        assert!(result.is_some());
2456        assert_eq!(result.unwrap().snapshot_id(), 10);
2457
2458        // Reading at snapshot 7 should skip 5 and fall back to PREEXISTING
2459        let result = readable_record_for(&head, 7, &invalid);
2460        assert!(result.is_some());
2461        assert_eq!(result.unwrap().snapshot_id(), PREEXISTING_SNAPSHOT_ID);
2462    }
2463
2464    // ========== Tests for assign_value() ==========
2465
2466    #[test]
2467    fn test_assign_value_copies_int() {
2468        let source = StateRecord::new(10, 42i32, None);
2469        let target = StateRecord::new(20, 0i32, None);
2470
2471        target.assign_value::<i32>(&source).expect("copy int value");
2472
2473        // Verify the value was copied
2474        target.with_value(|val: &i32| {
2475            assert_eq!(*val, 42);
2476        });
2477
2478        // Verify source is unchanged
2479        source.with_value(|val: &i32| {
2480            assert_eq!(*val, 42);
2481        });
2482
2483        // Verify snapshot IDs are unchanged
2484        assert_eq!(source.snapshot_id(), 10);
2485        assert_eq!(target.snapshot_id(), 20);
2486    }
2487
2488    #[test]
2489    fn test_assign_value_copies_string() {
2490        let source = StateRecord::new(10, "hello".to_string(), None);
2491        let target = StateRecord::new(20, "world".to_string(), None);
2492
2493        target
2494            .assign_value::<String>(&source)
2495            .expect("copy string value");
2496
2497        // Verify the value was copied
2498        target.with_value(|val: &String| {
2499            assert_eq!(val, "hello");
2500        });
2501
2502        // Verify source is unchanged
2503        source.with_value(|val: &String| {
2504            assert_eq!(val, "hello");
2505        });
2506    }
2507
2508    #[test]
2509    fn test_assign_value_reports_cleared_source() {
2510        let source = StateRecord::new(10, 42i32, None);
2511        let target = StateRecord::new(20, 0i32, None);
2512
2513        source.clear_value();
2514
2515        assert_eq!(
2516            target.assign_value::<i32>(&source),
2517            Err(StateRecordValueError::MissingOrWrongType {
2518                expected: std::any::type_name::<i32>(),
2519            })
2520        );
2521        assert_eq!(target.with_value(|val: &i32| *val), 0);
2522    }
2523
2524    #[test]
2525    fn test_assign_value_overwrites_existing_value() {
2526        let source = StateRecord::new(10, 100i32, None);
2527        let target = StateRecord::new(20, 999i32, None);
2528
2529        // Verify target has initial value
2530        target.with_value(|val: &i32| {
2531            assert_eq!(*val, 999);
2532        });
2533
2534        // Assign from source
2535        target
2536            .assign_value::<i32>(&source)
2537            .expect("overwrite int value");
2538
2539        // Verify target now has source's value
2540        target.with_value(|val: &i32| {
2541            assert_eq!(*val, 100);
2542        });
2543    }
2544
2545    #[test]
2546    fn test_assign_value_with_custom_type() {
2547        #[derive(Clone, PartialEq, Debug)]
2548        struct Point {
2549            x: f64,
2550            y: f64,
2551        }
2552
2553        let source = StateRecord::new(10, Point { x: 1.5, y: 2.5 }, None);
2554        let target = StateRecord::new(20, Point { x: 0.0, y: 0.0 }, None);
2555
2556        target
2557            .assign_value::<Point>(&source)
2558            .expect("copy point value");
2559
2560        target.with_value(|val: &Point| {
2561            assert_eq!(val, &Point { x: 1.5, y: 2.5 });
2562        });
2563    }
2564
2565    #[test]
2566    fn test_assign_value_self_assignment() {
2567        let record = StateRecord::new(10, 42i32, None);
2568
2569        // Self-assignment should work (though not useful in practice)
2570        record
2571            .assign_value::<i32>(&record)
2572            .expect("self-assign int value");
2573
2574        record.with_value(|val: &i32| {
2575            assert_eq!(*val, 42);
2576        });
2577    }
2578
2579    /// The user-visible leak behind "fps got lower and lower" during
2580    /// repeated text-handle drags: every pointer event writes drag state
2581    /// through `run_in_mutable_snapshot` (the `dispatch_ui_event` path).
2582    /// Each write must leave the record chain bounded — a growing chain
2583    /// makes every subsequent state read a longer linear scan, degrading
2584    /// every later frame.
2585    #[test]
2586    fn event_loop_writes_keep_the_record_chain_bounded() {
2587        crate::snapshot_pinning::reset_pinning_table();
2588        let state = SnapshotMutableState::new_in_arc(0.0f32, Arc::new(NeverEqual));
2589
2590        let mut lens = Vec::new();
2591        for event in 0..3000usize {
2592            crate::run_in_mutable_snapshot(|| {
2593                state.set(event as f32);
2594            })
2595            .expect("event snapshot applies");
2596            // The renderer reads the value from the global snapshot between
2597            // events (draw + layout consume the state each frame).
2598            let _ = state.get();
2599            if event % 500 == 499 {
2600                lens.push(state.record_chain_debug().len());
2601            }
2602        }
2603
2604        let final_len = *lens.last().expect("sampled chain lengths");
2605        assert!(
2606            final_len <= 16,
2607            "record chain grew without bound across event-loop writes: {lens:?}"
2608        );
2609    }
2610
2611    #[test]
2612    fn test_assign_value_with_vec() {
2613        let source = StateRecord::new(10, vec![1, 2, 3, 4, 5], None);
2614        let target = StateRecord::new(20, Vec::<i32>::new(), None);
2615
2616        target
2617            .assign_value::<Vec<i32>>(&source)
2618            .expect("copy vec value");
2619
2620        target.with_value(|val: &Vec<i32>| {
2621            assert_eq!(val, &vec![1, 2, 3, 4, 5]);
2622        });
2623
2624        // Verify it's a deep copy (modifying source won't affect target)
2625        source.replace_value(vec![10, 20]);
2626        target.with_value(|val: &Vec<i32>| {
2627            assert_eq!(val, &vec![1, 2, 3, 4, 5]);
2628        });
2629    }
2630}