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