Skip to main content

tessera_ui/
runtime.rs

1//! This module provides the global runtime state management for tessera.
2
3use std::{
4    any::{Any, TypeId},
5    cell::{Cell, RefCell},
6    collections::hash_map::Entry,
7    hash::{BuildHasher, Hash, Hasher},
8    marker::PhantomData,
9    sync::Arc,
10    time::Duration,
11};
12
13use hashbrown::{HashTable, hash_table::Entry as TableEntry};
14use parking_lot::RwLock;
15use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet};
16use slotmap::{SlotMap, new_key_type};
17use smallvec::SmallVec;
18
19use crate::{
20    NodeId,
21    accessibility::{AccessibilityActionHandler, AccessibilityNode},
22    component_call::ComponentCalls,
23    component_tree::{ComponentNode, ComponentTree, NodeRole, PreviousBuildNode},
24    context::{ContextGlobals, ContextMap},
25    execution_context::{
26        ExecutionContext, OrderFrame, with_execution_context, with_execution_context_mut,
27    },
28    focus::{
29        FocusDirection, FocusGroupNode, FocusHandleId, FocusNode, FocusProperties,
30        FocusRegistration, FocusRegistrationKind, FocusRequester, FocusRequesterId,
31        FocusRevealRequest, FocusScopeNode, FocusState, FocusTraversalPolicy,
32    },
33    inline_set::InlineSet,
34    layout::{DrawModifierHandle, LayoutModifierHandle},
35    modifier::Modifier,
36    prop::{CallbackWith, ErasedComponent, Prop},
37    read_dependencies::ReadDependencies,
38    session::{try_with_runtime_globals, with_runtime_globals},
39    time::Instant,
40};
41
42mod state_owner;
43
44pub use state_owner::StateOwner;
45
46#[derive(Clone, Copy)]
47enum OrderCounterKind {
48    Remember,
49    Functor,
50    Context,
51    FrameReceiver,
52    ModifierAttachment,
53}
54
55fn push_order_frame() {
56    with_execution_context_mut(|context| {
57        context.order_frame_stack.push(OrderFrame::default());
58    });
59}
60
61fn pop_order_frame(underflow_message: &str) {
62    with_execution_context_mut(|context| {
63        let popped = context.order_frame_stack.pop();
64        debug_assert!(popped.is_some(), "{underflow_message}");
65    });
66}
67
68fn next_order_counter(kind: OrderCounterKind, empty_message: &str) -> u64 {
69    with_execution_context_mut(|context| next_order_counter_in(context, kind, empty_message))
70}
71
72fn next_order_counter_in(
73    context: &mut ExecutionContext,
74    kind: OrderCounterKind,
75    empty_message: &str,
76) -> u64 {
77    debug_assert!(!context.order_frame_stack.is_empty(), "{empty_message}");
78    let frame = context.order_frame_stack.last_mut().expect(empty_message);
79    let counter = match kind {
80        OrderCounterKind::Remember => {
81            let counter = frame.remember;
82            frame.remember = frame.remember.wrapping_add(1);
83            counter
84        }
85        OrderCounterKind::Functor => {
86            let counter = frame.functor;
87            frame.functor = frame.functor.wrapping_add(1);
88            counter
89        }
90        OrderCounterKind::Context => {
91            let counter = frame.context;
92            frame.context = frame.context.wrapping_add(1);
93            counter
94        }
95        OrderCounterKind::ModifierAttachment => {
96            let counter = frame.modifier_attachment;
97            frame.modifier_attachment = frame
98                .modifier_attachment
99                .checked_add(1)
100                .expect("modifier attachment counter exhausted");
101            counter
102        }
103        OrderCounterKind::FrameReceiver => {
104            let counter = frame.frame_receiver;
105            frame.frame_receiver = frame.frame_receiver.wrapping_add(1);
106            counter
107        }
108    };
109    salt_order_counter(frame.occurrence_salt, counter)
110}
111
112fn salt_order_counter(salt: u64, counter: u64) -> u64 {
113    if salt == 0 {
114        counter
115    } else {
116        hash_components(&[&salt, &counter])
117    }
118}
119
120fn next_child_instance_call_index(context: &mut ExecutionContext) -> u64 {
121    let Some(frame) = context.order_frame_stack.last_mut() else {
122        return 0;
123    };
124    let index = frame.instance;
125    frame.instance = frame.instance.wrapping_add(1);
126    salt_order_counter(frame.occurrence_salt, index)
127}
128
129pub(crate) fn compute_context_slot_key() -> (u64, u64) {
130    let instance_logic_id = current_instance_logic_id();
131    let group_path_hash = current_group_path_hash();
132
133    let call_counter = next_order_counter(
134        OrderCounterKind::Context,
135        "ORDER_FRAME_STACK is empty; provide_context must be called inside a component",
136    );
137
138    let slot_hash = hash_components(&[&group_path_hash, &call_counter]);
139    (instance_logic_id, slot_hash)
140}
141
142#[derive(Hash, Eq, PartialEq, Clone, Copy)]
143struct SlotKey {
144    owned_owner: Option<u128>,
145    instance_logic_id: u64,
146    slot_hash: u64,
147    type_id: TypeId,
148}
149
150impl Default for SlotKey {
151    fn default() -> Self {
152        Self {
153            owned_owner: None,
154            instance_logic_id: 0,
155            slot_hash: 0,
156            type_id: TypeId::of::<()>(),
157        }
158    }
159}
160
161new_key_type! {
162    struct SlotHandle;
163}
164
165#[derive(Default)]
166struct SlotEntry {
167    key: SlotKey,
168    generation: u64,
169    value: Option<Arc<dyn Any + Send + Sync>>,
170    last_alive_epoch: u64,
171    retained: bool,
172}
173
174impl SlotEntry {
175    fn reuse_functor<T: Send + Sync + 'static>(
176        &mut self,
177        slot: SlotHandle,
178        epoch: u64,
179        init: &mut Option<impl FnOnce() -> T>,
180    ) -> (Arc<T>, u64) {
181        debug_assert_eq!(self.key.type_id, TypeId::of::<T>());
182        self.last_alive_epoch = epoch;
183        let value = self.value.get_or_insert_with(|| {
184            let value = Arc::new(init.take().expect("functor slot initialized once")());
185            self.generation = self.generation.wrapping_add(1);
186            value
187        });
188        (clone_slot_value(value, slot), self.generation)
189    }
190}
191
192#[derive(Default)]
193struct InstanceSlotCursor {
194    previous_order: SmallVec<[SlotHandle; 1]>,
195    current_order: SmallVec<[SlotHandle; 1]>,
196    cursor: usize,
197    epoch: u64,
198}
199
200impl InstanceSlotCursor {
201    fn begin_epoch(&mut self, epoch: u64) {
202        if self.epoch == epoch {
203            return;
204        }
205        std::mem::swap(&mut self.previous_order, &mut self.current_order);
206        if self.current_order.capacity() > self.previous_order.len().saturating_mul(2).max(4) {
207            self.current_order = SmallVec::new();
208        } else {
209            self.current_order.clear();
210        }
211        self.cursor = 0;
212        self.epoch = epoch;
213    }
214
215    fn fast_candidate(&self) -> Option<SlotHandle> {
216        self.previous_order.get(self.cursor).copied()
217    }
218
219    fn record_fast_match(&mut self, slot: SlotHandle) {
220        self.cursor = self.cursor.saturating_add(1);
221        self.current_order.push(slot);
222    }
223
224    fn record_slow_match(&mut self, slot: SlotHandle) {
225        if self.cursor < self.previous_order.len()
226            && let Some(offset) = self.previous_order[self.cursor..]
227                .iter()
228                .position(|candidate| *candidate == slot)
229        {
230            self.cursor += offset + 1;
231        }
232        self.current_order.push(slot);
233    }
234}
235
236#[derive(Default)]
237struct SlotTable {
238    owned_slots: HashMap<u128, HashSet<SlotHandle>>,
239    entries: SlotMap<SlotHandle, SlotEntry>,
240    slots_by_key: HashTable<SlotHandle>,
241    slots_by_owner: HashMap<u64, InlineSet<SlotHandle>>,
242    cursors_by_instance_logic_id: HashMap<u64, InstanceSlotCursor>,
243    epoch: u64,
244}
245impl SlotTable {
246    fn find_slot(&self, key: &SlotKey) -> Option<SlotHandle> {
247        self.slots_by_key
248            .find(FxBuildHasher.hash_one(key), |&slot| {
249                self.entries[slot].key == *key
250            })
251            .copied()
252    }
253
254    fn index_slot(&mut self, slot: SlotHandle) {
255        let key = &self.entries[slot].key;
256        self.slots_by_key
257            .insert_unique(FxBuildHasher.hash_one(key), slot, |&slot| {
258                FxBuildHasher.hash_one(&self.entries[slot].key)
259            });
260        if let Some(owner) = key.owned_owner {
261            self.owned_slots.entry(owner).or_default().insert(slot);
262            return;
263        }
264        InlineSet::insert_at(self.slots_by_owner.entry(key.instance_logic_id), slot);
265    }
266
267    fn remove_slot(&mut self, slot: SlotHandle) -> Option<SlotEntry> {
268        let entry = self.entries.remove(slot)?;
269        if let Ok(indexed) = self
270            .slots_by_key
271            .find_entry(FxBuildHasher.hash_one(&entry.key), |&candidate| {
272                candidate == slot
273            })
274        {
275            indexed.remove();
276        }
277        if let Some(owner) = entry.key.owned_owner {
278            if let Some(slots) = self.owned_slots.get_mut(&owner) {
279                slots.remove(&slot);
280                if slots.is_empty() {
281                    self.owned_slots.remove(&owner);
282                }
283            }
284            return Some(entry);
285        }
286        if let Entry::Occupied(mut slots) = self.slots_by_owner.entry(entry.key.instance_logic_id)
287            && slots.get_mut().remove_and_is_empty(&slot)
288        {
289            slots.remove();
290        }
291        Some(entry)
292    }
293
294    fn slots_for_instances<'a>(
295        &'a self,
296        instance_logic_ids: &'a HashSet<u64>,
297    ) -> impl Iterator<Item = SlotHandle> + 'a {
298        instance_logic_ids
299            .iter()
300            .filter_map(|id| self.slots_by_owner.get(id))
301            .flat_map(InlineSet::iter)
302    }
303
304    fn begin_epoch(&mut self) {
305        self.epoch = self.epoch.wrapping_add(1);
306    }
307
308    fn reset(&mut self) -> Vec<SlotEntry> {
309        let removed = self.entries.drain().map(|(_, entry)| entry).collect();
310        self.slots_by_key.clear();
311        self.slots_by_owner.clear();
312        self.owned_slots.clear();
313        self.cursors_by_instance_logic_id.clear();
314        self.epoch = 0;
315        removed
316    }
317    fn try_fast_slot_lookup(&mut self, key: &SlotKey) -> Option<(SlotHandle, &mut SlotEntry)> {
318        let cursor = self
319            .cursors_by_instance_logic_id
320            .entry(key.instance_logic_id)
321            .or_default();
322        cursor.begin_epoch(self.epoch);
323        let candidate = cursor.fast_candidate()?;
324
325        let entry = self
326            .entries
327            .get_mut(candidate)
328            .filter(|entry| entry.key == *key)?;
329
330        cursor.record_fast_match(candidate);
331        Some((candidate, entry))
332    }
333
334    fn record_slot_usage_slow(&mut self, instance_logic_id: u64, slot: SlotHandle) {
335        let epoch = self.epoch;
336        let cursor = self
337            .cursors_by_instance_logic_id
338            .entry(instance_logic_id)
339            .or_default();
340        cursor.begin_epoch(epoch);
341        cursor.record_slow_match(slot);
342    }
343}
344
345#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
346struct PersistentFocusHandleKey {
347    instance_key: u64,
348    slot_hash: u64,
349}
350
351#[derive(Clone, Copy)]
352struct PersistentFocusHandleEntry<T> {
353    value: T,
354    missing_frames: u8,
355}
356
357impl<T: Copy> PersistentFocusHandleEntry<T> {
358    fn new(value: T) -> Self {
359        Self {
360            value,
361            missing_frames: 0,
362        }
363    }
364
365    fn mark_live(&mut self) -> T {
366        self.missing_frames = 0;
367        self.value
368    }
369
370    fn retain_for_frame(&mut self) -> bool {
371        if self.missing_frames == 0 {
372            self.missing_frames = 1;
373            true
374        } else {
375            false
376        }
377    }
378}
379
380#[derive(Default)]
381struct PersistentFocusHandleStore {
382    keys_by_owner: HashMap<u64, HashSet<PersistentFocusHandleKey>>,
383    prune_candidates: HashSet<u64>,
384    live_handles: HashSet<FocusHandleId>,
385    targets: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusNode>>,
386    scopes: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusScopeNode>>,
387    groups: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusGroupNode>>,
388    requesters: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusRequester>>,
389}
390
391#[derive(Default)]
392pub(crate) struct RemovedPersistentFocusHandles {
393    pub handle_ids: HashSet<FocusHandleId>,
394    pub requester_ids: HashSet<FocusRequesterId>,
395}
396
397impl PersistentFocusHandleStore {
398    fn prune_removed(
399        &mut self,
400        removed_owners: HashSet<u64>,
401        is_live: impl Fn(u64) -> bool,
402    ) -> RemovedPersistentFocusHandles {
403        self.prune_candidates.extend(removed_owners);
404        let mut removed = RemovedPersistentFocusHandles::default();
405        for owner in std::mem::take(&mut self.prune_candidates) {
406            let live = is_live(owner);
407            let Some(keys) = self.keys_by_owner.get(&owner) else {
408                continue;
409            };
410            let mut retained = false;
411            for &key in keys {
412                if let Some(value) = prune_focus_entry(&mut self.targets, key, live) {
413                    removed.handle_ids.insert(value.handle_id());
414                }
415                if let Some(value) = prune_focus_entry(&mut self.scopes, key, live) {
416                    removed.handle_ids.insert(value.handle_id());
417                }
418                if let Some(value) = prune_focus_entry(&mut self.groups, key, live) {
419                    removed.handle_ids.insert(value.handle_id());
420                }
421                if let Some(value) = prune_focus_entry(&mut self.requesters, key, live) {
422                    removed.requester_ids.insert(value.requester_id());
423                }
424                retained |= self.targets.contains_key(&key)
425                    || self.scopes.contains_key(&key)
426                    || self.groups.contains_key(&key)
427                    || self.requesters.contains_key(&key);
428            }
429            if !live && retained {
430                self.prune_candidates.insert(owner);
431            } else if !retained {
432                self.keys_by_owner.remove(&owner);
433            }
434        }
435        for handle in &removed.handle_ids {
436            self.live_handles.remove(handle);
437        }
438        removed
439    }
440
441    fn contains_handle(&self, handle_id: FocusHandleId) -> bool {
442        self.live_handles.contains(&handle_id)
443    }
444
445    fn clear(&mut self) {
446        self.keys_by_owner.clear();
447        self.prune_candidates.clear();
448        self.live_handles.clear();
449        self.targets.clear();
450        self.scopes.clear();
451        self.groups.clear();
452        self.requesters.clear();
453    }
454}
455
456fn prune_focus_entry<T: Copy>(
457    entries: &mut HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<T>>,
458    key: PersistentFocusHandleKey,
459    live: bool,
460) -> Option<T> {
461    let entry = entries.get_mut(&key)?;
462    if live {
463        entry.mark_live();
464        return None;
465    }
466    if entry.retain_for_frame() {
467        return None;
468    }
469    entries.remove(&key).map(|entry| entry.value)
470}
471
472fn with_slot_table<R>(f: impl FnOnce(&SlotTable) -> R) -> R {
473    with_runtime_globals(|globals| f(&globals.slot_table.borrow()))
474}
475
476fn with_slot_table_mut<R>(f: impl FnOnce(&mut SlotTable) -> R) -> R {
477    with_runtime_globals(|globals| f(&mut globals.slot_table.borrow_mut()))
478}
479
480#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
481pub(crate) struct FunctorHandle {
482    session: u128,
483    slot: SlotHandle,
484    generation: u64,
485}
486
487impl FunctorHandle {
488    fn new(slot: SlotHandle, generation: u64) -> Self {
489        Self {
490            session: crate::session::current_session_identity().expect("active functor session"),
491            slot,
492            generation,
493        }
494    }
495}
496
497struct CallbackCell {
498    current: RwLock<Arc<dyn Fn() + Send + Sync>>,
499}
500
501impl CallbackCell {
502    fn new(current: Arc<dyn Fn() + Send + Sync>) -> Self {
503        Self {
504            current: RwLock::new(current),
505        }
506    }
507
508    fn update(&self, next: Arc<dyn Fn() + Send + Sync>) {
509        let previous = std::mem::replace(&mut *self.current.write(), next);
510        drop(previous);
511    }
512
513    fn shared(&self) -> Arc<dyn Fn() + Send + Sync> {
514        Arc::clone(&self.current.read())
515    }
516}
517
518struct CallbackWithCell<T, R> {
519    current: RwLock<Arc<dyn Fn(T) -> R + Send + Sync>>,
520}
521
522impl<T, R> CallbackWithCell<T, R> {
523    fn new(current: Arc<dyn Fn(T) -> R + Send + Sync>) -> Self {
524        Self {
525            current: RwLock::new(current),
526        }
527    }
528
529    fn update(&self, next: Arc<dyn Fn(T) -> R + Send + Sync>) {
530        let previous = std::mem::replace(&mut *self.current.write(), next);
531        drop(previous);
532    }
533
534    fn shared(&self) -> Arc<dyn Fn(T) -> R + Send + Sync> {
535        Arc::clone(&self.current.read())
536    }
537}
538
539struct RenderSlotCell {
540    current: RwLock<(Arc<dyn Fn() + Send + Sync>, Option<TypeId>)>,
541}
542
543impl RenderSlotCell {
544    fn new(current: Arc<dyn Fn() + Send + Sync>, static_type: Option<TypeId>) -> Self {
545        Self {
546            current: RwLock::new((current, static_type)),
547        }
548    }
549
550    fn update(&self, next: Arc<dyn Fn() + Send + Sync>, static_type: Option<TypeId>) -> bool {
551        let previous = {
552            let mut current = self.current.write();
553            // A new allocation alone must not invalidate unchanged capture-free content.
554            if static_type.is_some() && current.1 == static_type {
555                return false;
556            }
557            std::mem::replace(&mut *current, (next, static_type))
558        };
559        drop(previous);
560        true
561    }
562
563    fn shared(&self) -> Arc<dyn Fn() + Send + Sync> {
564        Arc::clone(&self.current.read().0)
565    }
566}
567
568struct RenderSlotWithCell<T> {
569    current: RwLock<(Arc<dyn Fn(T) + Send + Sync>, Option<TypeId>)>,
570}
571
572impl<T> RenderSlotWithCell<T> {
573    fn new(current: Arc<dyn Fn(T) + Send + Sync>, static_type: Option<TypeId>) -> Self {
574        Self {
575            current: RwLock::new((current, static_type)),
576        }
577    }
578
579    fn update(&self, next: Arc<dyn Fn(T) + Send + Sync>, static_type: Option<TypeId>) -> bool {
580        let previous = {
581            let mut current = self.current.write();
582            // Closure type still distinguishes different capture-free bodies at one slot.
583            if static_type.is_some() && current.1 == static_type {
584                return false;
585            }
586            std::mem::replace(&mut *current, (next, static_type))
587        };
588        drop(previous);
589        true
590    }
591
592    fn shared(&self) -> Arc<dyn Fn(T) + Send + Sync> {
593        Arc::clone(&self.current.read().0)
594    }
595}
596
597#[derive(Default)]
598struct LayoutDirtyTracker {
599    pending_measure_self_dirty_nodes: HashSet<u64>,
600    ready_measure_self_dirty_nodes: HashSet<u64>,
601    pending_placement_self_dirty_nodes: HashSet<u64>,
602    ready_placement_self_dirty_nodes: HashSet<u64>,
603    previous_children_by_node: HashMap<u64, Box<[u64]>>,
604    structural_changes: StructureReconcileResult,
605    pending_parent_data_dirty_nodes: HashSet<u64>,
606    pending_transform_dirty_nodes: HashSet<u64>,
607    pending_paint_dirty_nodes: HashSet<u64>,
608    pending_pointer_dirty_nodes: HashSet<u64>,
609    state_reads: ReadDependencies<StateReadDependencyKey, PhaseReader>,
610}
611
612#[derive(Clone, Copy, Hash, PartialEq, Eq)]
613pub(crate) enum ReadPhase {
614    Pointer,
615    Measure,
616    Place,
617    Transform,
618    Paint,
619}
620
621#[derive(Clone, Copy, Hash, PartialEq, Eq)]
622struct PhaseReader {
623    instance_key: u64,
624    phase: ReadPhase,
625}
626
627#[derive(Default)]
628pub(crate) struct LayoutDirtyNodes {
629    pub measure_self_nodes: HashSet<u64>,
630    pub placement_self_nodes: HashSet<u64>,
631    pub parent_data_nodes: HashSet<u64>,
632    pub transform_nodes: HashSet<u64>,
633    pub paint_nodes: HashSet<u64>,
634}
635
636#[derive(Default)]
637pub(crate) struct StructureReconcileResult {
638    pub changed_nodes: HashSet<u64>,
639    pub removed_nodes: HashSet<u64>,
640}
641
642/// Persisted replay snapshot for one component instance.
643#[derive(Clone)]
644pub(crate) struct ReplayNodeSnapshot {
645    pub instance_logic_id: u64,
646    pub group_path: GroupPath,
647    pub instance_key_override: Option<u64>,
648    pub context: ContextMap,
649    pub replay: Arc<dyn ErasedComponent>,
650}
651
652#[derive(Default)]
653struct ReplaySnapshots {
654    entries: Vec<(u64, ReplayNodeSnapshot)>,
655    indices: HashTable<usize>,
656    next_ordered: Cell<usize>,
657}
658
659impl ReplaySnapshots {
660    fn index_of(&self, key: &u64) -> Option<usize> {
661        self.indices
662            .find(FxBuildHasher.hash_one(key), |&index| {
663                self.entries[index].0 == *key
664            })
665            .copied()
666    }
667
668    fn get(&self, key: &u64) -> Option<&ReplayNodeSnapshot> {
669        Some(&self.entries[self.index_of(key)?].1)
670    }
671
672    fn get_for_replay_root(&self, key: &u64) -> Option<&ReplayNodeSnapshot> {
673        let index = self.index_of(key)?;
674        self.next_ordered.set(index);
675        Some(&self.entries[index].1)
676    }
677
678    fn get_ordered(&self, key: &u64) -> Option<&ReplayNodeSnapshot> {
679        let cursor = self.next_ordered.get();
680        // Replay follows prior declaration order until a subtree is retained or reordered.
681        let index = if self
682            .entries
683            .get(cursor)
684            .is_some_and(|entry| entry.0 == *key)
685        {
686            cursor
687        } else {
688            self.index_of(key)?
689        };
690        self.next_ordered.set(index + 1);
691        Some(&self.entries[index].1)
692    }
693
694    fn insert(&mut self, key: u64, snapshot: ReplayNodeSnapshot) -> Option<ReplayNodeSnapshot> {
695        let entry = self.indices.entry(
696            FxBuildHasher.hash_one(key),
697            |&index| self.entries[index].0 == key,
698            |&index| FxBuildHasher.hash_one(self.entries[index].0),
699        );
700        match entry {
701            TableEntry::Occupied(entry) => Some(std::mem::replace(
702                &mut self.entries[*entry.get()].1,
703                snapshot,
704            )),
705            TableEntry::Vacant(entry) => {
706                let index = self.entries.len();
707                self.entries.push((key, snapshot));
708                entry.insert(index);
709                None
710            }
711        }
712    }
713
714    fn remove(&mut self, key: &u64) -> Option<ReplayNodeSnapshot> {
715        let entry = self
716            .indices
717            .find_entry(FxBuildHasher.hash_one(key), |&index| {
718                self.entries[index].0 == *key
719            })
720            .ok()?;
721        let (index, _) = entry.remove();
722        let (_, snapshot) = self.entries.swap_remove(index);
723        if let Some((moved_key, _)) = self.entries.get(index) {
724            let previous_index = self.entries.len();
725            let mut moved = self
726                .indices
727                .find_entry(FxBuildHasher.hash_one(moved_key), |&index| {
728                    index == previous_index
729                })
730                .unwrap_or_else(|_| unreachable!("moved replay snapshot must be indexed"));
731            *moved.get_mut() = index;
732        }
733        Some(snapshot)
734    }
735
736    fn drain(&mut self) -> impl Iterator<Item = (u64, ReplayNodeSnapshot)> + '_ {
737        self.indices.clear();
738        self.entries.drain(..)
739    }
740}
741
742#[derive(Default)]
743struct ComponentReplayTracker {
744    previous_nodes: ReplaySnapshots,
745    current_nodes: ReplaySnapshots,
746    mode: ReplayTrackingMode,
747}
748
749#[derive(Default, PartialEq, Eq)]
750pub(crate) enum ReplayTrackingMode {
751    #[default]
752    Full,
753    Partial,
754}
755
756fn with_component_replay_tracker<R>(f: impl FnOnce(&ComponentReplayTracker) -> R) -> R {
757    with_runtime_globals(|globals| f(&globals.component_replay_tracker.borrow()))
758}
759
760fn with_component_replay_tracker_mut<R>(f: impl FnOnce(&mut ComponentReplayTracker) -> R) -> R {
761    with_runtime_globals(|globals| f(&mut globals.component_replay_tracker.borrow_mut()))
762}
763
764pub(crate) fn begin_frame_component_replay_tracking(mode: ReplayTrackingMode) {
765    let retired = with_component_replay_tracker_mut(|tracker| {
766        tracker.mode = mode;
767        tracker.previous_nodes.next_ordered.set(0);
768        (!tracker.current_nodes.entries.is_empty())
769            .then(|| std::mem::take(&mut tracker.current_nodes))
770    });
771    drop(retired);
772}
773
774pub(crate) fn finalize_frame_component_replay_tracking() {
775    let retired = with_component_replay_tracker_mut(|tracker| {
776        std::mem::replace(
777            &mut tracker.previous_nodes,
778            std::mem::take(&mut tracker.current_nodes),
779        )
780    });
781    drop(retired);
782}
783
784pub(crate) fn finalize_frame_component_replay_tracking_partial() {
785    let retired = with_component_replay_tracker_mut(|tracker| {
786        tracker.mode = ReplayTrackingMode::Full;
787        tracker
788            .current_nodes
789            .drain()
790            .filter_map(|(key, snapshot)| tracker.previous_nodes.insert(key, snapshot))
791            // Only props own user data. Release those after the borrow ends.
792            .map(|snapshot| snapshot.replay)
793            .collect::<Vec<_>>()
794    });
795    drop(retired);
796}
797
798pub(crate) fn reset_component_replay_tracking() {
799    let removed = with_component_replay_tracker_mut(std::mem::take);
800    drop(removed);
801}
802
803pub(crate) fn previous_component_replay_node(key: u64) -> Option<ReplayNodeSnapshot> {
804    with_component_replay_tracker(|tracker| {
805        tracker.previous_nodes.get_for_replay_root(&key).cloned()
806    })
807}
808
809pub(crate) fn previous_component_context(key: u64) -> Option<ContextMap> {
810    with_component_replay_tracker(|tracker| {
811        tracker
812            .previous_nodes
813            .get(&key)
814            .map(|node| node.context.clone())
815    })
816}
817
818pub(crate) fn remove_previous_component_replay_nodes(instance_keys: &HashSet<u64>) {
819    if instance_keys.is_empty() {
820        return;
821    }
822    let retired = with_component_replay_tracker_mut(|tracker| {
823        let mut retired = Vec::new();
824        for key in instance_keys {
825            retired.extend(tracker.previous_nodes.remove(key));
826            retired.extend(tracker.current_nodes.remove(key));
827        }
828        retired
829    });
830    drop(retired);
831}
832
833#[derive(Default)]
834struct BuildInvalidationTracker {
835    dirty_instance_keys: HashSet<u64>,
836    cross_instance_dirty_instance_keys: HashSet<u64>,
837    deferred_instance_keys: HashSet<u64>,
838}
839
840impl BuildInvalidationTracker {
841    fn invalidate(&mut self, readers: impl IntoIterator<Item = u64>, writer: Option<u64>) -> bool {
842        let mut wake = false;
843        for reader in readers {
844            if self.deferred_instance_keys.contains(&reader) {
845                continue;
846            }
847            wake |= self.dirty_instance_keys.insert(reader);
848            if writer.is_some_and(|writer| writer != reader) {
849                self.cross_instance_dirty_instance_keys.insert(reader);
850                wake = true;
851            }
852        }
853        wake
854    }
855}
856
857#[derive(Default)]
858pub(crate) struct BuildInvalidationSet {
859    pub dirty_instance_keys: HashSet<u64>,
860    pub cross_instance_dirty_instance_keys: HashSet<u64>,
861}
862
863#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
864struct StateReadDependencyKey {
865    // Keep generation in the dependency key to avoid ABA when a slot is
866    // recycled and later reused for another state value.
867    slot: SlotHandle,
868    generation: u64,
869}
870
871#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
872struct FocusReadDependencyKey {
873    kind: FocusReadDependencyKind,
874}
875
876#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
877enum FocusReadDependencyKind {
878    Handle(FocusHandleId),
879    Requester(FocusRequesterId),
880}
881
882type StateReadDependencyTracker = ReadDependencies<StateReadDependencyKey, u64>;
883
884type FocusReadDependencyTracker = ReadDependencies<FocusReadDependencyKey, u64>;
885
886type RenderSlotReadDependencyTracker = ReadDependencies<FunctorHandle, u64>;
887
888type RedrawWaker = Arc<dyn Fn() + Send + Sync + 'static>;
889
890fn with_build_invalidation_tracker<R>(f: impl FnOnce(&BuildInvalidationTracker) -> R) -> R {
891    with_runtime_globals(|globals| f(&globals.build_invalidation_tracker.borrow()))
892}
893
894fn with_build_invalidation_tracker_mut<R>(f: impl FnOnce(&mut BuildInvalidationTracker) -> R) -> R {
895    with_runtime_globals(|globals| f(&mut globals.build_invalidation_tracker.borrow_mut()))
896}
897
898fn with_state_read_dependency_tracker_mut<R>(
899    f: impl FnOnce(&mut StateReadDependencyTracker) -> R,
900) -> R {
901    with_runtime_globals(|globals| f(&mut globals.state_read_dependency_tracker.borrow_mut()))
902}
903
904fn with_focus_read_dependency_tracker<R>(f: impl FnOnce(&FocusReadDependencyTracker) -> R) -> R {
905    with_runtime_globals(|globals| f(&globals.focus_read_dependency_tracker.borrow()))
906}
907
908fn with_focus_read_dependency_tracker_mut<R>(
909    f: impl FnOnce(&mut FocusReadDependencyTracker) -> R,
910) -> R {
911    with_runtime_globals(|globals| f(&mut globals.focus_read_dependency_tracker.borrow_mut()))
912}
913
914fn with_render_slot_read_dependency_tracker_mut<R>(
915    f: impl FnOnce(&mut RenderSlotReadDependencyTracker) -> R,
916) -> R {
917    with_runtime_globals(|globals| f(&mut globals.render_slot_read_dependency_tracker.borrow_mut()))
918}
919
920fn with_redraw_waker<R>(f: impl FnOnce(&Option<RedrawWaker>) -> R) -> R {
921    with_runtime_globals(|globals| f(&globals.redraw_waker.borrow()))
922}
923
924fn with_redraw_waker_mut<R>(f: impl FnOnce(&mut Option<RedrawWaker>) -> R) -> R {
925    with_runtime_globals(|globals| f(&mut globals.redraw_waker.borrow_mut()))
926}
927
928pub(crate) fn schedule_runtime_redraw() {
929    let callback = with_redraw_waker(Clone::clone);
930    if let Some(callback) = callback {
931        callback();
932    }
933}
934
935pub(crate) fn install_redraw_waker(callback: RedrawWaker) {
936    crate::dispatcher::set_waker(Some(callback.clone()));
937    let previous = with_redraw_waker_mut(|waker| waker.replace(callback));
938    drop(previous);
939}
940
941pub(crate) fn clear_redraw_waker() {
942    crate::dispatcher::set_waker(None);
943    let removed = with_redraw_waker_mut(Option::take);
944    drop(removed);
945}
946
947pub(crate) fn current_replay_boundary_instance_key_from_scope() -> Option<u64> {
948    with_execution_context(|context| context.current_component_instance_stack.last().copied())
949}
950
951fn with_persistent_focus_handle_store<R>(f: impl FnOnce(&PersistentFocusHandleStore) -> R) -> R {
952    with_runtime_globals(|globals| f(&globals.persistent_focus_handle_store.borrow()))
953}
954
955fn with_persistent_focus_handle_store_mut<R>(
956    f: impl FnOnce(&mut PersistentFocusHandleStore) -> R,
957) -> R {
958    with_runtime_globals(|globals| f(&mut globals.persistent_focus_handle_store.borrow_mut()))
959}
960
961fn current_persistent_focus_handle_key<K: Hash>(slot_key: K) -> PersistentFocusHandleKey {
962    let Some(instance_key) = current_replay_boundary_instance_key_from_scope() else {
963        panic!("persistent focus handles must be requested during a component build");
964    };
965    let slot_hash = hash_components(&[&slot_key]);
966    PersistentFocusHandleKey {
967        instance_key,
968        slot_hash,
969    }
970}
971
972pub(crate) fn persistent_focus_target_for_current_instance<K: Hash>(slot_key: K) -> FocusNode {
973    let key = current_persistent_focus_handle_key(slot_key);
974    with_persistent_focus_handle_store_mut(|store| {
975        store
976            .keys_by_owner
977            .entry(key.instance_key)
978            .or_default()
979            .insert(key);
980        match store.targets.entry(key) {
981            std::collections::hash_map::Entry::Occupied(mut entry) => entry.get_mut().mark_live(),
982            std::collections::hash_map::Entry::Vacant(entry) => {
983                let value = FocusNode::new();
984                store.live_handles.insert(value.handle_id());
985                entry.insert(PersistentFocusHandleEntry::new(value));
986                value
987            }
988        }
989    })
990}
991
992pub(crate) fn persistent_focus_scope_for_current_instance<K: Hash>(slot_key: K) -> FocusScopeNode {
993    let key = current_persistent_focus_handle_key(slot_key);
994    with_persistent_focus_handle_store_mut(|store| {
995        store
996            .keys_by_owner
997            .entry(key.instance_key)
998            .or_default()
999            .insert(key);
1000        match store.scopes.entry(key) {
1001            std::collections::hash_map::Entry::Occupied(mut entry) => entry.get_mut().mark_live(),
1002            std::collections::hash_map::Entry::Vacant(entry) => {
1003                let value = FocusScopeNode::new();
1004                store.live_handles.insert(value.handle_id());
1005                entry.insert(PersistentFocusHandleEntry::new(value));
1006                value
1007            }
1008        }
1009    })
1010}
1011
1012pub(crate) fn persistent_focus_group_for_current_instance<K: Hash>(slot_key: K) -> FocusGroupNode {
1013    let key = current_persistent_focus_handle_key(slot_key);
1014    with_persistent_focus_handle_store_mut(|store| {
1015        store
1016            .keys_by_owner
1017            .entry(key.instance_key)
1018            .or_default()
1019            .insert(key);
1020        match store.groups.entry(key) {
1021            std::collections::hash_map::Entry::Occupied(mut entry) => entry.get_mut().mark_live(),
1022            std::collections::hash_map::Entry::Vacant(entry) => {
1023                let value = FocusGroupNode::new();
1024                store.live_handles.insert(value.handle_id());
1025                entry.insert(PersistentFocusHandleEntry::new(value));
1026                value
1027            }
1028        }
1029    })
1030}
1031
1032pub(crate) fn has_persistent_focus_handle(handle_id: FocusHandleId) -> bool {
1033    with_persistent_focus_handle_store(|store| store.contains_handle(handle_id))
1034}
1035
1036pub(crate) fn has_pending_persistent_focus_prune() -> bool {
1037    with_persistent_focus_handle_store(|store| !store.prune_candidates.is_empty())
1038}
1039
1040pub(crate) fn prune_persistent_focus_handles() {
1041    let removed_owners =
1042        TesseraRuntime::with_mut(|runtime| runtime.component_tree.take_removed_focus_owners());
1043    let removed = with_persistent_focus_handle_store_mut(|store| {
1044        store.prune_removed(removed_owners, |key| {
1045            TesseraRuntime::with(|runtime| {
1046                runtime
1047                    .component_tree
1048                    .find_node_id_by_instance_key(key)
1049                    .is_some()
1050            })
1051        })
1052    });
1053    if !removed.handle_ids.is_empty() || !removed.requester_ids.is_empty() {
1054        TesseraRuntime::with_mut(|runtime| {
1055            runtime
1056                .component_tree
1057                .focus_owner_mut()
1058                .remove_handles(&removed.handle_ids, &removed.requester_ids);
1059        });
1060    }
1061}
1062
1063pub(crate) fn clear_persistent_focus_handles() {
1064    with_persistent_focus_handle_store_mut(PersistentFocusHandleStore::clear);
1065}
1066
1067#[cfg(test)]
1068fn take_next_node_instance_logic_id_override() -> Option<u64> {
1069    with_execution_context_mut(|context| context.next_node_instance_logic_id_override.take())
1070}
1071
1072/// Runs `f` inside a replay scope restored from a previously recorded component
1073/// snapshot.
1074///
1075/// The replay scope restores:
1076/// - the control-flow group path active at the original call site
1077/// - the keyed-instance override active at the original call site
1078/// - a one-shot instance-logic-id override for the replayed component root
1079/// - the inherited context bindings captured at the original call site
1080pub(crate) fn with_replay_scope<R>(
1081    instance_logic_id: u64,
1082    group_path: GroupPath,
1083    instance_key_override: Option<u64>,
1084    context_map: ContextMap,
1085    f: impl FnOnce() -> R,
1086) -> R {
1087    struct ReplayScopeGuard {
1088        group_path: GroupPath,
1089        key_override: Option<u64>,
1090        logic_id_override: Option<u64>,
1091        context_map: ContextMap,
1092    }
1093
1094    impl Drop for ReplayScopeGuard {
1095        fn drop(&mut self) {
1096            with_execution_context_mut(|context| {
1097                context.group_path = self.group_path;
1098                context.instance_key_override = self.key_override;
1099                context.next_node_instance_logic_id_override = self.logic_id_override;
1100                std::mem::swap(&mut context.context_map, &mut self.context_map);
1101            });
1102        }
1103    }
1104
1105    let _guard = with_execution_context_mut(|context| ReplayScopeGuard {
1106        group_path: std::mem::replace(&mut context.group_path, group_path),
1107        key_override: std::mem::replace(&mut context.instance_key_override, instance_key_override),
1108        logic_id_override: context
1109            .next_node_instance_logic_id_override
1110            .replace(instance_logic_id),
1111        context_map: std::mem::replace(&mut context.context_map, context_map),
1112    });
1113
1114    f()
1115}
1116
1117pub(crate) fn with_build_dirty_instance_keys<R>(
1118    dirty_instance_keys: Arc<HashSet<u64>>,
1119    f: impl FnOnce() -> R,
1120) -> R {
1121    struct BuildDirtyScopeGuard {
1122        popped: bool,
1123    }
1124
1125    impl Drop for BuildDirtyScopeGuard {
1126        fn drop(&mut self) {
1127            if self.popped {
1128                return;
1129            }
1130            with_execution_context_mut(|context| {
1131                let popped = context.build_dirty_instance_keys_stack.pop();
1132                debug_assert!(
1133                    popped.is_some(),
1134                    "BUILD_DIRTY_INSTANCE_KEYS_STACK underflow: attempted to pop from empty stack"
1135                );
1136            });
1137            self.popped = true;
1138        }
1139    }
1140
1141    with_execution_context_mut(|context| {
1142        context
1143            .build_dirty_instance_keys_stack
1144            .push(dirty_instance_keys);
1145    });
1146    let _guard = BuildDirtyScopeGuard { popped: false };
1147    f()
1148}
1149
1150pub(crate) fn is_instance_key_build_dirty(instance_key: u64) -> bool {
1151    with_execution_context(|context| {
1152        context
1153            .build_dirty_instance_keys_stack
1154            .last()
1155            .is_some_and(|dirty_instance_keys| dirty_instance_keys.contains(&instance_key))
1156    })
1157}
1158
1159fn consume_pending_build_invalidation(instance_key: u64) -> bool {
1160    with_build_invalidation_tracker_mut(|tracker| {
1161        tracker
1162            .cross_instance_dirty_instance_keys
1163            .remove(&instance_key);
1164        tracker.dirty_instance_keys.remove(&instance_key)
1165    })
1166}
1167
1168pub(crate) fn record_replay_boundary_invalidation_for_instance_key(instance_key: u64) {
1169    let inserted =
1170        with_build_invalidation_tracker_mut(|tracker| tracker.invalidate([instance_key], None));
1171    if inserted && current_phase() != Some(RuntimePhase::Build) {
1172        schedule_runtime_redraw();
1173    }
1174}
1175
1176/// Re-queues invalidations that could not be processed in the current frame.
1177pub(crate) fn defer_build_invalidations(instance_keys: &HashSet<u64>) {
1178    if instance_keys.is_empty() {
1179        return;
1180    }
1181    with_build_invalidation_tracker_mut(|tracker| {
1182        for key in instance_keys {
1183            tracker.dirty_instance_keys.remove(key);
1184            tracker.cross_instance_dirty_instance_keys.remove(key);
1185            tracker.deferred_instance_keys.insert(*key);
1186        }
1187    });
1188    schedule_runtime_redraw();
1189}
1190
1191const POINTER_STATE_READER: crate::session::SessionLocal<std::cell::Cell<Option<u64>>> =
1192    crate::session::SessionLocal::new();
1193
1194pub(crate) fn with_pointer_state_reader<R>(key: u64, run: impl FnOnce() -> R) -> R {
1195    struct ReaderGuard(Option<u64>);
1196    impl Drop for ReaderGuard {
1197        fn drop(&mut self) {
1198            POINTER_STATE_READER.with(|reader| reader.set(self.0));
1199        }
1200    }
1201    let _guard = ReaderGuard(POINTER_STATE_READER.with(|reader| reader.replace(Some(key))));
1202    run()
1203}
1204
1205pub(crate) fn take_pointer_dirty_nodes() -> HashSet<u64> {
1206    with_layout_dirty_tracker_mut(|tracker| {
1207        std::mem::take(&mut tracker.pending_pointer_dirty_nodes)
1208    })
1209}
1210
1211fn track_state_read_dependency(slot: SlotHandle, generation: u64) {
1212    let phase = match current_phase() {
1213        Some(RuntimePhase::Input) if POINTER_STATE_READER.with(|reader| reader.get().is_some()) => {
1214            Some(ReadPhase::Pointer)
1215        }
1216        Some(RuntimePhase::Measure) => Some(ReadPhase::Measure),
1217        Some(RuntimePhase::Place) if current_measured_node_instance_key().is_some() => {
1218            Some(ReadPhase::Place)
1219        }
1220        Some(RuntimePhase::Place) => Some(ReadPhase::Transform),
1221        Some(RuntimePhase::Paint) => Some(ReadPhase::Paint),
1222        _ => None,
1223    };
1224    if let Some(phase) = phase {
1225        track_layout_state_read_dependency(slot, generation, phase);
1226        return;
1227    }
1228    if !matches!(current_phase(), Some(RuntimePhase::Build)) {
1229        return;
1230    }
1231    let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
1232        return;
1233    };
1234
1235    let key = StateReadDependencyKey { slot, generation };
1236    with_state_read_dependency_tracker_mut(|tracker| tracker.record(key, reader_instance_key));
1237}
1238
1239fn track_layout_state_read_dependency(slot: SlotHandle, generation: u64, phase: ReadPhase) {
1240    let Some(reader_instance_key) = POINTER_STATE_READER
1241        .with(|reader| reader.get())
1242        .or_else(current_measured_node_instance_key)
1243        .or_else(current_replay_boundary_instance_key_from_scope)
1244    else {
1245        return;
1246    };
1247    let reader_instance_key = PhaseReader {
1248        instance_key: reader_instance_key,
1249        phase,
1250    };
1251    let key = StateReadDependencyKey { slot, generation };
1252    with_layout_dirty_tracker_mut(|tracker| tracker.state_reads.record(key, reader_instance_key));
1253}
1254
1255fn mark_layout_state_readers_dirty(key: StateReadDependencyKey) -> bool {
1256    with_layout_dirty_tracker_mut(|tracker| {
1257        let Some(readers) = tracker.state_reads.readers(&key) else {
1258            return false;
1259        };
1260        for reader in readers {
1261            let target = match reader.phase {
1262                ReadPhase::Measure => &mut tracker.pending_measure_self_dirty_nodes,
1263                ReadPhase::Place => &mut tracker.pending_placement_self_dirty_nodes,
1264                ReadPhase::Transform => &mut tracker.pending_transform_dirty_nodes,
1265                ReadPhase::Paint => &mut tracker.pending_paint_dirty_nodes,
1266                ReadPhase::Pointer => &mut tracker.pending_pointer_dirty_nodes,
1267            };
1268            target.insert(reader.instance_key);
1269        }
1270        true
1271    })
1272}
1273
1274pub(crate) fn clear_phase_state_reads(instance_key: u64, phase: ReadPhase) {
1275    with_layout_dirty_tracker_mut(|tracker| {
1276        let reader = PhaseReader {
1277            instance_key,
1278            phase,
1279        };
1280        tracker.state_reads.remove_reader(&reader);
1281    });
1282}
1283
1284pub(crate) fn remove_layout_state_read_dependencies(instance_keys: &HashSet<u64>) {
1285    for &instance_key in instance_keys {
1286        for phase in [
1287            ReadPhase::Pointer,
1288            ReadPhase::Measure,
1289            ReadPhase::Place,
1290            ReadPhase::Transform,
1291            ReadPhase::Paint,
1292        ] {
1293            clear_phase_state_reads(instance_key, phase);
1294        }
1295    }
1296}
1297
1298fn track_focus_dependency(kind: FocusReadDependencyKind) {
1299    if !matches!(current_phase(), Some(RuntimePhase::Build)) {
1300        return;
1301    }
1302    let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
1303        return;
1304    };
1305
1306    let key = FocusReadDependencyKey { kind };
1307    with_focus_read_dependency_tracker_mut(|tracker| tracker.record(key, reader_instance_key));
1308}
1309
1310fn focus_read_subscribers_by_kind(kind: FocusReadDependencyKind) -> Vec<u64> {
1311    let key = FocusReadDependencyKey { kind };
1312    with_focus_read_dependency_tracker(|tracker| {
1313        tracker
1314            .readers(&key)
1315            .map(Iterator::collect)
1316            .unwrap_or_default()
1317    })
1318}
1319
1320pub(crate) fn track_focus_read_dependency(handle_id: FocusHandleId) {
1321    track_focus_dependency(FocusReadDependencyKind::Handle(handle_id));
1322}
1323
1324pub(crate) fn track_focus_requester_read_dependency(requester_id: FocusRequesterId) {
1325    track_focus_dependency(FocusReadDependencyKind::Requester(requester_id));
1326}
1327
1328pub(crate) fn focus_read_subscribers(handle_id: FocusHandleId) -> Vec<u64> {
1329    focus_read_subscribers_by_kind(FocusReadDependencyKind::Handle(handle_id))
1330}
1331
1332pub(crate) fn focus_requester_read_subscribers(requester_id: FocusRequesterId) -> Vec<u64> {
1333    focus_read_subscribers_by_kind(FocusReadDependencyKind::Requester(requester_id))
1334}
1335
1336pub(crate) fn track_render_slot_read_dependency(handle: FunctorHandle) {
1337    if !matches!(current_phase(), Some(RuntimePhase::Build)) {
1338        return;
1339    }
1340    let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
1341        return;
1342    };
1343
1344    with_render_slot_read_dependency_tracker_mut(|tracker| {
1345        tracker.record(handle, reader_instance_key)
1346    });
1347}
1348
1349fn invalidate_render_slot_readers(handle: FunctorHandle, creator_instance_key: u64) {
1350    with_execution_context(|context| {
1351        let dirty = context.build_dirty_instance_keys_stack.last();
1352        with_runtime_globals(|globals| {
1353            let readers = globals.render_slot_read_dependency_tracker.borrow();
1354            let Some(readers) = readers.readers(&handle) else {
1355                return;
1356            };
1357            let mut invalidations = globals.build_invalidation_tracker.borrow_mut();
1358            for instance_key in readers {
1359                if instance_key != creator_instance_key
1360                    && !dirty.is_some_and(|dirty| dirty.contains(&instance_key))
1361                {
1362                    invalidations.invalidate([instance_key], None);
1363                }
1364            }
1365        });
1366    });
1367}
1368
1369pub(crate) fn remove_state_read_dependencies(instance_keys: &HashSet<u64>) {
1370    crate::nested_scroll::remove_readers(instance_keys);
1371    if instance_keys.is_empty() {
1372        return;
1373    }
1374    with_state_read_dependency_tracker_mut(|tracker| tracker.remove_readers(instance_keys));
1375}
1376
1377pub(crate) fn remove_focus_read_dependencies(instance_keys: &HashSet<u64>) {
1378    if instance_keys.is_empty() {
1379        return;
1380    }
1381    with_focus_read_dependency_tracker_mut(|tracker| tracker.remove_readers(instance_keys));
1382}
1383
1384pub(crate) fn remove_render_slot_read_dependencies(instance_keys: &HashSet<u64>) {
1385    if instance_keys.is_empty() {
1386        return;
1387    }
1388    with_render_slot_read_dependency_tracker_mut(|tracker| tracker.remove_readers(instance_keys));
1389}
1390
1391pub(crate) fn reset_state_read_dependencies() {
1392    with_state_read_dependency_tracker_mut(|tracker| {
1393        *tracker = StateReadDependencyTracker::default();
1394    });
1395}
1396
1397pub(crate) fn reset_focus_read_dependencies() {
1398    with_focus_read_dependency_tracker_mut(|tracker| {
1399        *tracker = FocusReadDependencyTracker::default();
1400    });
1401}
1402
1403pub(crate) fn reset_render_slot_read_dependencies() {
1404    with_render_slot_read_dependency_tracker_mut(|tracker| {
1405        *tracker = RenderSlotReadDependencyTracker::default();
1406    });
1407}
1408
1409pub(crate) fn take_build_invalidations() -> BuildInvalidationSet {
1410    with_build_invalidation_tracker_mut(|tracker| BuildInvalidationSet {
1411        dirty_instance_keys: std::mem::take(&mut tracker.dirty_instance_keys),
1412        cross_instance_dirty_instance_keys: std::mem::take(
1413            &mut tracker.cross_instance_dirty_instance_keys,
1414        ),
1415    })
1416}
1417
1418pub(crate) fn begin_frame_build_invalidations() {
1419    with_build_invalidation_tracker_mut(|tracker| {
1420        tracker
1421            .dirty_instance_keys
1422            .extend(tracker.deferred_instance_keys.drain());
1423    });
1424}
1425
1426pub(crate) fn reset_build_invalidations() {
1427    with_build_invalidation_tracker_mut(|tracker| {
1428        *tracker = BuildInvalidationTracker::default();
1429    });
1430}
1431
1432pub(crate) fn remove_build_invalidations(instance_keys: &HashSet<u64>) {
1433    if instance_keys.is_empty() {
1434        return;
1435    }
1436    with_build_invalidation_tracker_mut(|tracker| {
1437        for key in instance_keys {
1438            tracker.dirty_instance_keys.remove(key);
1439            tracker.cross_instance_dirty_instance_keys.remove(key);
1440            tracker.deferred_instance_keys.remove(key);
1441        }
1442    });
1443}
1444
1445pub(crate) fn has_pending_build_invalidations() -> bool {
1446    with_build_invalidation_tracker(|tracker| {
1447        !tracker.dirty_instance_keys.is_empty() || !tracker.deferred_instance_keys.is_empty()
1448    })
1449}
1450
1451pub(crate) fn has_ready_build_invalidations() -> bool {
1452    with_build_invalidation_tracker(|tracker| !tracker.dirty_instance_keys.is_empty())
1453}
1454
1455#[derive(Default)]
1456struct FrameClockTracker {
1457    frame_origin: Option<Instant>,
1458    current_frame_time: Option<Instant>,
1459    current_frame_nanos: u64,
1460    previous_frame_time: Option<Instant>,
1461    frame_delta: Duration,
1462    receivers: HashMap<FrameNanosReceiverKey, FrameNanosReceiver>,
1463    executed_receiver_owners: Vec<u64>,
1464    receivers_by_owner: HashMap<u64, HashSet<FrameNanosReceiverKey>>,
1465}
1466
1467#[derive(Hash, Eq, PartialEq, Clone, Copy)]
1468struct FrameNanosReceiverKey {
1469    instance_logic_id: u64,
1470    receiver_hash: u64,
1471}
1472
1473/// Control flow for [`receive_frame_nanos`] callbacks.
1474#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1475pub enum FrameNanosControl {
1476    /// Keep this receiver registered and run it again on the next frame.
1477    Continue,
1478    /// Unregister this receiver after the current frame tick.
1479    Stop,
1480}
1481
1482type FrameNanosReceiverCallback = Box<dyn FnMut(u64) -> FrameNanosControl + Send + 'static>;
1483
1484struct FrameNanosReceiver {
1485    owner_instance_key: u64,
1486    callback: FrameNanosReceiverCallback,
1487}
1488
1489fn with_frame_clock_tracker<R>(f: impl FnOnce(&FrameClockTracker) -> R) -> R {
1490    with_runtime_globals(|globals| f(&globals.frame_clock_tracker.borrow()))
1491}
1492
1493fn with_frame_clock_tracker_mut<R>(f: impl FnOnce(&mut FrameClockTracker) -> R) -> R {
1494    with_runtime_globals(|globals| f(&mut globals.frame_clock_tracker.borrow_mut()))
1495}
1496
1497pub(crate) fn begin_frame_clock(now: Instant) {
1498    with_frame_clock_tracker_mut(|tracker| {
1499        let frame_origin = *tracker.frame_origin.get_or_insert(now);
1500        tracker.previous_frame_time = tracker.current_frame_time;
1501        tracker.current_frame_time = Some(now);
1502        tracker.current_frame_nanos = now
1503            .saturating_duration_since(frame_origin)
1504            .as_nanos()
1505            .min(u64::MAX as u128) as u64;
1506        tracker.frame_delta = tracker
1507            .previous_frame_time
1508            .map(|previous| now.saturating_duration_since(previous))
1509            .unwrap_or_default();
1510    });
1511}
1512
1513pub(crate) fn reset_frame_clock() {
1514    let removed = with_frame_clock_tracker_mut(std::mem::take);
1515    drop(removed);
1516}
1517
1518pub(crate) fn has_pending_frame_nanos_receivers() -> bool {
1519    with_frame_clock_tracker(|tracker| !tracker.receivers.is_empty())
1520}
1521
1522/// Frame-clock work performed by a completed frame.
1523#[derive(Clone, Debug, Default)]
1524pub struct FrameClockDiagnostics {
1525    /// Runtime session that owns these subscriptions.
1526    pub session: Option<crate::RuntimeSessionId>,
1527    /// Number of actual frame callback invocations.
1528    pub callbacks_executed: usize,
1529    /// Number of subscriptions remaining after the frame.
1530    pub pending_subscriptions: usize,
1531    /// Replay boundary owners whose callbacks executed this frame.
1532    pub executed_owners: Vec<u64>,
1533}
1534
1535#[cfg(any(
1536    test,
1537    feature = "testing",
1538    all(feature = "headless", not(target_family = "wasm"))
1539))]
1540pub(crate) fn frame_clock_diagnostics() -> FrameClockDiagnostics {
1541    with_frame_clock_tracker(|tracker| FrameClockDiagnostics {
1542        session: crate::RuntimeSession::current().map(|session| session.identity()),
1543        callbacks_executed: tracker.executed_receiver_owners.len(),
1544        pending_subscriptions: tracker.receivers.len(),
1545        executed_owners: tracker.executed_receiver_owners.clone(),
1546    })
1547}
1548
1549pub(crate) fn tick_frame_nanos_receivers() {
1550    crate::dispatcher::flush();
1551    let (frame_nanos, receivers) = with_frame_clock_tracker_mut(|tracker| {
1552        tracker.executed_receiver_owners.clear();
1553        (
1554            tracker.current_frame_nanos,
1555            std::mem::take(&mut tracker.receivers),
1556        )
1557    });
1558    for (key, mut receiver) in receivers {
1559        let registered = with_frame_clock_tracker(|tracker| {
1560            tracker
1561                .receivers_by_owner
1562                .get(&receiver.owner_instance_key)
1563                .is_some_and(|keys| keys.contains(&key))
1564        });
1565        if !registered {
1566            continue;
1567        }
1568        with_frame_clock_tracker_mut(|tracker| {
1569            tracker
1570                .executed_receiver_owners
1571                .push(receiver.owner_instance_key)
1572        });
1573        let active = matches!(
1574            (receiver.callback)(frame_nanos),
1575            FrameNanosControl::Continue
1576        );
1577        let discarded = with_frame_clock_tracker_mut(|tracker| {
1578            if active
1579                && tracker
1580                    .receivers_by_owner
1581                    .get(&receiver.owner_instance_key)
1582                    .is_some_and(|keys| keys.contains(&key))
1583            {
1584                match tracker.receivers.entry(key) {
1585                    std::collections::hash_map::Entry::Vacant(entry) => {
1586                        entry.insert(receiver);
1587                        return None;
1588                    }
1589                    std::collections::hash_map::Entry::Occupied(_) => {}
1590                }
1591            } else if !tracker.receivers.contains_key(&key) {
1592                if let Some(keys) = tracker
1593                    .receivers_by_owner
1594                    .get_mut(&receiver.owner_instance_key)
1595                {
1596                    keys.remove(&key);
1597                    if keys.is_empty() {
1598                        tracker
1599                            .receivers_by_owner
1600                            .remove(&receiver.owner_instance_key);
1601                    }
1602                }
1603            }
1604            Some(receiver)
1605        });
1606        drop(discarded);
1607    }
1608}
1609
1610pub(crate) fn remove_frame_nanos_receivers(instance_keys: &HashSet<u64>) {
1611    if instance_keys.is_empty() {
1612        return;
1613    }
1614    let removed = with_frame_clock_tracker_mut(|tracker| {
1615        let mut removed = Vec::new();
1616        for owner in instance_keys {
1617            if let Some(keys) = tracker.receivers_by_owner.remove(owner) {
1618                for key in keys {
1619                    removed.extend(tracker.receivers.remove(&key));
1620                }
1621            }
1622        }
1623        removed
1624    });
1625    drop(removed);
1626}
1627
1628pub(crate) fn clear_frame_nanos_receivers() {
1629    let removed = with_frame_clock_tracker_mut(|tracker| {
1630        tracker.receivers_by_owner.clear();
1631        std::mem::take(&mut tracker.receivers)
1632    });
1633    drop(removed);
1634}
1635
1636/// Returns the timestamp of the current frame, if available.
1637///
1638/// The value is set by the renderer at frame begin.
1639pub fn current_frame_time() -> Option<Instant> {
1640    with_frame_clock_tracker(|tracker| tracker.current_frame_time)
1641}
1642
1643/// Returns the current frame timestamp in nanoseconds from runtime origin.
1644pub fn current_frame_nanos() -> u64 {
1645    with_frame_clock_tracker(|tracker| tracker.current_frame_nanos)
1646}
1647
1648/// Returns the elapsed time since the previous frame.
1649pub fn frame_delta() -> Duration {
1650    with_frame_clock_tracker(|tracker| tracker.frame_delta)
1651}
1652
1653fn ensure_frame_receive_phase() {
1654    match current_phase() {
1655        Some(RuntimePhase::Build) => {}
1656        Some(RuntimePhase::Measure) => {
1657            panic!("receive_frame_nanos must not be called inside measure")
1658        }
1659        Some(RuntimePhase::Place | RuntimePhase::Paint | RuntimePhase::Input) => {
1660            panic!("receive_frame_nanos must be called inside a tessera component build")
1661        }
1662        None => panic!("receive_frame_nanos must be called inside a tessera component build"),
1663    }
1664}
1665
1666fn compute_frame_nanos_receiver_key() -> FrameNanosReceiverKey {
1667    let instance_logic_id = current_instance_logic_id();
1668    let group_path_hash = current_group_path_hash();
1669
1670    let call_counter = next_order_counter(
1671        OrderCounterKind::FrameReceiver,
1672        "ORDER_FRAME_STACK is empty; receive_frame_nanos must be called inside a component",
1673    );
1674
1675    let receiver_hash = hash_components(&[&group_path_hash, &call_counter]);
1676    FrameNanosReceiverKey {
1677        instance_logic_id,
1678        receiver_hash,
1679    }
1680}
1681
1682/// Register a per-frame callback driven by the renderer's frame clock.
1683///
1684/// Registration is keyed by the current callsite identity. Repeated calls from
1685/// the same position keep the existing active callback until it returns
1686/// [`FrameNanosControl::Stop`].
1687pub fn receive_frame_nanos<F>(callback: F)
1688where
1689    F: FnMut(u64) -> FrameNanosControl + Send + 'static,
1690{
1691    ensure_frame_receive_phase();
1692    let frame_nanos_state = remember(current_frame_nanos);
1693    let _ = frame_nanos_state.get();
1694
1695    let owner_instance_key = current_replay_boundary_instance_key_from_scope()
1696        .unwrap_or_else(|| panic!("receive_frame_nanos requires an active component node context"));
1697    let key = compute_frame_nanos_receiver_key();
1698
1699    let mut unused = Some(callback);
1700    with_frame_clock_tracker_mut(|tracker| {
1701        tracker.receivers.entry(key).or_insert_with(|| {
1702            let mut callback = unused.take().expect("new component clock callback");
1703            FrameNanosReceiver {
1704                owner_instance_key,
1705                callback: Box::new(move |frame_nanos| {
1706                    if !frame_nanos_state.is_alive() {
1707                        return FrameNanosControl::Stop;
1708                    }
1709                    frame_nanos_state.set(frame_nanos);
1710                    callback(frame_nanos)
1711                }),
1712            }
1713        });
1714        let owner = tracker.receivers[&key].owner_instance_key;
1715        tracker
1716            .receivers_by_owner
1717            .entry(owner)
1718            .or_default()
1719            .insert(key);
1720    });
1721    drop(unused);
1722}
1723
1724pub(crate) fn drop_slots_for_instance_logic_ids(instance_logic_ids: &HashSet<u64>) {
1725    crate::nested_scroll::remove_owners(instance_logic_ids);
1726    if instance_logic_ids.is_empty() {
1727        return;
1728    }
1729
1730    let removed = with_slot_table_mut(|table| {
1731        let mut freed = Vec::new();
1732        for slot in table.slots_for_instances(instance_logic_ids) {
1733            let Some(entry) = table.entries.get(slot) else {
1734                continue;
1735            };
1736            if entry.retained {
1737                continue;
1738            }
1739            freed.push(slot);
1740        }
1741        let mut removed = Vec::with_capacity(freed.len());
1742        for slot in freed {
1743            removed.extend(table.remove_slot(slot));
1744        }
1745        for instance_logic_id in instance_logic_ids {
1746            table.cursors_by_instance_logic_id.remove(instance_logic_id);
1747        }
1748        removed
1749    });
1750    drop(removed);
1751}
1752
1753fn with_layout_dirty_tracker_mut<R>(f: impl FnOnce(&mut LayoutDirtyTracker) -> R) -> R {
1754    with_runtime_globals(|globals| f(&mut globals.layout_dirty_tracker.borrow_mut()))
1755}
1756
1757fn record_layout_node_dirty(previous: Option<&ComponentNode>, node: &ComponentNode) {
1758    let instance_key = node.instance_key;
1759    let (measure_changed, placement_changed, transform_changed, parent_data_changed) =
1760        if let Some(previous) = previous {
1761            (
1762                !previous
1763                    .layout_policy()
1764                    .dyn_measure_eq(node.layout_policy())
1765                    || !previous.modifier.layout_measure_eq(&node.modifier),
1766                !previous
1767                    .layout_policy()
1768                    .dyn_placement_eq(node.layout_policy())
1769                    || !previous.modifier.layout_placement_eq(&node.modifier),
1770                !previous.modifier.position_eq(&node.modifier),
1771                !previous.modifier.parent_data_eq(&node.modifier),
1772            )
1773        } else {
1774            (true, true, true, true)
1775        };
1776    with_layout_dirty_tracker_mut(|tracker| {
1777        if measure_changed {
1778            tracker
1779                .pending_measure_self_dirty_nodes
1780                .insert(instance_key);
1781        } else if placement_changed {
1782            tracker
1783                .pending_placement_self_dirty_nodes
1784                .insert(instance_key);
1785        }
1786        if parent_data_changed {
1787            tracker.pending_parent_data_dirty_nodes.insert(instance_key);
1788        }
1789        if transform_changed {
1790            tracker.pending_transform_dirty_nodes.insert(instance_key);
1791        }
1792    });
1793}
1794
1795pub(crate) fn begin_frame_layout_dirty_tracking() {
1796    with_layout_dirty_tracker_mut(|tracker| {
1797        // Marks queued outside the build (input handlers, frame callbacks,
1798        // external state writes) live in the pending sets until the next
1799        // layout pass. Carry them into the ready sets instead of dropping them.
1800        tracker
1801            .ready_measure_self_dirty_nodes
1802            .extend(std::mem::take(
1803                &mut tracker.pending_measure_self_dirty_nodes,
1804            ));
1805        tracker
1806            .ready_placement_self_dirty_nodes
1807            .extend(std::mem::take(
1808                &mut tracker.pending_placement_self_dirty_nodes,
1809            ));
1810    });
1811}
1812
1813pub(crate) fn finalize_frame_layout_dirty_tracking() {
1814    with_layout_dirty_tracker_mut(|tracker| {
1815        tracker
1816            .ready_measure_self_dirty_nodes
1817            .extend(std::mem::take(
1818                &mut tracker.pending_measure_self_dirty_nodes,
1819            ));
1820        tracker
1821            .ready_placement_self_dirty_nodes
1822            .extend(std::mem::take(
1823                &mut tracker.pending_placement_self_dirty_nodes,
1824            ));
1825    });
1826}
1827
1828pub(crate) fn take_layout_dirty_nodes() -> LayoutDirtyNodes {
1829    with_layout_dirty_tracker_mut(|tracker| {
1830        // Layout-only writes must reach layout even when no component build
1831        // runs.
1832        let mut measure_self_nodes = std::mem::take(&mut tracker.ready_measure_self_dirty_nodes);
1833        measure_self_nodes.extend(std::mem::take(
1834            &mut tracker.pending_measure_self_dirty_nodes,
1835        ));
1836        let mut placement_self_nodes =
1837            std::mem::take(&mut tracker.ready_placement_self_dirty_nodes);
1838        placement_self_nodes.extend(std::mem::take(
1839            &mut tracker.pending_placement_self_dirty_nodes,
1840        ));
1841        LayoutDirtyNodes {
1842            measure_self_nodes,
1843            placement_self_nodes,
1844            parent_data_nodes: std::mem::take(&mut tracker.pending_parent_data_dirty_nodes),
1845            transform_nodes: std::mem::take(&mut tracker.pending_transform_dirty_nodes),
1846            paint_nodes: std::mem::take(&mut tracker.pending_paint_dirty_nodes),
1847        }
1848    })
1849}
1850
1851pub(crate) fn has_pending_layout_invalidations() -> bool {
1852    with_layout_dirty_tracker_mut(|tracker| {
1853        !tracker.pending_measure_self_dirty_nodes.is_empty()
1854            || !tracker.ready_measure_self_dirty_nodes.is_empty()
1855            || !tracker.pending_placement_self_dirty_nodes.is_empty()
1856            || !tracker.ready_placement_self_dirty_nodes.is_empty()
1857            || !tracker.pending_parent_data_dirty_nodes.is_empty()
1858            || !tracker.pending_transform_dirty_nodes.is_empty()
1859            || !tracker.pending_paint_dirty_nodes.is_empty()
1860            || !tracker.pending_pointer_dirty_nodes.is_empty()
1861    })
1862}
1863
1864pub(crate) fn reset_layout_dirty_tracking() {
1865    with_layout_dirty_tracker_mut(|tracker| *tracker = LayoutDirtyTracker::default());
1866}
1867
1868pub(crate) fn record_layout_structure(
1869    instance_key: u64,
1870    children: impl ExactSizeIterator<Item = u64> + Clone,
1871) {
1872    with_layout_dirty_tracker_mut(|tracker| {
1873        let previous = tracker
1874            .previous_children_by_node
1875            .get(&instance_key)
1876            .map_or(&[][..], Box::as_ref);
1877        if !previous.iter().copied().eq(children.clone()) {
1878            tracker
1879                .structural_changes
1880                .changed_nodes
1881                .insert(instance_key);
1882            if children.len() == 0 {
1883                tracker.previous_children_by_node.remove(&instance_key);
1884            } else {
1885                tracker
1886                    .previous_children_by_node
1887                    .insert(instance_key, children.collect());
1888            }
1889        }
1890        tracker
1891            .structural_changes
1892            .removed_nodes
1893            .remove(&instance_key);
1894    });
1895}
1896
1897pub(crate) fn remove_layout_structure(instance_keys: &HashSet<u64>) {
1898    if instance_keys.is_empty() {
1899        return;
1900    }
1901    with_layout_dirty_tracker_mut(|tracker| {
1902        for key in instance_keys {
1903            tracker.previous_children_by_node.remove(key);
1904            tracker.structural_changes.removed_nodes.insert(*key);
1905            tracker.structural_changes.changed_nodes.remove(key);
1906        }
1907    });
1908}
1909
1910pub(crate) fn take_layout_structure_changes() -> StructureReconcileResult {
1911    with_layout_dirty_tracker_mut(|tracker| std::mem::take(&mut tracker.structural_changes))
1912}
1913
1914/// Handle to memoized state created by [`remember`] and [`remember_with_key`].
1915///
1916/// `State<T>` is `Copy + Send + Sync` and provides `with`, `with_mut`, `get`,
1917/// `set`, and `cloned` to read or update the stored value.
1918///
1919/// Component factories such as [`remember`] run only during a Tessera build.
1920/// Remembered state survives recomposition while its slot is used; [`retain`]
1921/// also keeps unused slots until the component unmounts. [`StateOwner`] provides
1922/// a controller or route lifetime independent of component mounting.
1923///
1924/// Access rejects stale handles and handles owned by another UI session.
1925///
1926/// # Examples
1927///
1928/// ```
1929/// use tessera_ui::{remember, tessera};
1930///
1931/// #[tessera]
1932/// fn counter() {
1933///     let count = remember(|| 0usize);
1934///     count.with_mut(|c| *c += 1);
1935///     let current = count.get();
1936///     assert!(current >= 1);
1937/// }
1938/// ```
1939pub struct State<T> {
1940    session: u128,
1941    slot: SlotHandle,
1942    generation: u64,
1943    _marker: PhantomData<T>,
1944}
1945
1946impl<T> Copy for State<T> {}
1947
1948impl<T> Clone for State<T> {
1949    fn clone(&self) -> Self {
1950        *self
1951    }
1952}
1953
1954impl<T> PartialEq for State<T> {
1955    fn eq(&self, other: &Self) -> bool {
1956        self.session == other.session
1957            && self.slot == other.slot
1958            && self.generation == other.generation
1959    }
1960}
1961
1962impl<T> Eq for State<T> {}
1963
1964impl<T> Hash for State<T> {
1965    fn hash<H: Hasher>(&self, state: &mut H) {
1966        self.session.hash(state);
1967        self.slot.hash(state);
1968        self.generation.hash(state);
1969    }
1970}
1971
1972impl<T> State<T> {
1973    fn new(slot: SlotHandle, generation: u64) -> Self {
1974        Self {
1975            session: crate::session::current_session_identity().expect("active state session"),
1976            slot,
1977            generation,
1978            _marker: PhantomData,
1979        }
1980    }
1981}
1982
1983impl<T> State<T>
1984where
1985    T: Send + Sync + 'static,
1986{
1987    /// Returns whether this handle still refers to a live state slot.
1988    ///
1989    /// Remembered state can expire when its component leaves the tree;
1990    /// retained state remains alive until the runtime is reset.
1991    pub fn is_alive(&self) -> bool {
1992        if crate::session::current_session_identity() != Some(self.session) {
1993            return false;
1994        }
1995        try_with_runtime_globals(|globals| {
1996            let table = globals.slot_table.borrow();
1997            let Some(entry) = table.entries.get(self.slot) else {
1998                return false;
1999            };
2000
2001            entry.generation == self.generation && entry.key.type_id == TypeId::of::<T>()
2002        })
2003        .unwrap_or(false)
2004    }
2005
2006    /// Execute a closure with a shared reference to the stored value.
2007    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
2008        assert_eq!(
2009            crate::session::current_session_identity(),
2010            Some(self.session),
2011            "state belongs to a different runtime session"
2012        );
2013        track_state_read_dependency(self.slot, self.generation);
2014        let value = with_slot_table(|table| state_value::<T>(table, self.slot, self.generation));
2015        f(&value)
2016    }
2017
2018    /// Execute a closure with a mutable reference to the stored value.
2019    #[track_caller]
2020    pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
2021        assert_eq!(
2022            crate::session::current_session_identity(),
2023            Some(self.session),
2024            "state belongs to a different runtime session"
2025        );
2026        // Move the value out for the duration of the closure so user code can
2027        // re-enter runtime APIs (`remember`, callback handles) without hitting
2028        // the active slot-table borrow. The guard restores it on unwind too.
2029        let taken =
2030            with_slot_table_mut(|table| take_state_value::<T>(table, self.slot, self.generation));
2031        let mut guard = StateValueRestore::<T> {
2032            slot: self.slot,
2033            generation: self.generation,
2034            value: Some(taken),
2035        };
2036        let result = f(guard.value.as_mut().expect("state value is present"));
2037        let value = guard.value.take().expect("state value is present");
2038        let slot = self.slot;
2039        let generation = self.generation;
2040        let removed =
2041            with_slot_table_mut(|table| restore_state_value::<T>(table, slot, generation, value));
2042        if removed.is_none() {
2043            publish_state_write(self.slot, self.generation);
2044        }
2045        drop(removed);
2046        result
2047    }
2048
2049    /// Get a cloned value. Requires `T: Clone`.
2050    pub fn get(&self) -> T
2051    where
2052        T: Clone,
2053    {
2054        self.with(Clone::clone)
2055    }
2056
2057    /// Replaces the value and invalidates readers only when it differs.
2058    ///
2059    /// The comparison does not subscribe the caller to the state. This permits
2060    /// publishing derived geometry without making its publisher a reader.
2061    #[track_caller]
2062    pub fn set_if_changed(&self, value: T) -> bool
2063    where
2064        T: PartialEq,
2065    {
2066        assert_eq!(
2067            crate::session::current_session_identity(),
2068            Some(self.session),
2069            "state belongs to a different runtime session"
2070        );
2071        let taken =
2072            with_slot_table_mut(|table| take_state_value::<T>(table, self.slot, self.generation));
2073        let mut guard = StateValueRestore {
2074            slot: self.slot,
2075            generation: self.generation,
2076            value: Some(taken),
2077        };
2078        let changed = guard.value.as_ref().expect("state value is present") != &value;
2079        let removed = if changed {
2080            guard.value.replace(value)
2081        } else {
2082            Some(value)
2083        };
2084        let current = guard.value.take().expect("state value is present");
2085        let discarded = with_slot_table_mut(|table| {
2086            restore_state_value(table, self.slot, self.generation, current)
2087        });
2088        if changed && discarded.is_none() {
2089            publish_state_write(self.slot, self.generation);
2090        }
2091        drop(discarded);
2092        drop(removed);
2093        changed
2094    }
2095
2096    /// Replace the stored value.
2097    #[track_caller]
2098    pub fn set(&self, value: T) {
2099        self.with_mut(|slot| *slot = value);
2100    }
2101}
2102
2103fn publish_state_write(slot: SlotHandle, generation: u64) {
2104    // A write performed while another component is building is a backwards
2105    // write for every other reader: they may already have been rebuilt in
2106    // this pass with the old value, so their invalidation must not be
2107    // treated as already covered by the current replay round.
2108    let writer_instance_key = with_execution_context(|context| {
2109        if context.phase_stack.last() == Some(&RuntimePhase::Build) {
2110            context.current_component_instance_stack.last().copied()
2111        } else {
2112            None
2113        }
2114    });
2115    let key = StateReadDependencyKey { slot, generation };
2116    let build_dirty = with_runtime_globals(|globals| {
2117        let dependencies = globals.state_read_dependency_tracker.borrow();
2118        dependencies.readers(&key).is_some_and(|readers| {
2119            globals
2120                .build_invalidation_tracker
2121                .borrow_mut()
2122                .invalidate(readers, writer_instance_key)
2123        })
2124    });
2125    let layout_dirty = mark_layout_state_readers_dirty(key);
2126    if build_dirty || layout_dirty {
2127        schedule_runtime_redraw();
2128    }
2129}
2130
2131pub(crate) struct RuntimeGlobals {
2132    pub(crate) component_calls: ComponentCalls,
2133    pub(crate) context: ContextGlobals,
2134    slot_table: RefCell<SlotTable>,
2135    component_replay_tracker: RefCell<ComponentReplayTracker>,
2136    build_invalidation_tracker: RefCell<BuildInvalidationTracker>,
2137    state_read_dependency_tracker: RefCell<StateReadDependencyTracker>,
2138    focus_read_dependency_tracker: RefCell<FocusReadDependencyTracker>,
2139    render_slot_read_dependency_tracker: RefCell<RenderSlotReadDependencyTracker>,
2140    redraw_waker: RefCell<Option<RedrawWaker>>,
2141    persistent_focus_handle_store: RefCell<PersistentFocusHandleStore>,
2142    frame_clock_tracker: RefCell<FrameClockTracker>,
2143    layout_dirty_tracker: RefCell<LayoutDirtyTracker>,
2144    runtime: RefCell<TesseraRuntime>,
2145}
2146
2147impl RuntimeGlobals {
2148    pub(crate) fn with_runtime<R>(&self, read: impl FnOnce(&TesseraRuntime) -> R) -> R {
2149        read(&self.runtime.borrow())
2150    }
2151
2152    fn new() -> Self {
2153        Self {
2154            component_calls: ComponentCalls::default(),
2155            context: ContextGlobals::default(),
2156            slot_table: RefCell::new(SlotTable::default()),
2157            component_replay_tracker: RefCell::new(ComponentReplayTracker::default()),
2158            build_invalidation_tracker: RefCell::new(BuildInvalidationTracker::default()),
2159            state_read_dependency_tracker: RefCell::new(StateReadDependencyTracker::default()),
2160            focus_read_dependency_tracker: RefCell::new(FocusReadDependencyTracker::default()),
2161            render_slot_read_dependency_tracker: RefCell::new(
2162                RenderSlotReadDependencyTracker::default(),
2163            ),
2164            redraw_waker: RefCell::new(None),
2165            persistent_focus_handle_store: RefCell::new(PersistentFocusHandleStore::default()),
2166            frame_clock_tracker: RefCell::new(FrameClockTracker::default()),
2167            layout_dirty_tracker: RefCell::new(LayoutDirtyTracker::default()),
2168            runtime: RefCell::new(TesseraRuntime::default()),
2169        }
2170    }
2171}
2172
2173impl Default for RuntimeGlobals {
2174    fn default() -> Self {
2175        Self::new()
2176    }
2177}
2178/// Runtime state container.
2179#[derive(Default)]
2180pub struct TesseraRuntime {
2181    /// Hierarchical structure of all UI components in the application.
2182    pub component_tree: ComponentTree,
2183    /// Current window dimensions in physical pixels.
2184    pub(crate) window_size: [u32; 2],
2185    /// Cursor icon change request from UI components.
2186    pub cursor_icon_request: Option<winit::window::CursorIcon>,
2187    /// Whether the window is currently minimized.
2188    pub(crate) window_minimized: bool,
2189}
2190
2191impl TesseraRuntime {
2192    /// Executes a closure with a shared, read-only reference to the runtime.
2193    pub fn with<F, R>(f: F) -> R
2194    where
2195        F: FnOnce(&Self) -> R,
2196    {
2197        with_runtime_globals(|globals| globals.with_runtime(f))
2198    }
2199
2200    /// Executes a closure with an exclusive, mutable reference to the runtime.
2201    pub fn with_mut<F, R>(f: F) -> R
2202    where
2203        F: FnOnce(&mut Self) -> R,
2204    {
2205        with_runtime_globals(|globals| f(&mut globals.runtime.borrow_mut()))
2206    }
2207
2208    /// Get the current window size in physical pixels.
2209    pub fn window_size(&self) -> [u32; 2] {
2210        self.window_size
2211    }
2212
2213    pub(crate) fn set_current_children_scale(&mut self, scale: f32) {
2214        self.component_tree
2215            .current_node_mut()
2216            .expect("Scale must be attached to a layout node")
2217            .children_scale = scale;
2218    }
2219
2220    /// Appends a modifier chain to the current component node.
2221    pub(crate) fn append_current_modifier(&mut self, modifier: Modifier) {
2222        if let Some(node) = self.component_tree.current_node_mut() {
2223            node.modifier = node.modifier.clone().then(modifier);
2224        } else {
2225            debug_assert!(
2226                false,
2227                "append_current_modifier must be called inside a component build"
2228            );
2229        }
2230    }
2231
2232    pub(crate) fn push_current_globally_positioned_handler(
2233        &mut self,
2234        handler: crate::component_tree::GloballyPositionedHandler,
2235    ) {
2236        if let Some(node) = self.component_tree.current_node_mut() {
2237            node.interaction_mut()
2238                .globally_positioned_handlers
2239                .push(handler);
2240        } else {
2241            debug_assert!(
2242                false,
2243                "push_current_globally_positioned_handler must be called inside a component build"
2244            );
2245        }
2246    }
2247
2248    pub(crate) fn set_current_accessibility(
2249        &mut self,
2250        accessibility: Option<AccessibilityNode>,
2251        handler: Option<AccessibilityActionHandler>,
2252    ) {
2253        let Some(node_id) = current_node_id() else {
2254            debug_assert!(
2255                false,
2256                "accessibility must be attached inside a component build"
2257            );
2258            return;
2259        };
2260        let metadatas = self.component_tree.metadatas_mut();
2261        if accessibility.is_some() || handler.is_some() {
2262            metadatas
2263                .entry_or_default(node_id)
2264                .set_accessibility(accessibility, handler);
2265        } else if let Some(metadata) = metadatas.get_mut(&node_id) {
2266            metadata.set_accessibility(None, None);
2267        }
2268    }
2269
2270    pub(crate) fn bind_current_focus_requester(&mut self, requester: FocusRequester) {
2271        if let Some(current) = self.component_tree.current_node_mut() {
2272            current.interaction_mut().focus_requester_binding = Some(requester);
2273        } else {
2274            debug_assert!(
2275                false,
2276                "bind_current_focus_requester must be called inside a component build"
2277            );
2278        }
2279    }
2280
2281    pub(crate) fn ensure_current_focus_target(&mut self, node: FocusNode) {
2282        if let Some(current) = self.component_tree.current_node_mut() {
2283            if current.interaction().focus_registration.is_none() {
2284                current.interaction_mut().focus_registration =
2285                    Some(FocusRegistration::target(node));
2286            }
2287        } else {
2288            debug_assert!(
2289                false,
2290                "ensure_current_focus_target must be called inside a component build"
2291            );
2292        }
2293    }
2294
2295    pub(crate) fn ensure_current_focus_scope(&mut self, scope: FocusScopeNode) {
2296        if let Some(current) = self.component_tree.current_node_mut() {
2297            if current.interaction().focus_registration.is_none() {
2298                current.interaction_mut().focus_registration =
2299                    Some(FocusRegistration::scope(scope));
2300            }
2301        } else {
2302            debug_assert!(
2303                false,
2304                "ensure_current_focus_scope must be called inside a component build"
2305            );
2306        }
2307    }
2308
2309    pub(crate) fn ensure_current_focus_group(&mut self, group: FocusGroupNode) {
2310        if let Some(current) = self.component_tree.current_node_mut() {
2311            if current.interaction().focus_registration.is_none() {
2312                current.interaction_mut().focus_registration =
2313                    Some(FocusRegistration::group(group));
2314            }
2315        } else {
2316            debug_assert!(
2317                false,
2318                "ensure_current_focus_group must be called inside a component build"
2319            );
2320        }
2321    }
2322
2323    pub(crate) fn current_focus_target_handle(&self) -> Option<FocusNode> {
2324        let registration = self
2325            .component_tree
2326            .current_node()?
2327            .interaction()
2328            .focus_registration?;
2329        (registration.kind == FocusRegistrationKind::Target)
2330            .then(|| FocusNode::from_handle_id(registration.id))
2331    }
2332
2333    pub(crate) fn current_focus_scope_handle(&self) -> Option<FocusScopeNode> {
2334        let registration = self
2335            .component_tree
2336            .current_node()?
2337            .interaction()
2338            .focus_registration?;
2339        (registration.kind == FocusRegistrationKind::Scope)
2340            .then(|| FocusScopeNode::from_handle_id(registration.id))
2341    }
2342
2343    pub(crate) fn current_focus_group_handle(&self) -> Option<FocusGroupNode> {
2344        let registration = self
2345            .component_tree
2346            .current_node()?
2347            .interaction()
2348            .focus_registration?;
2349        (registration.kind == FocusRegistrationKind::Group)
2350            .then(|| FocusGroupNode::from_handle_id(registration.id))
2351    }
2352
2353    pub(crate) fn set_current_focus_properties(&mut self, properties: FocusProperties) {
2354        if let Some(current) = self.component_tree.current_node_mut() {
2355            if let Some(registration) = current.interaction_mut().focus_registration.as_mut() {
2356                registration.properties = properties;
2357            } else {
2358                debug_assert!(
2359                    false,
2360                    "set_current_focus_properties requires focus_target, focus_scope, or focus_group first"
2361                );
2362            }
2363        } else {
2364            debug_assert!(
2365                false,
2366                "set_current_focus_properties must be called inside a component build"
2367            );
2368        }
2369    }
2370
2371    pub(crate) fn set_current_preferred_tab_stop(&mut self, preferred: bool) {
2372        let registration = self
2373            .component_tree
2374            .current_node_mut()
2375            .and_then(|node| node.interaction_mut().focus_registration.as_mut());
2376        if let Some(registration) = registration {
2377            registration.properties.preferred_tab_stop = preferred;
2378        } else {
2379            debug_assert!(false, "preferred_tab_stop requires a focus registration");
2380        }
2381    }
2382
2383    pub(crate) fn set_current_focus_traversal_policy(&mut self, policy: FocusTraversalPolicy) {
2384        if let Some(current) = self.component_tree.current_node_mut() {
2385            if current
2386                .interaction()
2387                .focus_registration
2388                .is_some_and(|registration| {
2389                    matches!(
2390                        registration.kind,
2391                        FocusRegistrationKind::Scope | FocusRegistrationKind::Group
2392                    )
2393                })
2394            {
2395                current.interaction_mut().focus_traversal_policy = Some(policy);
2396            } else {
2397                debug_assert!(
2398                    false,
2399                    "set_current_focus_traversal_policy requires focus_scope or focus_group first"
2400                );
2401            }
2402        } else {
2403            debug_assert!(
2404                false,
2405                "set_current_focus_traversal_policy must be called inside a component build"
2406            );
2407        }
2408    }
2409
2410    pub(crate) fn set_current_focus_changed_handler(&mut self, handler: CallbackWith<FocusState>) {
2411        if let Some(current) = self.component_tree.current_node_mut() {
2412            current.interaction_mut().focus_changed_handler = Some(handler);
2413        } else {
2414            debug_assert!(
2415                false,
2416                "set_current_focus_changed_handler must be called inside a component build"
2417            );
2418        }
2419    }
2420
2421    pub(crate) fn set_current_focus_event_handler(&mut self, handler: CallbackWith<FocusState>) {
2422        if let Some(current) = self.component_tree.current_node_mut() {
2423            current.interaction_mut().focus_event_handler = Some(handler);
2424        } else {
2425            debug_assert!(
2426                false,
2427                "set_current_focus_event_handler must be called inside a component build"
2428            );
2429        }
2430    }
2431
2432    pub(crate) fn set_current_focus_beyond_bounds_handler(
2433        &mut self,
2434        handler: CallbackWith<FocusDirection, bool>,
2435    ) {
2436        if let Some(current) = self.component_tree.current_node_mut() {
2437            current.interaction_mut().focus_beyond_bounds_handler = Some(handler);
2438        } else {
2439            debug_assert!(
2440                false,
2441                "set_current_focus_beyond_bounds_handler must be called inside a component build"
2442            );
2443        }
2444    }
2445
2446    pub(crate) fn set_current_focus_reveal_handler(
2447        &mut self,
2448        handler: CallbackWith<FocusRevealRequest, bool>,
2449    ) {
2450        if let Some(current) = self.component_tree.current_node_mut() {
2451            current.interaction_mut().focus_reveal_handler = Some(handler);
2452        } else {
2453            debug_assert!(
2454                false,
2455                "set_current_focus_reveal_handler must be called inside a component build"
2456            );
2457        }
2458    }
2459
2460    pub(crate) fn set_current_focus_restorer_fallback(&mut self, fallback: FocusRequester) {
2461        if let Some(current) = self.component_tree.current_node_mut() {
2462            if current
2463                .interaction()
2464                .focus_registration
2465                .is_some_and(|registration| registration.kind == FocusRegistrationKind::Scope)
2466            {
2467                current.interaction_mut().focus_restorer_fallback = Some(fallback);
2468            } else {
2469                debug_assert!(
2470                    false,
2471                    "set_current_focus_restorer_fallback requires focus_scope or focus_restorer first"
2472                );
2473            }
2474        } else {
2475            debug_assert!(
2476                false,
2477                "set_current_focus_restorer_fallback must be called inside a component build"
2478            );
2479        }
2480    }
2481}
2482
2483/// Guard that records the current component node id for the calling thread.
2484/// Nested components push their id and pop on drop, forming a stack.
2485pub struct NodeContextGuard {
2486    popped: bool,
2487    #[cfg(feature = "profiling")]
2488    profiling_guard: Option<crate::profiler::ScopeGuard>,
2489}
2490
2491/// Guard that keeps the current component instance key on the execution stack.
2492pub struct CurrentComponentInstanceGuard {
2493    popped: bool,
2494}
2495
2496/// Guard that keeps the instance key of the layout node currently being
2497/// measured on the execution stack.
2498pub(crate) struct MeasuredNodeInstanceGuard {
2499    popped: bool,
2500}
2501
2502/// Execution phase for `remember` usage checks.
2503#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2504pub enum RuntimePhase {
2505    /// Component render/build phase (allowed for `remember`).
2506    Build,
2507    /// Measurement phase (disallowed for `remember`).
2508    Measure,
2509    /// Child placement and window-position resolution phase.
2510    Place,
2511    /// Drawing command recording phase.
2512    Paint,
2513    /// Input handling phase (disallowed for `remember`).
2514    Input,
2515}
2516
2517/// Guard for execution phase stack.
2518pub struct PhaseGuard {
2519    popped: bool,
2520}
2521
2522impl PhaseGuard {
2523    /// Pop the current phase immediately.
2524    pub fn pop(mut self) {
2525        if !self.popped {
2526            pop_phase();
2527            self.popped = true;
2528        }
2529    }
2530}
2531
2532impl Drop for PhaseGuard {
2533    fn drop(&mut self) {
2534        if !self.popped {
2535            pop_phase();
2536            self.popped = true;
2537        }
2538    }
2539}
2540
2541impl NodeContextGuard {
2542    /// Pop the current node id immediately. Usually you rely on `Drop` instead.
2543    pub fn pop(mut self) {
2544        self.leave();
2545    }
2546
2547    fn leave(&mut self) {
2548        if !self.popped {
2549            self.popped = true;
2550            pop_current_node();
2551        }
2552    }
2553}
2554
2555impl Drop for NodeContextGuard {
2556    fn drop(&mut self) {
2557        #[cfg(feature = "profiling")]
2558        {
2559            let _ = self.profiling_guard.take();
2560        }
2561        self.leave();
2562    }
2563}
2564
2565impl CurrentComponentInstanceGuard {
2566    /// Pop the current component instance key immediately. Usually you rely on
2567    /// `Drop` instead.
2568    pub fn pop(mut self) {
2569        if !self.popped {
2570            pop_current_component_instance_key();
2571            self.popped = true;
2572        }
2573    }
2574}
2575
2576impl Drop for CurrentComponentInstanceGuard {
2577    fn drop(&mut self) {
2578        if !self.popped {
2579            pop_current_component_instance_key();
2580            self.popped = true;
2581        }
2582    }
2583}
2584
2585/// Push the instance key of the layout node currently being measured.
2586pub(crate) fn push_measured_node_instance_key(instance_key: u64) -> MeasuredNodeInstanceGuard {
2587    with_execution_context_mut(|context| {
2588        context.measured_node_instance_key_stack.push(instance_key);
2589    });
2590    MeasuredNodeInstanceGuard { popped: false }
2591}
2592
2593fn pop_measured_node_instance_key() {
2594    with_execution_context_mut(|context| {
2595        let popped = context.measured_node_instance_key_stack.pop();
2596        debug_assert!(
2597            popped.is_some(),
2598            "Attempted to pop measured node instance key from an empty stack"
2599        );
2600    });
2601}
2602
2603impl Drop for MeasuredNodeInstanceGuard {
2604    fn drop(&mut self) {
2605        if !self.popped {
2606            pop_measured_node_instance_key();
2607            self.popped = true;
2608        }
2609    }
2610}
2611
2612/// Instance key of the layout node currently being measured, if any.
2613pub(crate) fn current_measured_node_instance_key() -> Option<u64> {
2614    with_execution_context(|context| context.measured_node_instance_key_stack.last().copied())
2615}
2616
2617pub(crate) fn reserve_component_identity_in(
2618    context: &mut ExecutionContext,
2619    component_type_id: u64,
2620) -> u64 {
2621    if let Some(identity) = context.next_node_instance_logic_id_override.take() {
2622        return identity;
2623    }
2624    let parent_call_index = next_child_instance_call_index(context);
2625    let parent_instance_logic_id = context.instance_logic_id_stack.last().copied().unwrap_or(0);
2626    let group_path_hash = context.group_path.digest();
2627    let has_group_path = !context.group_path.is_empty();
2628    let key_override = context.instance_key_override;
2629
2630    // Combine component_type_id with parent_instance_logic_id, the current
2631    // control-flow group path, and the call index local to that group. This
2632    // ensures:
2633    // 1. foo(1) and foo(2) get different logic_ids (via parent_call_index)
2634    // 2. Components in different control-flow groups get different logic_ids
2635    //    even when each group starts its local call index from zero
2636    // 3. Components in different container instances get different logic_ids
2637    //    (via parent_instance_logic_id)
2638    let instance_salt = if let Some(key_hash) = key_override {
2639        hash_components(&[&key_hash, &group_path_hash, &parent_call_index])
2640    } else if has_group_path {
2641        hash_components(&[&group_path_hash, &parent_call_index])
2642    } else {
2643        parent_call_index
2644    };
2645
2646    if parent_call_index == 0
2647        && parent_instance_logic_id == 0
2648        && key_override.is_none()
2649        && !has_group_path
2650    {
2651        component_type_id
2652    } else {
2653        hash_components(&[
2654            &component_type_id,
2655            &parent_instance_logic_id,
2656            &instance_salt,
2657        ])
2658    }
2659}
2660
2661/// Push the given node id as the current executing component for this thread.
2662pub fn push_current_node(
2663    node_id: NodeId,
2664    component_type_id: u64,
2665    fn_name: &str,
2666) -> NodeContextGuard {
2667    enter_node_context(node_id, fn_name, |context| {
2668        reserve_component_identity_in(context, component_type_id)
2669    })
2670}
2671
2672/// Push the given node id with an already resolved instance logic id.
2673///
2674/// This is used outside build (for example input/measure), where component
2675/// identity must be restored from the recorded tree node rather than derived
2676/// from call order.
2677pub fn push_current_node_with_instance_logic_id(
2678    node_id: NodeId,
2679    instance_logic_id: u64,
2680    fn_name: &str,
2681) -> NodeContextGuard {
2682    enter_node_context(node_id, fn_name, |context| {
2683        next_child_instance_call_index(context);
2684        instance_logic_id
2685    })
2686}
2687
2688fn enter_node_context(
2689    node_id: NodeId,
2690    fn_name: &str,
2691    resolve_identity: impl FnOnce(&mut ExecutionContext) -> u64,
2692) -> NodeContextGuard {
2693    #[cfg(not(feature = "profiling"))]
2694    let _ = fn_name;
2695    #[allow(unused_variables)]
2696    let (parent_node_id, phase) = with_execution_context_mut(|context| {
2697        let identity = resolve_identity(context);
2698        let parent = push_node_context(context, node_id, identity);
2699        (parent, context.phase_stack.last().copied())
2700    });
2701
2702    NodeContextGuard {
2703        popped: false,
2704        #[cfg(feature = "profiling")]
2705        profiling_guard: match phase {
2706            Some(RuntimePhase::Build) => {
2707                crate::profiler::make_build_scope_guard(node_id, parent_node_id, fn_name)
2708            }
2709            _ => None,
2710        },
2711    }
2712}
2713
2714fn push_node_context(
2715    context: &mut ExecutionContext,
2716    node_id: NodeId,
2717    identity: u64,
2718) -> Option<NodeId> {
2719    let parent = context.node_context_stack.last().copied();
2720    context.node_context_stack.push(node_id);
2721    context.instance_logic_id_stack.push(identity);
2722    context.order_frame_stack.push(OrderFrame::default());
2723    parent
2724}
2725
2726struct ComponentBuildContext {
2727    entered_node: bool,
2728    #[cfg(feature = "profiling")]
2729    profiling_guard: Option<crate::profiler::ScopeGuard>,
2730}
2731
2732impl ComponentBuildContext {
2733    fn begin(component_type_id: u64) -> (Self, u64, u64) {
2734        let (logic_id, instance_key) = with_execution_context_mut(|context| {
2735            let logic_id = reserve_component_identity_in(context, component_type_id);
2736            let instance_key = instance_key_in(context, logic_id);
2737            context.phase_stack.push(RuntimePhase::Build);
2738            context.current_component_instance_stack.push(instance_key);
2739            (logic_id, instance_key)
2740        });
2741        (
2742            Self {
2743                entered_node: false,
2744                #[cfg(feature = "profiling")]
2745                profiling_guard: None,
2746            },
2747            logic_id,
2748            instance_key,
2749        )
2750    }
2751
2752    fn enter_node(&mut self, node_id: NodeId, logic_id: u64, name: &str) {
2753        let parent =
2754            with_execution_context_mut(|context| push_node_context(context, node_id, logic_id));
2755        self.entered_node = true;
2756        #[cfg(feature = "profiling")]
2757        {
2758            self.profiling_guard = crate::profiler::make_build_scope_guard(node_id, parent, name);
2759        }
2760        #[cfg(not(feature = "profiling"))]
2761        let _ = (parent, name);
2762    }
2763}
2764
2765impl Drop for ComponentBuildContext {
2766    fn drop(&mut self) {
2767        #[cfg(feature = "profiling")]
2768        drop(self.profiling_guard.take());
2769        with_execution_context_mut(|context| {
2770            let instance = context.current_component_instance_stack.pop();
2771            debug_assert!(instance.is_some(), "component instance must be active");
2772            if self.entered_node {
2773                pop_node_context(context);
2774            }
2775            let phase = context.phase_stack.pop();
2776            debug_assert_eq!(phase, Some(RuntimePhase::Build));
2777        });
2778    }
2779}
2780
2781/// A component build scope that restores the previous node and build context on exit.
2782pub struct ComponentNodeScope {
2783    instance_logic_id: u64,
2784    retained: bool,
2785    _context: ComponentBuildContext,
2786    previous: Option<PreviousBuildNode>,
2787}
2788
2789impl ComponentNodeScope {
2790    /// Return the identity of the component in this scope.
2791    pub fn instance_logic_id(&self) -> u64 {
2792        self.instance_logic_id
2793    }
2794
2795    /// Returns whether the previous subtree is still valid for this call.
2796    pub fn is_retained(&self) -> bool {
2797        self.retained
2798    }
2799}
2800
2801impl Drop for ComponentNodeScope {
2802    fn drop(&mut self) {
2803        TesseraRuntime::with_mut(|runtime| {
2804            let tree = &mut runtime.component_tree;
2805            if let Some(previous) = &self.previous {
2806                let id = tree.current_build_node_id().expect("active component node");
2807                tree.reconcile_build_node(id, previous);
2808            }
2809            let id = tree.current_build_node_id().expect("active component node");
2810            let node = tree.get(id).expect("active component node");
2811            if node.role == NodeRole::Layout && !self.retained {
2812                record_layout_node_dirty(
2813                    self.previous.as_ref().map(|previous| &previous.node),
2814                    node,
2815                );
2816            }
2817            tree.pop_node();
2818        });
2819    }
2820}
2821
2822/// Enter the build scope for one registered component call.
2823pub fn begin_component_node<P: Prop>(
2824    name: &'static str,
2825    component_type_id: u64,
2826    props: &Arc<P>,
2827) -> ComponentNodeScope {
2828    begin_component_node_with(
2829        name,
2830        component_type_id,
2831        props.as_ref(),
2832        &|| props.clone(),
2833        NodeRole::Composition,
2834    )
2835}
2836
2837enum ReplaySnapshotUpdate {
2838    Keep,
2839    Replace(Option<Arc<dyn ErasedComponent>>),
2840}
2841
2842fn begin_component_node_with(
2843    name: &'static str,
2844    component_type_id: u64,
2845    props: &dyn ErasedComponent,
2846    current_replay: &dyn Fn() -> Arc<dyn ErasedComponent>,
2847    role: NodeRole,
2848) -> ComponentNodeScope {
2849    let (mut context, instance_logic_id, instance_key) =
2850        ComponentBuildContext::begin(component_type_id);
2851    let snapshot_update = with_component_replay_tracker(|tracker| {
2852        let Some(previous) = tracker
2853            .previous_nodes
2854            .get_ordered(&instance_key)
2855            .filter(|previous| {
2856                previous.instance_logic_id == instance_logic_id && previous.replay.equals(props)
2857            })
2858        else {
2859            return ReplaySnapshotUpdate::Replace(None);
2860        };
2861        if tracker.mode == ReplayTrackingMode::Partial
2862            && with_execution_context(|context| {
2863                previous.group_path == context.group_path
2864                    && previous.instance_key_override == context.instance_key_override
2865                    && previous.context.ptr_eq(&context.context_map)
2866            })
2867            && tracker.current_nodes.get(&instance_key).is_none()
2868        {
2869            ReplaySnapshotUpdate::Keep
2870        } else {
2871            ReplaySnapshotUpdate::Replace(Some(previous.replay.clone()))
2872        }
2873    });
2874    let pending_dirty = consume_pending_build_invalidation(instance_key);
2875    let props_unchanged = !matches!(snapshot_update, ReplaySnapshotUpdate::Replace(None));
2876    let can_retain =
2877        props_unchanged && !pending_dirty && !is_instance_key_build_dirty(instance_key);
2878    let (id, retained, previous) = TesseraRuntime::with_mut(|runtime| {
2879        let tree = &mut runtime.component_tree;
2880        if can_retain
2881            && let Some(id) = tree.attach_retained_subtree(instance_key, instance_logic_id)
2882        {
2883            let retained = tree.get_mut(id).expect("retained component node");
2884            retained.replay_boundary = true;
2885            retained.props_unchanged_from_previous = true;
2886            return (id, true, None);
2887        }
2888        let mut node = ComponentNode::new(name, role);
2889        node.instance_logic_id = instance_logic_id;
2890        node.instance_key = instance_key;
2891        node.replay_boundary = true;
2892        node.props_unchanged_from_previous = props_unchanged;
2893        let (id, previous) = tree.rebuild_node(node);
2894        (id, false, previous)
2895    });
2896    context.enter_node(id, instance_logic_id, name);
2897    let scope = ComponentNodeScope {
2898        instance_logic_id,
2899        retained,
2900        previous,
2901        _context: context,
2902    };
2903    if let ReplaySnapshotUpdate::Replace(previous_replay) = snapshot_update {
2904        let replay = previous_replay.unwrap_or_else(current_replay);
2905        let snapshot = with_execution_context(|context| ReplayNodeSnapshot {
2906            instance_logic_id,
2907            group_path: context.group_path,
2908            instance_key_override: context.instance_key_override,
2909            context: context.context_map.clone(),
2910            replay,
2911        });
2912        let retired = with_component_replay_tracker_mut(|tracker| {
2913            tracker.current_nodes.insert(instance_key, snapshot)
2914        });
2915        drop(retired);
2916    }
2917    scope
2918}
2919
2920/// Enters the build scope of a component that defines its own layout.
2921pub fn begin_layout_component_node<P: Prop>(
2922    name: &'static str,
2923    component_type_id: u64,
2924    props: &Arc<P>,
2925) -> ComponentNodeScope {
2926    begin_component_node_with(
2927        name,
2928        component_type_id,
2929        props.as_ref(),
2930        &|| props.clone(),
2931        NodeRole::Layout,
2932    )
2933}
2934
2935/// Installs the policy, drawing behavior, and modifiers for the current layout component.
2936pub fn configure_layout_node(
2937    policy: impl Into<LayoutModifierHandle>,
2938    draw: impl Into<DrawModifierHandle>,
2939    modifier: Modifier,
2940) {
2941    let policy = policy.into();
2942    let draw = draw.into().into_node();
2943    // Measurement may mutate local policy state, so keep the build input separate.
2944    let measurement_policy = policy.as_policy().clone_box();
2945    TesseraRuntime::with_mut(|runtime| {
2946        let node = runtime
2947            .component_tree
2948            .current_node_mut()
2949            .expect("active layout node");
2950        assert_eq!(
2951            node.role,
2952            NodeRole::Layout,
2953            "active node must be a layout component"
2954        );
2955        node.layout_node = measurement_policy;
2956        node.layout_input_policy = policy;
2957        node.draw_node = draw;
2958    });
2959    modifier.attach();
2960}
2961
2962/// Push the current component instance key for the active execution scope.
2963pub fn push_current_component_instance_key(instance_key: u64) -> CurrentComponentInstanceGuard {
2964    with_execution_context_mut(|context| {
2965        context.current_component_instance_stack.push(instance_key);
2966    });
2967    CurrentComponentInstanceGuard { popped: false }
2968}
2969
2970fn pop_current_component_instance_key() {
2971    with_execution_context_mut(|context| {
2972        let popped = context.current_component_instance_stack.pop();
2973        debug_assert!(
2974            popped.is_some(),
2975            "Attempted to pop current component instance key from an empty stack"
2976        );
2977    });
2978}
2979
2980fn pop_current_node() {
2981    with_execution_context_mut(pop_node_context);
2982}
2983
2984fn pop_node_context(context: &mut ExecutionContext) {
2985    let popped = context.node_context_stack.pop();
2986    debug_assert!(
2987        popped.is_some(),
2988        "Attempted to pop current node from an empty stack"
2989    );
2990    let order_frame = context.order_frame_stack.pop();
2991    debug_assert!(order_frame.is_some(), "component order frame must exist");
2992    context.instance_logic_id_stack.pop();
2993}
2994
2995/// Get the node id at the top of the active session's component stack.
2996pub fn current_node_id() -> Option<NodeId> {
2997    with_execution_context(|context| context.node_context_stack.last().copied())
2998}
2999
3000fn current_instance_logic_id_opt() -> Option<u64> {
3001    with_execution_context(|context| context.instance_logic_id_stack.last().copied())
3002}
3003
3004/// Returns the current component instance logic id.
3005pub(crate) fn current_instance_logic_id() -> u64 {
3006    current_instance_logic_id_opt()
3007        .expect("current_instance_logic_id must be called inside a component")
3008}
3009
3010/// Returns the instance key for the current component call site.
3011pub(crate) fn current_instance_key() -> u64 {
3012    with_execution_context(|context| {
3013        let instance_logic_id = context
3014            .instance_logic_id_stack
3015            .last()
3016            .expect("current_instance_key must be called inside a component");
3017        instance_key_in(context, *instance_logic_id)
3018    })
3019}
3020
3021fn instance_key_in(context: &ExecutionContext, logic_id: u64) -> u64 {
3022    let group_path_hash = context.group_path.digest();
3023    hash_components(&[&logic_id, &group_path_hash])
3024}
3025
3026/// Push an execution phase for the current thread.
3027pub fn push_phase(phase: RuntimePhase) -> PhaseGuard {
3028    with_execution_context_mut(|context| {
3029        context.phase_stack.push(phase);
3030    });
3031    PhaseGuard { popped: false }
3032}
3033
3034fn pop_phase() {
3035    with_execution_context_mut(|context| {
3036        let popped = context.phase_stack.pop();
3037        debug_assert!(
3038            popped.is_some(),
3039            "Attempted to pop execution phase from an empty stack"
3040        );
3041    });
3042}
3043
3044pub(crate) fn current_phase() -> Option<RuntimePhase> {
3045    with_execution_context(|context| context.phase_stack.last().copied())
3046}
3047
3048#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3049pub(crate) struct GroupPath(Option<u64>);
3050
3051impl GroupPath {
3052    pub(crate) fn extend(self, group_id: u64) -> Self {
3053        Self(Some(hash_components(&[&(self.0, group_id)])))
3054    }
3055
3056    fn digest(self) -> u64 {
3057        self.0.unwrap_or_default()
3058    }
3059
3060    fn is_empty(self) -> bool {
3061        self.0.is_none()
3062    }
3063}
3064
3065#[cfg(test)]
3066fn current_group_path() -> GroupPath {
3067    with_execution_context(|context| context.group_path)
3068}
3069
3070fn current_group_path_hash() -> u64 {
3071    with_execution_context(|context| context.group_path.digest())
3072}
3073
3074fn state_value<T: Send + Sync + 'static>(
3075    table: &SlotTable,
3076    slot: SlotHandle,
3077    generation: u64,
3078) -> Arc<T> {
3079    let entry = table
3080        .entries
3081        .get(slot)
3082        .unwrap_or_else(|| panic!("State points to freed slot: {slot:?}"));
3083    if entry.generation != generation {
3084        panic!(
3085            "State is stale (slot {slot:?}, generation {generation}, current generation {})",
3086            entry.generation
3087        );
3088    }
3089    if entry.key.type_id != TypeId::of::<T>() {
3090        panic!(
3091            "State type mismatch for slot {slot:?}: expected {}, stored {:?}",
3092            std::any::type_name::<T>(),
3093            entry.key.type_id
3094        );
3095    }
3096    clone_slot_value(
3097        entry
3098            .value
3099            .as_ref()
3100            .unwrap_or_else(|| panic!("State slot {slot:?} has been cleared")),
3101        slot,
3102    )
3103}
3104
3105/// Restores a value moved out of the slot table by [`State::with_mut`].
3106struct StateValueRestore<T: Send + Sync + 'static> {
3107    slot: SlotHandle,
3108    generation: u64,
3109    value: Option<T>,
3110}
3111
3112impl<T: Send + Sync + 'static> Drop for StateValueRestore<T> {
3113    fn drop(&mut self) {
3114        if let Some(value) = self.value.take() {
3115            let slot = self.slot;
3116            let generation = self.generation;
3117            let removed = with_slot_table_mut(|table| {
3118                restore_state_value::<T>(table, slot, generation, value)
3119            });
3120            drop(removed);
3121        }
3122    }
3123}
3124
3125fn take_state_value<T: Send + Sync + 'static>(
3126    table: &mut SlotTable,
3127    slot: SlotHandle,
3128    generation: u64,
3129) -> T {
3130    let entry = table
3131        .entries
3132        .get_mut(slot)
3133        .unwrap_or_else(|| panic!("State points to freed slot: {slot:?}"));
3134    if entry.generation != generation {
3135        panic!(
3136            "State is stale (slot {slot:?}, generation {generation}, current generation {})",
3137            entry.generation
3138        );
3139    }
3140    if entry.key.type_id != TypeId::of::<T>() {
3141        panic!(
3142            "State type mismatch for slot {slot:?}: expected {}, stored {:?}",
3143            std::any::type_name::<T>(),
3144            entry.key.type_id
3145        );
3146    }
3147    let value = entry
3148        .value
3149        .as_ref()
3150        .unwrap_or_else(|| panic!("State slot {slot:?} has been cleared"));
3151    assert_eq!(
3152        Arc::strong_count(value),
3153        1,
3154        "State slot {slot:?} is being read"
3155    );
3156    let value = entry.value.take().expect("state value is present");
3157    Arc::try_unwrap(
3158        value
3159            .downcast::<T>()
3160            .unwrap_or_else(|_| panic!("State slot {slot:?} downcast failed")),
3161    )
3162    .unwrap_or_else(|_| unreachable!("state value must be unique"))
3163}
3164
3165fn restore_state_value<T: Send + Sync + 'static>(
3166    table: &mut SlotTable,
3167    slot: SlotHandle,
3168    generation: u64,
3169    value: T,
3170) -> Option<T> {
3171    let Some(entry) = table.entries.get_mut(slot) else {
3172        return Some(value);
3173    };
3174    if entry.generation != generation
3175        || entry.key.type_id != TypeId::of::<T>()
3176        || entry.value.is_some()
3177    {
3178        return Some(value);
3179    }
3180    entry.value = Some(Arc::new(value));
3181    None
3182}
3183
3184fn clone_slot_value<T: Send + Sync + 'static>(
3185    value: &Arc<dyn Any + Send + Sync>,
3186    slot: SlotHandle,
3187) -> Arc<T> {
3188    value
3189        .clone()
3190        .downcast::<T>()
3191        .unwrap_or_else(|_| panic!("Slot {slot:?} downcast failed"))
3192}
3193
3194fn resolve_state_key<T: Send + Sync + 'static>(
3195    key: SlotKey,
3196    retained: bool,
3197    init: impl FnOnce() -> T,
3198) -> State<T> {
3199    if let Some(state) = with_slot_table_mut(|table| {
3200        let slot = table.find_slot(&key)?;
3201        let epoch = table.epoch;
3202        let entry = table.entries.get_mut(slot)?;
3203        entry.last_alive_epoch = epoch;
3204        entry.retained |= retained;
3205        if key.owned_owner.is_none() {
3206            entry.value.as_ref()?;
3207        }
3208        Some(State::new(slot, entry.generation))
3209    }) {
3210        return state;
3211    }
3212    let value = init();
3213    let (state, unused) = with_slot_table_mut(|table| {
3214        if let Some(slot) = table.find_slot(&key) {
3215            let epoch = table.epoch;
3216            let entry = &mut table.entries[slot];
3217            entry.last_alive_epoch = epoch;
3218            entry.retained |= retained;
3219            if entry.value.is_some() || key.owned_owner.is_some() {
3220                return (State::new(slot, entry.generation), Some(value));
3221            }
3222            entry.value = Some(Arc::new(value));
3223            entry.generation = entry
3224                .generation
3225                .checked_add(1)
3226                .expect("state generation exhausted");
3227            return (State::new(slot, entry.generation), None);
3228        }
3229        let slot = table.entries.insert(SlotEntry {
3230            key,
3231            generation: 1,
3232            value: Some(Arc::new(value)),
3233            last_alive_epoch: table.epoch,
3234            retained,
3235        });
3236        table.index_slot(slot);
3237        (State::new(slot, 1), None)
3238    });
3239    drop(unused);
3240    state
3241}
3242
3243pub(crate) fn modifier_attachment_path() -> [u64; 2] {
3244    [
3245        current_group_path_hash(),
3246        next_order_counter(
3247            OrderCounterKind::ModifierAttachment,
3248            "modifier attachment requires a build scope",
3249        ),
3250    ]
3251}
3252
3253pub(crate) fn modifier_state<T: Send + Sync + 'static>(
3254    instance: u64,
3255    path: &[u64],
3256    ordinal: usize,
3257    init: impl FnOnce() -> T,
3258) -> State<T> {
3259    let key = SlotKey {
3260        owned_owner: None,
3261        instance_logic_id: instance,
3262        slot_hash: hash_components(&[&("modifier-state", path, ordinal)]),
3263        type_id: TypeId::of::<T>(),
3264    };
3265    resolve_state_key(key, false, init)
3266}
3267
3268struct ModifierClockLifetime {
3269    session: u128,
3270    key: FrameNanosReceiverKey,
3271    owner: u64,
3272}
3273
3274impl Drop for ModifierClockLifetime {
3275    fn drop(&mut self) {
3276        if crate::session::current_session_identity() != Some(self.session) {
3277            return;
3278        }
3279        let removed = with_frame_clock_tracker_mut(|tracker| {
3280            if let Some(keys) = tracker.receivers_by_owner.get_mut(&self.owner) {
3281                keys.remove(&self.key);
3282                if keys.is_empty() {
3283                    tracker.receivers_by_owner.remove(&self.owner);
3284                }
3285            }
3286            tracker.receivers.remove(&self.key)
3287        });
3288        drop(removed);
3289    }
3290}
3291
3292pub(crate) fn modifier_frame_receiver<F>(
3293    instance: u64,
3294    owner: u64,
3295    path: &[u64],
3296    ordinal: usize,
3297    callback: F,
3298) where
3299    F: FnMut(u64) -> FrameNanosControl + Send + 'static,
3300{
3301    let state = modifier_state(instance, path, ordinal, current_frame_nanos);
3302    let _ = state.get();
3303    let key = FrameNanosReceiverKey {
3304        instance_logic_id: instance,
3305        receiver_hash: hash_components(&[&("modifier-clock", path, ordinal)]),
3306    };
3307    let _lifetime = modifier_state(instance, path, ordinal, || ModifierClockLifetime {
3308        session: crate::session::current_session_identity().expect("modifier clock session"),
3309        key,
3310        owner,
3311    });
3312    let mut unused = Some(callback);
3313    with_frame_clock_tracker_mut(|tracker| {
3314        tracker.receivers.entry(key).or_insert_with(|| {
3315            let mut callback = unused.take().expect("new modifier clock callback");
3316            FrameNanosReceiver {
3317                owner_instance_key: owner,
3318                callback: Box::new(move |nanos| {
3319                    if !state.is_alive() {
3320                        return FrameNanosControl::Stop;
3321                    }
3322                    state.set(nanos);
3323                    callback(nanos)
3324                }),
3325            }
3326        });
3327        tracker
3328            .receivers_by_owner
3329            .entry(owner)
3330            .or_default()
3331            .insert(key);
3332    });
3333    drop(unused);
3334}
3335
3336fn resolve_state_slot<K, F, T>(
3337    key: K,
3338    init: F,
3339    retained: bool,
3340    _type_name: &'static str,
3341) -> State<T>
3342where
3343    K: Hash,
3344    F: FnOnce() -> T,
3345    T: Send + Sync + 'static,
3346{
3347    ensure_build_phase();
3348    let instance_logic_id = current_instance_logic_id();
3349    let group_path_hash = current_group_path_hash();
3350    let key_hash = hash_components(&[&key]);
3351    let positional = key_hash == hash_components(&[&()]);
3352    // Positional state is addressed by the dedicated remember counter, not by
3353    // the number of preceding state slots. Conditional state (for example the
3354    // frame-clock slot inside `receive_frame_nanos`) must not shift later
3355    // `remember` calls onto another value.
3356    let call_counter = next_order_counter(
3357        OrderCounterKind::Remember,
3358        "ORDER_FRAME_STACK is empty; remember must be called inside a component",
3359    );
3360    let type_hash = hash_components(&[&TypeId::of::<T>()]);
3361    // The call counter distinguishes positional calls, while the value type
3362    // keeps calls of different types from sharing one slot key.
3363    let slot_hash = if positional {
3364        hash_components(&[&group_path_hash, &type_hash, &call_counter])
3365    } else {
3366        hash_components(&[&group_path_hash, &key_hash])
3367    };
3368    let slot_key = SlotKey {
3369        owned_owner: None,
3370        instance_logic_id,
3371        slot_hash,
3372        type_id: TypeId::of::<T>(),
3373    };
3374
3375    resolve_state_key(slot_key, retained, init)
3376}
3377
3378#[cfg(test)]
3379fn current_instance_key_override() -> Option<u64> {
3380    with_execution_context(|context| context.instance_key_override)
3381}
3382
3383/// RAII guard that tracks control-flow grouping for the current component node.
3384///
3385/// A guard pushes the provided group id when constructed and pops it when
3386/// dropped, ensuring grouping stays balanced even with early returns or panics.
3387pub struct GroupGuard {
3388    _path: PathGroupGuard,
3389}
3390
3391impl GroupGuard {
3392    /// Push a group id onto the current component's group stack.
3393    pub fn new(group_id: u64) -> Self {
3394        let occurrence_salt = with_execution_context_mut(|context| {
3395            let Some(frame) = context.order_frame_stack.last_mut() else {
3396                return 0;
3397            };
3398            let occurrence = if let Some((_, next)) = frame
3399                .group_occurrences
3400                .iter_mut()
3401                .find(|(id, _)| *id == group_id)
3402            {
3403                let occurrence = *next;
3404                *next = next.wrapping_add(1);
3405                occurrence
3406            } else {
3407                frame.group_occurrences.push((group_id, 1));
3408                0
3409            };
3410            if occurrence == 0 && frame.occurrence_salt == 0 {
3411                0
3412            } else {
3413                hash_components(&[&frame.occurrence_salt, &group_id, &occurrence])
3414            }
3415        });
3416        let guard = Self::keyed(group_id);
3417        with_execution_context_mut(|context| {
3418            context
3419                .order_frame_stack
3420                .last_mut()
3421                .expect("group order frame must exist")
3422                .occurrence_salt = occurrence_salt;
3423        });
3424        guard
3425    }
3426
3427    fn keyed(group_id: u64) -> Self {
3428        let path = PathGroupGuard::new(group_id);
3429        push_order_frame();
3430        Self { _path: path }
3431    }
3432}
3433
3434impl Drop for GroupGuard {
3435    fn drop(&mut self) {
3436        pop_order_frame("ORDER_FRAME_STACK underflow: attempted to pop GroupGuard frame");
3437    }
3438}
3439
3440/// RAII guard for path-only control-flow groups, primarily used for loop
3441/// bodies.
3442///
3443/// Unlike [`GroupGuard`], this guard does not create a new local order frame.
3444/// Repeated iterations therefore continue consuming sibling call indices from
3445/// the surrounding component scope instead of restarting from zero each time.
3446pub struct PathGroupGuard {
3447    previous: GroupPath,
3448    #[cfg(debug_assertions)]
3449    entered: GroupPath,
3450}
3451
3452impl PathGroupGuard {
3453    /// Push a group id onto the current component's group stack without
3454    /// resetting local call-order counters.
3455    pub fn new(group_id: u64) -> Self {
3456        with_execution_context_mut(|context| {
3457            let entered = context.group_path.extend(group_id);
3458            Self {
3459                previous: std::mem::replace(&mut context.group_path, entered),
3460                #[cfg(debug_assertions)]
3461                entered,
3462            }
3463        })
3464    }
3465}
3466
3467impl Drop for PathGroupGuard {
3468    fn drop(&mut self) {
3469        with_execution_context_mut(|context| {
3470            #[cfg(debug_assertions)]
3471            debug_assert_eq!(context.group_path, self.entered, "Unbalanced group scope");
3472            context.group_path = self.previous;
3473        });
3474    }
3475}
3476
3477/// RAII guard that sets a stable instance key for the duration of a block.
3478pub struct InstanceKeyGuard {
3479    key_hash: u64,
3480    previous: Option<u64>,
3481}
3482
3483impl InstanceKeyGuard {
3484    /// Push a key hash for instance identity.
3485    pub fn new(key_hash: u64) -> Self {
3486        let previous =
3487            with_execution_context_mut(|context| context.instance_key_override.replace(key_hash));
3488        Self { key_hash, previous }
3489    }
3490}
3491
3492impl Drop for InstanceKeyGuard {
3493    fn drop(&mut self) {
3494        with_execution_context_mut(|context| {
3495            let popped = std::mem::replace(&mut context.instance_key_override, self.previous);
3496            debug_assert_eq!(
3497                popped,
3498                Some(self.key_hash),
3499                "Unbalanced InstanceKeyGuard stack"
3500            );
3501        });
3502    }
3503}
3504
3505fn hash_components<H: Hash + ?Sized>(parts: &[&H]) -> u64 {
3506    let mut hasher = rustc_hash::FxHasher::default();
3507    for part in parts {
3508        part.hash(&mut hasher);
3509    }
3510    hasher.finish()
3511}
3512
3513fn compute_functor_slot_key() -> (u64, u64) {
3514    with_execution_context_mut(|context| {
3515        let instance_logic_id = *context
3516            .instance_logic_id_stack
3517            .last()
3518            .expect("current_instance_logic_id must be called inside a component");
3519        let group_path_hash = context.group_path.digest();
3520        let call_counter = next_order_counter_in(
3521            context,
3522            OrderCounterKind::Functor,
3523            "ORDER_FRAME_STACK is empty; callback constructors must be called inside a component",
3524        );
3525        let key_hash = hash_components(&[&()]);
3526        let slot_hash = hash_components(&[&group_path_hash, &key_hash, &call_counter]);
3527        (instance_logic_id, slot_hash)
3528    })
3529}
3530
3531pub(crate) fn ensure_build_phase() {
3532    ensure_build_phase_at(current_phase());
3533}
3534
3535pub(crate) fn ensure_build_phase_at(phase: Option<RuntimePhase>) {
3536    match phase {
3537        Some(RuntimePhase::Build) => {}
3538        Some(RuntimePhase::Measure) => {
3539            panic!("remember must not be called inside measure; move state to component render")
3540        }
3541        Some(RuntimePhase::Place) => {
3542            panic!("remember must not be called during placement; move state to component render")
3543        }
3544        Some(RuntimePhase::Paint) => {
3545            panic!("remember must not be called during drawing; move state to component render")
3546        }
3547        Some(RuntimePhase::Input) => {
3548            panic!(
3549                "remember must not be called inside typed input handlers; move state to component render"
3550            )
3551        }
3552        None => panic!(
3553            "remember must be called inside a tessera component. Ensure you're calling this from within a function annotated with #[tessera]."
3554        ),
3555    }
3556}
3557
3558fn remember_functor_cell<T, F>(init: F) -> (Arc<T>, FunctorHandle)
3559where
3560    T: Send + Sync + 'static,
3561    F: FnOnce() -> T,
3562{
3563    ensure_build_phase();
3564    let (instance_logic_id, slot_hash) = compute_functor_slot_key();
3565    let slot_key = SlotKey {
3566        owned_owner: None,
3567        instance_logic_id,
3568        slot_hash,
3569        type_id: TypeId::of::<T>(),
3570    };
3571
3572    let mut init = Some(init);
3573    let (value, slot, generation) = with_slot_table_mut(|table| {
3574        let epoch = table.epoch;
3575        if let Some((slot, entry)) = table.try_fast_slot_lookup(&slot_key) {
3576            let (value, generation) = entry.reuse_functor(slot, epoch, &mut init);
3577            return (value, slot, generation);
3578        }
3579        if let Some(slot) = table.find_slot(&slot_key) {
3580            table.record_slot_usage_slow(instance_logic_id, slot);
3581            let (value, generation) = table.entries[slot].reuse_functor(slot, epoch, &mut init);
3582            return (value, slot, generation);
3583        }
3584
3585        let value = Arc::new(init.take().expect("functor slot initialized once")());
3586        let generation = 1;
3587        let slot = table.entries.insert(SlotEntry {
3588            key: slot_key,
3589            generation,
3590            value: Some(value.clone()),
3591            last_alive_epoch: epoch,
3592            retained: false,
3593        });
3594        table.index_slot(slot);
3595        table.record_slot_usage_slow(instance_logic_id, slot);
3596        (value, slot, generation)
3597    });
3598    (value, FunctorHandle::new(slot, generation))
3599}
3600
3601fn load_functor_cell<T>(handle: FunctorHandle) -> Arc<T>
3602where
3603    T: Send + Sync + 'static,
3604{
3605    assert_eq!(
3606        crate::session::current_session_identity(),
3607        Some(handle.session),
3608        "functor belongs to a different runtime session"
3609    );
3610    with_slot_table(|table| {
3611        let entry = table
3612            .entries
3613            .get(handle.slot)
3614            .unwrap_or_else(|| panic!("Callback points to freed slot: {:?}", handle.slot));
3615
3616        if entry.generation != handle.generation {
3617            panic!(
3618                "Callback is stale (slot {:?}, generation {}, current generation {})",
3619                handle.slot, handle.generation, entry.generation
3620            );
3621        }
3622
3623        if entry.key.type_id != TypeId::of::<T>() {
3624            panic!(
3625                "Callback type mismatch for slot {:?}: expected {}, stored {:?}",
3626                handle.slot,
3627                std::any::type_name::<T>(),
3628                entry.key.type_id
3629            );
3630        }
3631
3632        clone_slot_value::<T>(
3633            entry
3634                .value
3635                .as_ref()
3636                .unwrap_or_else(|| panic!("Callback slot {:?} has been cleared", handle.slot)),
3637            handle.slot,
3638        )
3639    })
3640}
3641
3642pub(crate) fn remember_callback_handle<F>(handler: F) -> FunctorHandle
3643where
3644    F: Fn() + Send + Sync + 'static,
3645{
3646    let handler = Arc::new(handler) as Arc<dyn Fn() + Send + Sync>;
3647    let (cell, handle) = remember_functor_cell({
3648        let handler = Arc::clone(&handler);
3649        move || CallbackCell::new(handler)
3650    });
3651    cell.update(handler);
3652    handle
3653}
3654
3655pub(crate) fn invoke_callback_handle(handle: FunctorHandle) {
3656    let callback = load_functor_cell::<CallbackCell>(handle).shared();
3657    callback();
3658}
3659
3660pub(crate) fn remember_render_slot_handle<F>(render: F) -> FunctorHandle
3661where
3662    F: Fn() + Send + Sync + 'static,
3663{
3664    let static_type = (std::mem::size_of::<F>() == 0).then(TypeId::of::<F>);
3665    let render = Arc::new(render) as Arc<dyn Fn() + Send + Sync>;
3666    let creator_instance_key = current_replay_boundary_instance_key_from_scope()
3667        .unwrap_or_else(|| panic!("RenderSlot handles must be created during a component build"));
3668    let (cell, handle) = remember_functor_cell({
3669        let render = Arc::clone(&render);
3670        move || RenderSlotCell::new(render, static_type)
3671    });
3672    if !cell.update(render, static_type) {
3673        return handle;
3674    }
3675    invalidate_render_slot_readers(handle, creator_instance_key);
3676    handle
3677}
3678
3679pub(crate) fn invoke_render_slot_handle(handle: FunctorHandle) {
3680    let render = load_functor_cell::<RenderSlotCell>(handle).shared();
3681    render();
3682}
3683
3684pub(crate) fn remember_render_slot_with_handle<T, F>(render: F) -> FunctorHandle
3685where
3686    T: 'static,
3687    F: Fn(T) + Send + Sync + 'static,
3688{
3689    let static_type = (std::mem::size_of::<F>() == 0).then(TypeId::of::<F>);
3690    let render = Arc::new(render) as Arc<dyn Fn(T) + Send + Sync>;
3691    let creator_instance_key =
3692        current_replay_boundary_instance_key_from_scope().unwrap_or_else(|| {
3693            panic!("RenderSlotWith handles must be created during a component build")
3694        });
3695    let (cell, handle) = remember_functor_cell({
3696        let render = Arc::clone(&render);
3697        move || RenderSlotWithCell::new(render, static_type)
3698    });
3699    if !cell.update(render, static_type) {
3700        return handle;
3701    }
3702    invalidate_render_slot_readers(handle, creator_instance_key);
3703    handle
3704}
3705
3706pub(crate) fn invoke_render_slot_with_handle<T>(handle: FunctorHandle, value: T)
3707where
3708    T: 'static,
3709{
3710    let render = load_functor_cell::<RenderSlotWithCell<T>>(handle).shared();
3711    render(value)
3712}
3713
3714pub(crate) fn remember_callback_with_handle<T, R, F>(handler: F) -> FunctorHandle
3715where
3716    T: 'static,
3717    R: 'static,
3718    F: Fn(T) -> R + Send + Sync + 'static,
3719{
3720    let handler = Arc::new(handler) as Arc<dyn Fn(T) -> R + Send + Sync>;
3721    let (cell, handle) = remember_functor_cell({
3722        let handler = Arc::clone(&handler);
3723        move || CallbackWithCell::new(handler)
3724    });
3725    cell.update(handler);
3726    handle
3727}
3728
3729pub(crate) fn invoke_callback_with_handle<T, R>(handle: FunctorHandle, value: T) -> R
3730where
3731    T: 'static,
3732    R: 'static,
3733{
3734    let callback = load_functor_cell::<CallbackWithCell<T, R>>(handle).shared();
3735    callback(value)
3736}
3737
3738/// Start a new state-slot epoch for the current recomposition pass.
3739pub fn begin_recompose_slot_epoch() {
3740    with_slot_table_mut(SlotTable::begin_epoch);
3741}
3742
3743pub(crate) fn shutdown_runtime() {
3744    clear_frame_nanos_receivers();
3745    reset_slots();
3746    reset_component_replay_tracking();
3747    clear_redraw_waker();
3748    let tree = TesseraRuntime::with_mut(|runtime| std::mem::take(&mut runtime.component_tree));
3749    drop(tree);
3750}
3751
3752pub(crate) fn reset_component_slots() {
3753    crate::component_call::reset_pending_calls();
3754    crate::context::reset_context_slots();
3755    let removed = with_slot_table_mut(|table| {
3756        let slots = table
3757            .entries
3758            .iter()
3759            .filter_map(|(slot, entry)| entry.key.owned_owner.is_none().then_some(slot))
3760            .collect::<Vec<_>>();
3761        slots
3762            .into_iter()
3763            .filter_map(|slot| table.remove_slot(slot))
3764            .collect::<Vec<_>>()
3765    });
3766    drop(removed);
3767}
3768
3769/// Releases all slots when the UI session shuts down.
3770pub fn reset_slots() {
3771    crate::component_call::reset_pending_calls();
3772    crate::context::reset_context_slots();
3773    let removed = with_slot_table_mut(SlotTable::reset);
3774    drop(removed);
3775}
3776
3777pub(crate) fn recycle_recomposed_slots_for_instance_logic_ids(instance_logic_ids: &HashSet<u64>) {
3778    if instance_logic_ids.is_empty() {
3779        return;
3780    }
3781
3782    let removed = with_slot_table_mut(|table| {
3783        let epoch = table.epoch;
3784        let mut freed = Vec::new();
3785
3786        for slot in table.slots_for_instances(instance_logic_ids) {
3787            let Some(entry) = table.entries.get(slot) else {
3788                continue;
3789            };
3790            if entry.last_alive_epoch == epoch || entry.retained {
3791                continue;
3792            }
3793            freed.push(slot);
3794        }
3795
3796        let mut removed = Vec::with_capacity(freed.len());
3797        for slot in freed {
3798            removed.extend(table.remove_slot(slot));
3799        }
3800        removed
3801    });
3802    drop(removed);
3803}
3804
3805pub(crate) fn live_slot_instance_logic_ids() -> HashSet<u64> {
3806    with_slot_table(|table| table.slots_by_owner.keys().copied().collect())
3807}
3808
3809/// Initializes state by an explicit key within the current component build.
3810///
3811/// Use stable business keys for reordered collections. The initializer runs only
3812/// when the slot is first used; see [`State`] for access and lifetime rules.
3813pub fn remember_with_key<K, F, T>(key: K, init: F) -> State<T>
3814where
3815    K: Hash,
3816    F: FnOnce() -> T,
3817    T: Send + Sync + 'static,
3818{
3819    resolve_state_slot(key, init, false, "remember_with_key")
3820}
3821
3822/// Initializes state by call order and control-flow position during a component build.
3823///
3824/// Use [`remember_with_key`] for reordered collections; see [`State`] for access
3825/// and lifetime rules.
3826pub fn remember<F, T>(init: F) -> State<T>
3827where
3828    F: FnOnce() -> T,
3829    T: Send + Sync + 'static,
3830{
3831    remember_with_key((), init)
3832}
3833
3834/// Initializes keyed state during a build and keeps it until the component unmounts.
3835///
3836/// Unused retained slots survive conditional branches; see [`State`] for access
3837/// rules and [`StateOwner`] for independent controller or route lifetimes.
3838pub fn retain_with_key<K, F, T>(key: K, init: F) -> State<T>
3839where
3840    K: Hash,
3841    F: FnOnce() -> T,
3842    T: Send + Sync + 'static,
3843{
3844    resolve_state_slot(key, init, true, "retain_with_key")
3845}
3846
3847/// Initializes state by call order during a build, retaining it until unmount.
3848///
3849/// Use [`retain_with_key`] for reordered collections; see [`State`] for access
3850/// and lifetime rules.
3851pub fn retain<F, T>(init: F) -> State<T>
3852where
3853    F: FnOnce() -> T,
3854    T: Send + Sync + 'static,
3855{
3856    retain_with_key((), init)
3857}
3858
3859/// Groups the execution of a block of code with a stable key.
3860///
3861/// This is useful for maintaining state identity in dynamic lists or loops
3862/// where the order of items might change.
3863///
3864/// # Examples
3865///
3866/// ```
3867/// use tessera_ui::{key, remember, tessera};
3868///
3869/// #[tessera]
3870/// fn my_list(items: Vec<String>) {
3871///     for item in items.iter() {
3872///         key(item.clone(), || {
3873///             let state = remember(|| 0);
3874///         });
3875///     }
3876/// }
3877/// ```
3878pub fn key<K, F, R>(key: K, block: F) -> R
3879where
3880    K: Hash,
3881    F: FnOnce() -> R,
3882{
3883    let key_hash = hash_components(&[&key]);
3884    let _group_guard = GroupGuard::keyed(key_hash);
3885    let _instance_guard = InstanceKeyGuard::new(key_hash);
3886    block()
3887}
3888
3889#[cfg(test)]
3890mod tests {
3891    use std::sync::atomic::{AtomicUsize, Ordering};
3892
3893    use super::*;
3894    use crate::component_tree::{ComponentNode, NodeRole};
3895    use crate::execution_context::{
3896        reset_execution_context, with_execution_context, with_execution_context_mut,
3897    };
3898    use crate::layout::MeasureScope;
3899    use crate::modifier::{LayoutModifierChild, LayoutModifierInput, LayoutModifierNode};
3900    use crate::prop::{CallbackWith, RenderSlot};
3901    use crate::tessera;
3902
3903    #[derive(Clone, Default, PartialEq)]
3904    struct ConditionalLoopProbe {
3905        values: crate::testing::TestProbe<[Option<State<usize>>; 3]>,
3906        order: crate::testing::TestProbe<Option<State<Vec<usize>>>>,
3907        tick: crate::testing::TestProbe<Option<State<usize>>>,
3908    }
3909
3910    #[derive(Clone, PartialEq)]
3911    struct ConditionalLoopWidth(State<usize>);
3912
3913    impl LayoutModifierNode for ConditionalLoopWidth {
3914        fn measure(
3915            &self,
3916            _scope: &mut MeasureScope<'_>,
3917        ) -> Result<crate::LayoutResult, crate::MeasurementError> {
3918            Ok(crate::LayoutResult::new(crate::ComputedData {
3919                width: crate::Px(self.0.get() as i32),
3920                height: crate::Px(10),
3921            }))
3922        }
3923    }
3924
3925    struct ConditionalLoopTag(usize);
3926
3927    impl crate::SemanticsModifierNode for ConditionalLoopTag {
3928        fn apply(
3929            &self,
3930            semantics: &mut crate::AccessibilityNode,
3931            _handler: &mut Option<crate::AccessibilityActionHandler>,
3932        ) {
3933            *semantics = semantics.clone().with_key(format!("loop-item-{}", self.0));
3934        }
3935    }
3936
3937    #[derive(Clone, Copy, PartialEq)]
3938    struct ConditionalLoopRow;
3939
3940    impl LayoutModifierNode for ConditionalLoopRow {
3941        fn measure(
3942            &self,
3943            scope: &mut MeasureScope<'_>,
3944        ) -> Result<crate::LayoutResult, crate::MeasurementError> {
3945            let mut result = crate::LayoutResult::new(crate::ComputedData::min_from_constraint(
3946                scope.parent_constraint().as_ref(),
3947            ));
3948            let mut x = crate::Px::ZERO;
3949            for child in scope.children() {
3950                let measured = child.measure(scope, &crate::Constraint::NONE)?;
3951                result.place_child(measured, crate::PxPosition::new(x, crate::Px::ZERO));
3952                x += measured.width;
3953            }
3954            Ok(result)
3955        }
3956    }
3957
3958    #[tessera(crate)]
3959    fn conditional_loop_item(index: usize, probe: ConditionalLoopProbe) {
3960        let value = remember(|| index);
3961        let mut values = probe.values.get();
3962        values[index] = Some(value);
3963        probe.values.set(values);
3964        crate::layout::layout()
3965            .policy(ConditionalLoopWidth(value))
3966            .modifier(crate::Modifier::new().push_semantics(ConditionalLoopTag(index)));
3967    }
3968
3969    #[tessera(crate)]
3970    fn conditional_loop_fixture(keyed: bool, probe: ConditionalLoopProbe) {
3971        let order = remember(|| vec![0, 1, 2]);
3972        let tick = remember(|| 0);
3973        probe.order.set(Some(order));
3974        probe.tick.set(Some(tick));
3975        crate::layout::layout()
3976            .policy(ConditionalLoopRow)
3977            .child(move || {
3978                let _ = tick.get();
3979                for index in order.get() {
3980                    if index > 0 {
3981                        if keyed {
3982                            key(index, || {
3983                                conditional_loop_item(index, probe.clone());
3984                            });
3985                        } else {
3986                            conditional_loop_item(index, probe.clone());
3987                        }
3988                    }
3989                }
3990            });
3991    }
3992
3993    #[test]
3994    fn conditional_loop_components_keep_independent_state_through_replay_and_keyed_reorder() {
3995        let _runtime_session = crate::RuntimeSession::new();
3996        let _runtime_scope = _runtime_session.enter();
3997        for keyed in [false, true] {
3998            let probe = ConditionalLoopProbe::default();
3999            let mut session = crate::testing::start_layout_test_session(
4000                crate::testing::layout_test({
4001                    let probe = probe.clone();
4002                    move || conditional_loop_fixture(keyed, probe.clone())
4003                })
4004                .viewport_px(100, 20),
4005            );
4006            let initial = crate::testing::pump_layout_test_session(&mut session, 0);
4007            let values = probe.values.get();
4008            let first = values[1].unwrap();
4009            let second = values[2].unwrap();
4010            assert_eq!(first.get(), 1);
4011            assert_eq!(second.get(), 2);
4012            initial.node("loop-item-1").size(1, 10).position(0, 0);
4013            initial.node("loop-item-2").size(2, 10).position(1, 0);
4014
4015            first.set(11);
4016            let changed = crate::testing::pump_layout_test_session(&mut session, 16_666_667);
4017            changed.node("loop-item-1").size(11, 10).position(0, 0);
4018            changed.node("loop-item-2").size(2, 10).position(11, 0);
4019            assert_eq!(second.get(), 2);
4020            probe.tick.get().unwrap().set(1);
4021            let replayed = crate::testing::pump_layout_test_session(&mut session, 33_333_334);
4022            replayed.node("loop-item-1").size(11, 10).position(0, 0);
4023            replayed.node("loop-item-2").size(2, 10).position(11, 0);
4024
4025            if keyed {
4026                probe.order.get().unwrap().set(vec![0, 2, 1]);
4027                let reordered = crate::testing::pump_layout_test_session(&mut session, 50_000_001);
4028                reordered.node("loop-item-2").size(2, 10).position(0, 0);
4029                reordered.node("loop-item-1").size(11, 10).position(2, 0);
4030                assert!(probe.values.get()[1].unwrap() == first);
4031                assert!(probe.values.get()[2].unwrap() == second);
4032
4033                probe.order.get().unwrap().set(vec![0, 2]);
4034                let removed = crate::testing::pump_layout_test_session(&mut session, 66_666_668);
4035                removed.node("loop-item-2").size(2, 10).position(0, 0);
4036                assert!(!first.is_alive());
4037                assert!(second.is_alive());
4038            }
4039        }
4040    }
4041
4042    #[tessera(crate)]
4043    fn required_value_component(value: Option<usize>, on_value: Option<CallbackWith<usize>>) {
4044        let value = value.expect("missing required prop `value` in `required_value_component`");
4045        if let Some(on_value) = on_value {
4046            on_value.call(value);
4047        }
4048    }
4049
4050    #[tessera(crate)]
4051    fn optional_value_component(
4052        value: Option<usize>,
4053        on_value: Option<CallbackWith<Option<usize>>>,
4054    ) {
4055        if let Some(on_value) = on_value {
4056            on_value.call(value);
4057        }
4058    }
4059
4060    fn seed_previous_replay_snapshot(instance_key: u64) {
4061        #[derive(Clone)]
4062        struct Noop;
4063
4064        impl Prop for Noop {
4065            fn prop_eq(&self, _: &Self) -> bool {
4066                true
4067            }
4068
4069            fn run_component(self: Arc<Self>) {}
4070        }
4071
4072        with_component_replay_tracker_mut(|tracker| {
4073            tracker.previous_nodes.insert(
4074                instance_key,
4075                ReplayNodeSnapshot {
4076                    instance_logic_id: 0,
4077                    group_path: GroupPath::default(),
4078                    instance_key_override: None,
4079                    context: ContextMap::default(),
4080                    replay: Arc::new(Noop),
4081                },
4082            );
4083        });
4084    }
4085
4086    #[test]
4087    fn replay_snapshot_cursor_tracks_order_and_recovers_after_reordering() {
4088        let _runtime_session = crate::RuntimeSession::new();
4089        let _runtime_scope = _runtime_session.enter();
4090        for key in [11, 22, 33, 44] {
4091            seed_previous_replay_snapshot(key);
4092        }
4093
4094        with_component_replay_tracker(|tracker| {
4095            let snapshots = &tracker.previous_nodes;
4096            let root = snapshots.get_for_replay_root(&22).expect("root snapshot");
4097            assert_eq!(snapshots.next_ordered.get(), 1);
4098            assert!(std::ptr::eq(root, snapshots.get_ordered(&22).unwrap()));
4099            assert_eq!(snapshots.next_ordered.get(), 2);
4100            assert!(std::ptr::eq(
4101                snapshots.get_ordered(&33).unwrap(),
4102                snapshots.get(&33).unwrap(),
4103            ));
4104            assert_eq!(snapshots.next_ordered.get(), 3);
4105            assert!(std::ptr::eq(
4106                snapshots.get_ordered(&11).unwrap(),
4107                snapshots.get(&11).unwrap(),
4108            ));
4109        });
4110
4111        remove_previous_component_replay_nodes(&[22].into_iter().collect());
4112        with_component_replay_tracker(|tracker| {
4113            let snapshots = &tracker.previous_nodes;
4114            assert!(std::ptr::eq(
4115                snapshots.get_ordered(&44).unwrap(),
4116                snapshots.get(&44).unwrap(),
4117            ));
4118            assert!(snapshots.get_ordered(&22).is_none());
4119        });
4120    }
4121
4122    #[test]
4123    fn removing_a_replay_snapshot_preserves_lookup_for_the_moved_snapshot() {
4124        let _runtime_session = crate::RuntimeSession::new();
4125        let _runtime_scope = _runtime_session.enter();
4126        for key in [11, 22, 33] {
4127            seed_previous_replay_snapshot(key);
4128        }
4129
4130        remove_previous_component_replay_nodes(&[11].into_iter().collect());
4131        assert!(previous_component_replay_node(11).is_none());
4132        assert!(previous_component_replay_node(33).is_some());
4133        assert!(previous_component_replay_node(22).is_some());
4134
4135        remove_previous_component_replay_nodes(&[33].into_iter().collect());
4136        assert!(previous_component_replay_node(33).is_none());
4137        assert!(previous_component_replay_node(22).is_some());
4138    }
4139
4140    #[test]
4141    fn partial_replay_publishes_all_snapshots_before_dropping_old_props() {
4142        #[derive(Clone)]
4143        struct ReleaseOnDrop(crate::testing::TestProbe<bool>);
4144
4145        impl Prop for ReleaseOnDrop {
4146            fn prop_eq(&self, _: &Self) -> bool {
4147                true
4148            }
4149
4150            fn run_component(self: Arc<Self>) {}
4151        }
4152
4153        impl Drop for ReleaseOnDrop {
4154            fn drop(&mut self) {
4155                assert_eq!(
4156                    previous_component_replay_node(11)
4157                        .unwrap()
4158                        .instance_logic_id,
4159                    11
4160                );
4161                assert!(previous_component_replay_node(22).is_some());
4162                self.0.set(true);
4163            }
4164        }
4165
4166        let _runtime_session = crate::RuntimeSession::new();
4167        let _runtime_scope = _runtime_session.enter();
4168        let released = crate::testing::TestProbe::new(false);
4169        seed_previous_replay_snapshot(11);
4170        with_component_replay_tracker_mut(|tracker| {
4171            let mut snapshot = tracker.previous_nodes.get(&11).unwrap().clone();
4172            tracker.previous_nodes.insert(
4173                11,
4174                ReplayNodeSnapshot {
4175                    replay: Arc::new(ReleaseOnDrop(released.clone())),
4176                    ..snapshot.clone()
4177                },
4178            );
4179            snapshot.instance_logic_id = 11;
4180            tracker.current_nodes.insert(11, snapshot.clone());
4181            tracker.current_nodes.insert(22, snapshot);
4182        });
4183
4184        finalize_frame_component_replay_tracking_partial();
4185        assert!(released.get());
4186    }
4187
4188    fn with_test_component_scope<R>(component_type_id: u64, f: impl FnOnce() -> R) -> R {
4189        reset_execution_context();
4190        let mut arena = crate::Arena::<()>::new();
4191        let node_id = arena.new_node(());
4192        let _phase_guard = push_phase(RuntimePhase::Build);
4193        let _node_guard = push_current_node(node_id, component_type_id, "test_component");
4194        let _instance_guard = push_current_component_instance_key(current_instance_key());
4195        crate::component_call::with_pending_scope(f)
4196    }
4197
4198    #[test]
4199    fn state_liveness_survives_updates_and_rejects_expired_slots() {
4200        let _runtime_session = crate::RuntimeSession::new();
4201        let _runtime_scope = _runtime_session.enter();
4202        reset_slots();
4203        let state = with_test_component_scope(815, || remember(|| 4_u32));
4204        assert!(state.is_alive());
4205        let other = with_test_component_scope(816, || remember(|| 0_u32));
4206        state.with(|value| {
4207            other.set(*value + 1);
4208            assert_eq!(state.get(), 4);
4209            assert!(std::panic::catch_unwind(|| state.set(9)).is_err());
4210            assert_eq!(state.get(), 4);
4211        });
4212        assert_eq!(other.get(), 5);
4213        state.with_mut(|value| {
4214            assert!(state.is_alive());
4215            *value = 5;
4216        });
4217        assert_eq!(state.get(), 5);
4218        reset_slots();
4219        assert!(!state.is_alive());
4220        let replacement = with_test_component_scope(815, || remember(|| 7_u32));
4221        assert!(replacement.is_alive());
4222        assert!(!state.is_alive());
4223    }
4224
4225    #[test]
4226    fn receive_frame_nanos_panics_without_component_scope() {
4227        let _runtime_session = crate::RuntimeSession::new();
4228        let _runtime_scope = _runtime_session.enter();
4229        reset_frame_clock();
4230        begin_frame_clock(Instant::now());
4231
4232        let result = std::panic::catch_unwind(|| {
4233            receive_frame_nanos(|_| FrameNanosControl::Continue);
4234        });
4235        assert!(result.is_err());
4236    }
4237
4238    #[test]
4239    fn tick_frame_nanos_receivers_removes_stopped_receivers() {
4240        let _runtime_session = crate::RuntimeSession::new();
4241        let _runtime_scope = _runtime_session.enter();
4242        reset_frame_clock();
4243        begin_frame_clock(Instant::now());
4244
4245        with_frame_clock_tracker_mut(|tracker| {
4246            tracker.receivers.insert(
4247                FrameNanosReceiverKey {
4248                    instance_logic_id: 1,
4249                    receiver_hash: 1,
4250                },
4251                FrameNanosReceiver {
4252                    owner_instance_key: 123,
4253                    callback: Box::new(|_| FrameNanosControl::Stop),
4254                },
4255            );
4256        });
4257
4258        tick_frame_nanos_receivers();
4259        assert!(with_frame_clock_tracker(|tracker| tracker
4260            .receivers
4261            .is_empty()));
4262    }
4263
4264    #[test]
4265    fn with_build_dirty_instance_keys_marks_current_scope() {
4266        let _runtime_session = crate::RuntimeSession::new();
4267        let _runtime_scope = _runtime_session.enter();
4268        let mut outer = HashSet::default();
4269        outer.insert(7);
4270
4271        assert!(!is_instance_key_build_dirty(7));
4272        with_build_dirty_instance_keys(Arc::new(outer), || {
4273            assert!(is_instance_key_build_dirty(7));
4274            assert!(!is_instance_key_build_dirty(8));
4275
4276            let mut inner = HashSet::default();
4277            inner.insert(8);
4278            with_build_dirty_instance_keys(Arc::new(inner), || {
4279                assert!(!is_instance_key_build_dirty(7));
4280                assert!(is_instance_key_build_dirty(8));
4281            });
4282
4283            assert!(is_instance_key_build_dirty(7));
4284            assert!(!is_instance_key_build_dirty(8));
4285        });
4286        assert!(!is_instance_key_build_dirty(7));
4287    }
4288
4289    #[test]
4290    fn with_build_dirty_instance_keys_restores_on_panic() {
4291        let _runtime_session = crate::RuntimeSession::new();
4292        let _runtime_scope = _runtime_session.enter();
4293        let mut dirty = HashSet::default();
4294        dirty.insert(11);
4295
4296        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4297            with_build_dirty_instance_keys(Arc::new(dirty), || {
4298                assert!(is_instance_key_build_dirty(11));
4299                panic!("expected panic");
4300            });
4301        }));
4302        assert!(result.is_err());
4303        assert!(!is_instance_key_build_dirty(11));
4304    }
4305
4306    #[derive(Clone, PartialEq)]
4307    struct DirtySplitPolicy {
4308        measure_key: u32,
4309        placement_key: u32,
4310    }
4311
4312    impl crate::modifier::LayoutModifierNode for DirtySplitPolicy {
4313        fn measure(
4314            &self,
4315            _input: &mut MeasureScope<'_>,
4316        ) -> Result<crate::LayoutResult, crate::MeasurementError> {
4317            Ok(crate::LayoutResult::new(crate::ComputedData::ZERO))
4318        }
4319
4320        fn measure_eq(&self, other: &Self) -> bool {
4321            self.measure_key == other.measure_key
4322        }
4323
4324        fn placement_eq(&self, other: &Self) -> bool {
4325            self.placement_key == other.placement_key
4326        }
4327    }
4328
4329    #[derive(Clone, Copy, PartialEq)]
4330    struct DirtyMeasureModifierNode {
4331        width: u32,
4332    }
4333
4334    impl LayoutModifierNode for DirtyMeasureModifierNode {
4335        fn measure_content(
4336            &self,
4337            _input: &LayoutModifierInput<'_>,
4338            child: &mut dyn LayoutModifierChild,
4339        ) -> Result<crate::LayoutResult, crate::MeasurementError> {
4340            let size = child.measure(&crate::Constraint::exact(
4341                crate::Px::new(self.width as i32),
4342                crate::Px::new(10),
4343            ))?;
4344            child.place(crate::PxPosition::ZERO);
4345            Ok(crate::LayoutResult::new(size))
4346        }
4347    }
4348
4349    fn dirty_test_node(policy: DirtySplitPolicy, modifier: Modifier) -> ComponentNode {
4350        let mut node = ComponentNode::new("dirty_test", NodeRole::Layout);
4351        node.instance_key = 1;
4352        node.layout_input_policy = LayoutModifierHandle::from(policy);
4353        node.modifier = modifier;
4354        node
4355    }
4356
4357    #[test]
4358    fn layout_dirty_tracking_separates_measure_and_placement_changes() {
4359        let _runtime_session = crate::RuntimeSession::new();
4360        let _runtime_scope = _runtime_session.enter();
4361        reset_layout_dirty_tracking();
4362
4363        let first = dirty_test_node(
4364            DirtySplitPolicy {
4365                measure_key: 0,
4366                placement_key: 0,
4367            },
4368            Modifier::new(),
4369        );
4370        begin_frame_layout_dirty_tracking();
4371        record_layout_node_dirty(None, &first);
4372        finalize_frame_layout_dirty_tracking();
4373        let dirty = take_layout_dirty_nodes();
4374        assert!(dirty.measure_self_nodes.contains(&1));
4375        assert!(dirty.placement_self_nodes.is_empty());
4376
4377        let second = dirty_test_node(
4378            DirtySplitPolicy {
4379                measure_key: 0,
4380                placement_key: 1,
4381            },
4382            Modifier::new(),
4383        );
4384        begin_frame_layout_dirty_tracking();
4385        record_layout_node_dirty(Some(&first), &second);
4386        finalize_frame_layout_dirty_tracking();
4387        let dirty = take_layout_dirty_nodes();
4388        assert!(!dirty.measure_self_nodes.contains(&1));
4389        assert!(dirty.placement_self_nodes.contains(&1));
4390
4391        let third = dirty_test_node(
4392            DirtySplitPolicy {
4393                measure_key: 1,
4394                placement_key: 1,
4395            },
4396            Modifier::new(),
4397        );
4398        begin_frame_layout_dirty_tracking();
4399        record_layout_node_dirty(Some(&second), &third);
4400        finalize_frame_layout_dirty_tracking();
4401        let dirty = take_layout_dirty_nodes();
4402        assert!(dirty.measure_self_nodes.contains(&1));
4403        assert!(!dirty.placement_self_nodes.contains(&1));
4404    }
4405
4406    #[test]
4407    fn measure_state_write_dirties_layout_before_next_build() {
4408        let _runtime_session = crate::RuntimeSession::new();
4409        let _runtime_scope = _runtime_session.enter();
4410        reset_layout_dirty_tracking();
4411        begin_recompose_slot_epoch();
4412        let root_node = TesseraRuntime::with_mut(|runtime| {
4413            runtime
4414                .component_tree
4415                .add_node(ComponentNode::new("root", NodeRole::Layout))
4416        });
4417
4418        let state = {
4419            let _phase_guard = push_phase(RuntimePhase::Build);
4420            let _node_guard = push_current_node(root_node, 9201, "root");
4421            remember(|| 0i32)
4422        };
4423        let instance_key = {
4424            let _phase_guard = push_phase(RuntimePhase::Measure);
4425            let _node_guard = push_current_node_with_instance_logic_id(root_node, 9201, "root");
4426            let _instance_guard = push_current_component_instance_key(77);
4427            state.with(|value| *value)
4428        };
4429        assert_eq!(instance_key, 0);
4430
4431        state.set(1);
4432        finalize_frame_layout_dirty_tracking();
4433        let dirty = take_layout_dirty_nodes();
4434        assert!(dirty.measure_self_nodes.contains(&77));
4435    }
4436
4437    #[test]
4438    fn layout_dirty_tracking_marks_measure_when_layout_modifier_changes() {
4439        let _runtime_session = crate::RuntimeSession::new();
4440        let _runtime_scope = _runtime_session.enter();
4441        reset_layout_dirty_tracking();
4442
4443        let policy = DirtySplitPolicy {
4444            measure_key: 0,
4445            placement_key: 0,
4446        };
4447        let first = dirty_test_node(
4448            policy.clone(),
4449            Modifier::new().push_layout(DirtyMeasureModifierNode { width: 10 }),
4450        );
4451        begin_frame_layout_dirty_tracking();
4452        record_layout_node_dirty(None, &first);
4453        finalize_frame_layout_dirty_tracking();
4454        let _ = take_layout_dirty_nodes();
4455
4456        let second = dirty_test_node(
4457            policy,
4458            Modifier::new().push_layout(DirtyMeasureModifierNode { width: 20 }),
4459        );
4460        begin_frame_layout_dirty_tracking();
4461        record_layout_node_dirty(Some(&first), &second);
4462        finalize_frame_layout_dirty_tracking();
4463        let dirty = take_layout_dirty_nodes();
4464        assert!(dirty.measure_self_nodes.contains(&1));
4465        assert!(!dirty.placement_self_nodes.contains(&1));
4466    }
4467
4468    #[test]
4469    fn with_replay_scope_restores_group_path_and_override() {
4470        let _runtime_session = crate::RuntimeSession::new();
4471        let _runtime_scope = _runtime_session.enter();
4472        let _outer = PathGroupGuard::new(1);
4473        let _middle = PathGroupGuard::new(2);
4474        let _inner = PathGroupGuard::new(3);
4475        let original = current_group_path();
4476        let replay = GroupPath::default().extend(7).extend(8);
4477        with_execution_context_mut(|context| {
4478            context.instance_key_override = Some(5);
4479        });
4480        with_execution_context_mut(|context| {
4481            context.next_node_instance_logic_id_override = Some(9);
4482        });
4483
4484        with_replay_scope(42, replay, Some(11), ContextMap::default(), || {
4485            assert_eq!(current_group_path(), replay);
4486            assert_eq!(current_instance_key_override(), Some(11));
4487            assert_eq!(take_next_node_instance_logic_id_override(), Some(42));
4488            assert_eq!(take_next_node_instance_logic_id_override(), None);
4489        });
4490
4491        assert_eq!(current_group_path(), original);
4492        assert_eq!(current_instance_key_override(), Some(5));
4493        let restored_override =
4494            with_execution_context(|context| context.next_node_instance_logic_id_override);
4495        assert_eq!(restored_override, Some(9));
4496    }
4497
4498    #[test]
4499    fn with_replay_scope_restores_on_panic() {
4500        let _runtime_session = crate::RuntimeSession::new();
4501        let _runtime_scope = _runtime_session.enter();
4502        let _outer = PathGroupGuard::new(5);
4503        let original = current_group_path();
4504        let replay = GroupPath::default().extend(10);
4505        with_execution_context_mut(|context| {
4506            context.instance_key_override = Some(13);
4507        });
4508        with_execution_context_mut(|context| {
4509            context.next_node_instance_logic_id_override = None;
4510        });
4511
4512        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4513            with_replay_scope(77, replay, Some(17), ContextMap::default(), || {
4514                assert_eq!(current_group_path(), replay);
4515                assert_eq!(current_instance_key_override(), Some(17));
4516                panic!("expected panic");
4517            });
4518        }));
4519        assert!(result.is_err());
4520
4521        assert_eq!(current_group_path(), original);
4522        assert_eq!(current_instance_key_override(), Some(13));
4523        let restored_override =
4524            with_execution_context(|context| context.next_node_instance_logic_id_override);
4525        assert_eq!(restored_override, None);
4526    }
4527
4528    #[test]
4529    fn group_local_remember_does_not_shift_following_slots() {
4530        let _runtime_session = crate::RuntimeSession::new();
4531        let _runtime_scope = _runtime_session.enter();
4532        reset_slots();
4533
4534        begin_recompose_slot_epoch();
4535        with_test_component_scope(1001, || {
4536            let stable_state = remember(|| 1usize);
4537            stable_state.set(41);
4538        });
4539
4540        begin_recompose_slot_epoch();
4541        with_test_component_scope(1001, || {
4542            {
4543                let _group_guard = GroupGuard::new(7);
4544                let _branch_state = remember(|| 10usize);
4545            }
4546            let stable_state = remember(|| 1usize);
4547            assert_eq!(stable_state.get(), 41);
4548        });
4549    }
4550
4551    #[test]
4552    fn conditional_frame_receiver_does_not_shift_following_remember_slots() {
4553        let _runtime_session = crate::RuntimeSession::new();
4554        let _runtime_scope = _runtime_session.enter();
4555        reset_slots();
4556        reset_frame_clock();
4557        begin_frame_clock(Instant::now());
4558
4559        begin_recompose_slot_epoch();
4560        with_test_component_scope(1002, || {
4561            let stable_state = remember(|| 1usize);
4562            stable_state.set(99);
4563        });
4564
4565        begin_recompose_slot_epoch();
4566        with_test_component_scope(1002, || {
4567            {
4568                let _group_guard = GroupGuard::new(9);
4569                receive_frame_nanos(|_| FrameNanosControl::Stop);
4570            }
4571            let stable_state = remember(|| 1usize);
4572            assert_eq!(stable_state.get(), 99);
4573        });
4574    }
4575
4576    #[test]
4577    fn tessera_uses_none_for_omitted_option_props() {
4578        let _runtime_session = crate::RuntimeSession::new();
4579        let _runtime_scope = _runtime_session.enter();
4580        let observed = crate::testing::TestProbe::new(99usize);
4581
4582        with_test_component_scope(11014, || {
4583            let observed = observed.clone();
4584            optional_value_component().on_value(move |value: Option<usize>| {
4585                observed.set(value.map_or(0, |next| next.saturating_add(1)));
4586            });
4587        });
4588
4589        assert_eq!(observed.get(), 0);
4590    }
4591
4592    #[test]
4593    fn tessera_accepts_non_default_required_props_when_provided() {
4594        let _runtime_session = crate::RuntimeSession::new();
4595        let _runtime_scope = _runtime_session.enter();
4596        let observed = crate::testing::TestProbe::new(0usize);
4597
4598        with_test_component_scope(11016, || {
4599            let observed = observed.clone();
4600            required_value_component()
4601                .value(13)
4602                .on_value(move |value: usize| {
4603                    observed.set(value);
4604                });
4605        });
4606
4607        assert_eq!(observed.get(), 13);
4608    }
4609
4610    #[test]
4611    fn render_slot_update_invalidates_reader_instance() {
4612        let _runtime_session = crate::RuntimeSession::new();
4613        let _runtime_scope = _runtime_session.enter();
4614        reset_slots();
4615        reset_component_replay_tracking();
4616        reset_render_slot_read_dependencies();
4617        reset_build_invalidations();
4618
4619        let wakeups = Arc::new(AtomicUsize::new(0));
4620        let count = Arc::clone(&wakeups);
4621        install_redraw_waker(Arc::new(move || {
4622            count.fetch_add(1, Ordering::Relaxed);
4623        }));
4624
4625        let reader_boundary_key = crate::testing::TestProbe::new(0u64);
4626
4627        begin_recompose_slot_epoch();
4628        let first = with_test_component_scope(11002, || {
4629            let reader_boundary_key = reader_boundary_key.clone();
4630            RenderSlot::new(move || {
4631                let instance_key = current_replay_boundary_instance_key_from_scope()
4632                    .expect("slot render must run inside replay boundary");
4633                reader_boundary_key.set(instance_key);
4634            })
4635        });
4636
4637        with_test_component_scope(11003, || {
4638            first.render();
4639        });
4640        let reader_instance_key = reader_boundary_key.get();
4641        assert!(reader_instance_key != 0);
4642        seed_previous_replay_snapshot(reader_instance_key);
4643
4644        assert!(!has_pending_build_invalidations());
4645
4646        begin_recompose_slot_epoch();
4647        let second = with_test_component_scope(11002, || RenderSlot::new(|| {}));
4648
4649        assert!(first == second);
4650        assert!(has_pending_build_invalidations());
4651
4652        let invalidations = take_build_invalidations();
4653        let mut expected = HashSet::default();
4654        expected.insert(reader_instance_key);
4655        assert_eq!(invalidations.dirty_instance_keys, expected);
4656        assert_eq!(wakeups.load(Ordering::Relaxed), 0);
4657        defer_build_invalidations(&invalidations.dirty_instance_keys);
4658        assert_eq!(wakeups.load(Ordering::Relaxed), 1);
4659        clear_redraw_waker();
4660    }
4661
4662    #[test]
4663    fn render_slot_update_skips_reader_already_dirty_in_current_build() {
4664        let _runtime_session = crate::RuntimeSession::new();
4665        let _runtime_scope = _runtime_session.enter();
4666        reset_slots();
4667        reset_component_replay_tracking();
4668        reset_render_slot_read_dependencies();
4669        reset_build_invalidations();
4670        reset_execution_context();
4671
4672        let reader_boundary_key = crate::testing::TestProbe::new(0u64);
4673
4674        begin_recompose_slot_epoch();
4675        let first = with_test_component_scope(12002, || {
4676            let reader_boundary_key = reader_boundary_key.clone();
4677            RenderSlot::new(move || {
4678                let instance_key = current_replay_boundary_instance_key_from_scope()
4679                    .expect("slot render must run inside replay boundary");
4680                reader_boundary_key.set(instance_key);
4681            })
4682        });
4683
4684        with_test_component_scope(12003, || {
4685            first.render();
4686        });
4687        let reader_instance_key = reader_boundary_key.get();
4688        assert!(reader_instance_key != 0);
4689        seed_previous_replay_snapshot(reader_instance_key);
4690
4691        assert!(!has_pending_build_invalidations());
4692
4693        begin_recompose_slot_epoch();
4694        let mut dirty = HashSet::default();
4695        dirty.insert(reader_instance_key);
4696        with_build_dirty_instance_keys(Arc::new(dirty), || {
4697            let mut arena = crate::Arena::<()>::new();
4698            let node_id = arena.new_node(());
4699            let _phase_guard = push_phase(RuntimePhase::Build);
4700            let _node_guard = push_current_node(node_id, 12002, "test_component");
4701            let _instance_guard = push_current_component_instance_key(current_instance_key());
4702            let second = RenderSlot::new(|| {});
4703            assert!(first == second);
4704        });
4705
4706        assert!(!has_pending_build_invalidations());
4707    }
4708
4709    #[test]
4710    fn group_local_child_identity_does_not_shift_following_siblings() {
4711        let _runtime_session = crate::RuntimeSession::new();
4712        let _runtime_scope = _runtime_session.enter();
4713        fn stable_child_instance_logic_id(with_group_child: bool) -> u64 {
4714            let mut arena = crate::Arena::<()>::new();
4715            let root_node = arena.new_node(());
4716            let stable_child_node = arena.new_node(());
4717            let group_child_node = arena.new_node(());
4718
4719            let _phase_guard = push_phase(RuntimePhase::Build);
4720            let _root_guard = push_current_node(root_node, 2001, "root_component");
4721            let _root_instance_guard = push_current_component_instance_key(current_instance_key());
4722
4723            if with_group_child {
4724                let _group_guard = GroupGuard::new(5);
4725                let _group_child_guard =
4726                    push_current_node(group_child_node, 2002, "group_child_component");
4727                let _group_child_instance_guard =
4728                    push_current_component_instance_key(current_instance_key());
4729                let _ = current_instance_logic_id();
4730            }
4731
4732            let _stable_child_guard =
4733                push_current_node(stable_child_node, 2003, "stable_child_component");
4734            current_instance_logic_id()
4735        }
4736
4737        assert_eq!(
4738            stable_child_instance_logic_id(false),
4739            stable_child_instance_logic_id(true)
4740        );
4741    }
4742
4743    #[test]
4744    fn child_components_in_different_groups_get_distinct_instance_logic_ids() {
4745        let _runtime_session = crate::RuntimeSession::new();
4746        let _runtime_scope = _runtime_session.enter();
4747        let mut arena = crate::Arena::<()>::new();
4748        let root_node = arena.new_node(());
4749        let first_child_node = arena.new_node(());
4750        let second_child_node = arena.new_node(());
4751
4752        let _phase_guard = push_phase(RuntimePhase::Build);
4753        let _root_guard = push_current_node(root_node, 3001, "root_component");
4754        let _root_instance_guard = push_current_component_instance_key(current_instance_key());
4755
4756        let first_id = {
4757            let _group_guard = GroupGuard::new(11);
4758            let _child_guard = push_current_node(first_child_node, 3002, "grouped_child");
4759            current_instance_logic_id()
4760        };
4761
4762        let second_id = {
4763            let _group_guard = GroupGuard::new(12);
4764            let _child_guard = push_current_node(second_child_node, 3002, "grouped_child");
4765            current_instance_logic_id()
4766        };
4767
4768        assert_ne!(first_id, second_id);
4769    }
4770
4771    #[test]
4772    fn child_components_in_repeated_path_groups_keep_distinct_instance_logic_ids() {
4773        let _runtime_session = crate::RuntimeSession::new();
4774        let _runtime_scope = _runtime_session.enter();
4775        let mut arena = crate::Arena::<()>::new();
4776        let root_node = arena.new_node(());
4777        let first_child_node = arena.new_node(());
4778        let second_child_node = arena.new_node(());
4779
4780        let _phase_guard = push_phase(RuntimePhase::Build);
4781        let _root_guard = push_current_node(root_node, 4001, "root_component");
4782        let _root_instance_guard = push_current_component_instance_key(current_instance_key());
4783
4784        let first_id = {
4785            let _group_guard = PathGroupGuard::new(21);
4786            let _child_guard = push_current_node(first_child_node, 4002, "loop_child");
4787            current_instance_logic_id()
4788        };
4789
4790        let second_id = {
4791            let _group_guard = PathGroupGuard::new(21);
4792            let _child_guard = push_current_node(second_child_node, 4002, "loop_child");
4793            current_instance_logic_id()
4794        };
4795
4796        assert_ne!(first_id, second_id);
4797    }
4798
4799    #[test]
4800    fn conditional_remember_keeps_later_state_stable() {
4801        let _runtime_session = crate::RuntimeSession::new();
4802        let _runtime_scope = _runtime_session.enter();
4803        begin_recompose_slot_epoch();
4804        let root_node = TesseraRuntime::with_mut(|runtime| {
4805            runtime
4806                .component_tree
4807                .add_node(ComponentNode::new("root", NodeRole::Composition))
4808        });
4809        let _phase_guard = push_phase(RuntimePhase::Build);
4810
4811        let stable = {
4812            let _node_guard = push_current_node(root_node, 9101, "root");
4813            remember(|| 11u32)
4814        };
4815        stable.set(23);
4816
4817        begin_recompose_slot_epoch();
4818        {
4819            let _node_guard = push_current_node(root_node, 9101, "root");
4820            let _ = remember(|| current_frame_nanos());
4821            let shifted = remember(|| 99u32);
4822            assert_eq!(shifted.get(), 99);
4823        }
4824        assert_eq!(stable.get(), 23);
4825
4826        begin_recompose_slot_epoch();
4827        {
4828            let _node_guard = push_current_node(root_node, 9101, "root");
4829            let restored = remember(|| 77u32);
4830            assert_eq!(restored.get(), 23);
4831        }
4832        assert_eq!(stable.get(), 23);
4833    }
4834
4835    #[test]
4836    fn drop_slots_for_instance_logic_ids_keeps_retained_entries() {
4837        let _runtime_session = crate::RuntimeSession::new();
4838        let _runtime_scope = _runtime_session.enter();
4839        let mut table = SlotTable::default();
4840        let keep_key = SlotKey {
4841            owned_owner: None,
4842            instance_logic_id: 7,
4843            slot_hash: 11,
4844            type_id: TypeId::of::<i32>(),
4845        };
4846        let drop_key = SlotKey {
4847            owned_owner: None,
4848            instance_logic_id: 7,
4849            slot_hash: 12,
4850            type_id: TypeId::of::<i32>(),
4851        };
4852
4853        let keep_slot = table.entries.insert(SlotEntry {
4854            key: keep_key,
4855            generation: 1,
4856            value: Some(Arc::new(10_i32)),
4857            last_alive_epoch: 0,
4858            retained: true,
4859        });
4860        let drop_slot = table.entries.insert(SlotEntry {
4861            key: drop_key,
4862            generation: 1,
4863            value: Some(Arc::new(20_i32)),
4864            last_alive_epoch: 0,
4865            retained: false,
4866        });
4867        table.index_slot(keep_slot);
4868        table.index_slot(drop_slot);
4869        with_slot_table_mut(|slot_table| *slot_table = table);
4870
4871        let mut stale = HashSet::default();
4872        stale.insert(7_u64);
4873        drop_slots_for_instance_logic_ids(&stale);
4874
4875        with_slot_table(|table| {
4876            assert!(table.entries.get(keep_slot).is_some());
4877            assert_eq!(table.find_slot(&keep_key), Some(keep_slot));
4878            assert!(table.entries.get(drop_slot).is_none());
4879            assert_eq!(table.find_slot(&drop_key), None);
4880        });
4881    }
4882}