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::RefCell,
6    hash::{Hash, Hasher},
7    marker::PhantomData,
8    sync::Arc,
9    time::Duration,
10};
11
12use parking_lot::RwLock;
13use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
14use slotmap::{SlotMap, new_key_type};
15use smallvec::SmallVec;
16
17use crate::{
18    NodeId,
19    accessibility::{AccessibilityActionHandler, AccessibilityNode},
20    component_tree::ComponentTree,
21    execution_context::{OrderFrame, with_execution_context, with_execution_context_mut},
22    focus::{
23        FocusDirection, FocusGroupNode, FocusHandleId, FocusNode, FocusProperties,
24        FocusRegistration, FocusRegistrationKind, FocusRequester, FocusRequesterId,
25        FocusRevealRequest, FocusScopeNode, FocusState, FocusTraversalPolicy,
26    },
27    layout::{LayoutPolicyDyn, RenderPolicyDyn},
28    modifier::Modifier,
29    prop::{CallbackWith, ComponentReplayData, ErasedComponentRunner, Prop},
30    time::Instant,
31};
32
33#[derive(Clone, Copy)]
34enum OrderCounterKind {
35    Remember,
36    Functor,
37    Context,
38    FrameReceiver,
39}
40
41fn push_order_frame() {
42    with_execution_context_mut(|context| {
43        context.order_frame_stack.push(OrderFrame::default());
44    });
45}
46
47fn pop_order_frame(underflow_message: &str) {
48    with_execution_context_mut(|context| {
49        let popped = context.order_frame_stack.pop();
50        debug_assert!(popped.is_some(), "{underflow_message}");
51    });
52}
53
54fn next_order_counter(kind: OrderCounterKind, empty_message: &str) -> u64 {
55    with_execution_context_mut(|context| {
56        debug_assert!(!context.order_frame_stack.is_empty(), "{empty_message}");
57        let frame = context.order_frame_stack.last_mut().expect(empty_message);
58        match kind {
59            OrderCounterKind::Remember => {
60                let counter = frame.remember;
61                frame.remember = frame.remember.wrapping_add(1);
62                counter
63            }
64            OrderCounterKind::Functor => {
65                let counter = frame.functor;
66                frame.functor = frame.functor.wrapping_add(1);
67                counter
68            }
69            OrderCounterKind::Context => {
70                let counter = frame.context;
71                frame.context = frame.context.wrapping_add(1);
72                counter
73            }
74            OrderCounterKind::FrameReceiver => {
75                let counter = frame.frame_receiver;
76                frame.frame_receiver = frame.frame_receiver.wrapping_add(1);
77                counter
78            }
79        }
80    })
81}
82
83fn next_child_instance_call_index() -> u64 {
84    with_execution_context_mut(|context| {
85        let Some(frame) = context.order_frame_stack.last_mut() else {
86            return 0;
87        };
88        let index = frame.instance;
89        frame.instance = frame.instance.wrapping_add(1);
90        index
91    })
92}
93
94pub(crate) fn compute_context_slot_key() -> (u64, u64) {
95    let instance_logic_id = current_instance_logic_id();
96    let group_path_hash = current_group_path_hash();
97
98    let call_counter = next_order_counter(
99        OrderCounterKind::Context,
100        "ORDER_FRAME_STACK is empty; provide_context must be called inside a component",
101    );
102
103    let slot_hash = hash_components(&[&group_path_hash, &call_counter]);
104    (instance_logic_id, slot_hash)
105}
106
107#[derive(Hash, Eq, PartialEq, Clone, Copy)]
108struct SlotKey {
109    instance_logic_id: u64,
110    slot_hash: u64,
111    type_id: TypeId,
112}
113
114impl Default for SlotKey {
115    fn default() -> Self {
116        Self {
117            instance_logic_id: 0,
118            slot_hash: 0,
119            type_id: TypeId::of::<()>(),
120        }
121    }
122}
123
124new_key_type! {
125    struct SlotHandle;
126}
127
128#[derive(Default)]
129struct SlotEntry {
130    key: SlotKey,
131    generation: u64,
132    value: Option<Arc<dyn Any + Send + Sync>>,
133    last_alive_epoch: u64,
134    retained: bool,
135}
136
137#[derive(Default)]
138struct InstanceSlotCursor {
139    previous_order: SmallVec<[SlotHandle; 4]>,
140    current_order: SmallVec<[SlotHandle; 4]>,
141    cursor: usize,
142    epoch: u64,
143}
144
145impl InstanceSlotCursor {
146    fn begin_epoch(&mut self, epoch: u64) {
147        if self.epoch == epoch {
148            return;
149        }
150        self.previous_order = std::mem::take(&mut self.current_order);
151        self.cursor = 0;
152        self.epoch = epoch;
153    }
154
155    fn fast_candidate(&self) -> Option<SlotHandle> {
156        self.previous_order.get(self.cursor).copied()
157    }
158
159    fn record_fast_match(&mut self, slot: SlotHandle) {
160        self.cursor = self.cursor.saturating_add(1);
161        self.current_order.push(slot);
162    }
163
164    fn record_slow_match(&mut self, slot: SlotHandle) {
165        if self.cursor < self.previous_order.len()
166            && let Some(offset) = self.previous_order[self.cursor..]
167                .iter()
168                .position(|candidate| *candidate == slot)
169        {
170            self.cursor += offset + 1;
171        }
172        self.current_order.push(slot);
173    }
174}
175
176#[derive(Default)]
177struct SlotTable {
178    entries: SlotMap<SlotHandle, SlotEntry>,
179    key_to_slot: HashMap<SlotKey, SlotHandle>,
180    cursors_by_instance_logic_id: HashMap<u64, InstanceSlotCursor>,
181    epoch: u64,
182}
183
184impl SlotTable {
185    fn begin_epoch(&mut self) {
186        self.epoch = self.epoch.wrapping_add(1);
187    }
188
189    fn reset(&mut self) {
190        self.entries.clear();
191        self.key_to_slot.clear();
192        self.cursors_by_instance_logic_id.clear();
193        self.epoch = 0;
194    }
195
196    fn try_fast_slot_lookup(&mut self, key: SlotKey) -> Option<SlotHandle> {
197        let epoch = self.epoch;
198        let candidate = {
199            let cursor = self
200                .cursors_by_instance_logic_id
201                .entry(key.instance_logic_id)
202                .or_default();
203            cursor.begin_epoch(epoch);
204            cursor.fast_candidate()
205        }?;
206
207        let is_match = self
208            .entries
209            .get(candidate)
210            .is_some_and(|entry| entry.key == key);
211
212        if !is_match {
213            return None;
214        }
215
216        let cursor = self
217            .cursors_by_instance_logic_id
218            .get_mut(&key.instance_logic_id)
219            .expect("cursor entry should exist");
220        cursor.record_fast_match(candidate);
221        Some(candidate)
222    }
223
224    fn record_slot_usage_slow(&mut self, instance_logic_id: u64, slot: SlotHandle) {
225        let epoch = self.epoch;
226        let cursor = self
227            .cursors_by_instance_logic_id
228            .entry(instance_logic_id)
229            .or_default();
230        cursor.begin_epoch(epoch);
231        cursor.record_slow_match(slot);
232    }
233}
234
235#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
236struct PersistentFocusHandleKey {
237    instance_key: u64,
238    slot_hash: u64,
239}
240
241#[derive(Clone, Copy)]
242struct PersistentFocusHandleEntry<T> {
243    value: T,
244    missing_frames: u8,
245}
246
247impl<T: Copy> PersistentFocusHandleEntry<T> {
248    fn new(value: T) -> Self {
249        Self {
250            value,
251            missing_frames: 0,
252        }
253    }
254
255    fn mark_live(&mut self) -> T {
256        self.missing_frames = 0;
257        self.value
258    }
259
260    fn retain_for_frame(&mut self) -> bool {
261        if self.missing_frames == 0 {
262            self.missing_frames = 1;
263            true
264        } else {
265            false
266        }
267    }
268}
269
270#[derive(Default)]
271struct PersistentFocusHandleStore {
272    targets: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusNode>>,
273    scopes: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusScopeNode>>,
274    groups: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusGroupNode>>,
275    requesters: HashMap<PersistentFocusHandleKey, PersistentFocusHandleEntry<FocusRequester>>,
276}
277
278#[derive(Default)]
279pub(crate) struct RemovedPersistentFocusHandles {
280    pub handle_ids: HashSet<FocusHandleId>,
281    pub requester_ids: HashSet<FocusRequesterId>,
282}
283
284impl PersistentFocusHandleStore {
285    fn retain_instance_keys(
286        &mut self,
287        live_instance_keys: &HashSet<u64>,
288    ) -> RemovedPersistentFocusHandles {
289        let mut removed = RemovedPersistentFocusHandles::default();
290        self.targets.retain(|key, handle| {
291            if !live_instance_keys.contains(&key.instance_key) {
292                if handle.retain_for_frame() {
293                    true
294                } else {
295                    removed.handle_ids.insert(handle.value.handle_id());
296                    false
297                }
298            } else {
299                handle.mark_live();
300                true
301            }
302        });
303        self.scopes.retain(|key, scope| {
304            if !live_instance_keys.contains(&key.instance_key) {
305                if scope.retain_for_frame() {
306                    true
307                } else {
308                    removed.handle_ids.insert(scope.value.handle_id());
309                    false
310                }
311            } else {
312                scope.mark_live();
313                true
314            }
315        });
316        self.groups.retain(|key, group| {
317            if !live_instance_keys.contains(&key.instance_key) {
318                if group.retain_for_frame() {
319                    true
320                } else {
321                    removed.handle_ids.insert(group.value.handle_id());
322                    false
323                }
324            } else {
325                group.mark_live();
326                true
327            }
328        });
329        self.requesters.retain(|key, requester| {
330            if !live_instance_keys.contains(&key.instance_key) {
331                if requester.retain_for_frame() {
332                    true
333                } else {
334                    removed.requester_ids.insert(requester.value.requester_id());
335                    false
336                }
337            } else {
338                requester.mark_live();
339                true
340            }
341        });
342        removed
343    }
344
345    fn contains_handle(&self, handle_id: FocusHandleId) -> bool {
346        self.targets
347            .values()
348            .any(|entry| entry.value.handle_id() == handle_id)
349            || self
350                .scopes
351                .values()
352                .any(|entry| entry.value.handle_id() == handle_id)
353            || self
354                .groups
355                .values()
356                .any(|entry| entry.value.handle_id() == handle_id)
357    }
358
359    fn clear(&mut self) {
360        self.targets.clear();
361        self.scopes.clear();
362        self.groups.clear();
363        self.requesters.clear();
364    }
365}
366
367fn with_slot_table<R>(f: impl FnOnce(&SlotTable) -> R) -> R {
368    RUNTIME_GLOBALS.with(|globals| f(&globals.slot_table.borrow()))
369}
370
371fn with_slot_table_mut<R>(f: impl FnOnce(&mut SlotTable) -> R) -> R {
372    RUNTIME_GLOBALS.with(|globals| f(&mut globals.slot_table.borrow_mut()))
373}
374
375#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
376pub(crate) struct FunctorHandle {
377    slot: SlotHandle,
378    generation: u64,
379}
380
381impl FunctorHandle {
382    fn new(slot: SlotHandle, generation: u64) -> Self {
383        Self { slot, generation }
384    }
385}
386
387struct CallbackCell {
388    current: RwLock<Arc<dyn Fn() + Send + Sync>>,
389}
390
391impl CallbackCell {
392    fn new(current: Arc<dyn Fn() + Send + Sync>) -> Self {
393        Self {
394            current: RwLock::new(current),
395        }
396    }
397
398    fn update(&self, next: Arc<dyn Fn() + Send + Sync>) {
399        *self.current.write() = next;
400    }
401
402    fn shared(&self) -> Arc<dyn Fn() + Send + Sync> {
403        Arc::clone(&self.current.read())
404    }
405}
406
407struct CallbackWithCell<T, R> {
408    current: RwLock<Arc<dyn Fn(T) -> R + Send + Sync>>,
409}
410
411impl<T, R> CallbackWithCell<T, R> {
412    fn new(current: Arc<dyn Fn(T) -> R + Send + Sync>) -> Self {
413        Self {
414            current: RwLock::new(current),
415        }
416    }
417
418    fn update(&self, next: Arc<dyn Fn(T) -> R + Send + Sync>) {
419        *self.current.write() = next;
420    }
421
422    fn shared(&self) -> Arc<dyn Fn(T) -> R + Send + Sync> {
423        Arc::clone(&self.current.read())
424    }
425}
426
427struct RenderSlotCell {
428    current: RwLock<Arc<dyn Fn() + Send + Sync>>,
429}
430
431impl RenderSlotCell {
432    fn new(current: Arc<dyn Fn() + Send + Sync>) -> Self {
433        Self {
434            current: RwLock::new(current),
435        }
436    }
437
438    fn update(&self, next: Arc<dyn Fn() + Send + Sync>) {
439        *self.current.write() = next;
440    }
441
442    fn shared(&self) -> Arc<dyn Fn() + Send + Sync> {
443        Arc::clone(&self.current.read())
444    }
445}
446
447struct RenderSlotWithCell<T> {
448    current: RwLock<Arc<dyn Fn(T) + Send + Sync>>,
449}
450
451impl<T> RenderSlotWithCell<T> {
452    fn new(current: Arc<dyn Fn(T) + Send + Sync>) -> Self {
453        Self {
454            current: RwLock::new(current),
455        }
456    }
457
458    fn update(&self, next: Arc<dyn Fn(T) + Send + Sync>) {
459        *self.current.write() = next;
460    }
461
462    fn shared(&self) -> Arc<dyn Fn(T) + Send + Sync> {
463        Arc::clone(&self.current.read())
464    }
465}
466
467#[derive(Default)]
468struct LayoutDirtyTracker {
469    previous_layout_inputs_by_node: HashMap<u64, LayoutInputSnapshot>,
470    frame_layout_inputs_by_node: HashMap<u64, LayoutInputSnapshot>,
471    pending_measure_self_dirty_nodes: HashSet<u64>,
472    ready_measure_self_dirty_nodes: HashSet<u64>,
473    pending_placement_self_dirty_nodes: HashSet<u64>,
474    ready_placement_self_dirty_nodes: HashSet<u64>,
475    previous_children_by_node: HashMap<u64, Vec<u64>>,
476}
477
478struct LayoutInputSnapshot {
479    policy: Box<dyn LayoutPolicyDyn>,
480    modifier: Modifier,
481}
482
483#[derive(Default)]
484pub(crate) struct LayoutDirtyNodes {
485    pub measure_self_nodes: HashSet<u64>,
486    pub placement_self_nodes: HashSet<u64>,
487}
488
489#[derive(Default)]
490pub(crate) struct StructureReconcileResult {
491    pub changed_nodes: HashSet<u64>,
492    pub removed_nodes: HashSet<u64>,
493}
494
495/// Persisted replay snapshot for one component instance.
496#[allow(dead_code)]
497#[derive(Clone)]
498pub(crate) struct ReplayNodeSnapshot {
499    pub instance_key: u64,
500    pub parent_instance_key: Option<u64>,
501    pub instance_logic_id: u64,
502    pub group_path: Vec<u64>,
503    pub instance_key_override: Option<u64>,
504    pub fn_name: String,
505    pub replay: ComponentReplayData,
506}
507
508#[derive(Default)]
509struct ComponentReplayTracker {
510    previous_nodes: HashMap<u64, ReplayNodeSnapshot>,
511    current_nodes: HashMap<u64, ReplayNodeSnapshot>,
512}
513
514fn with_component_replay_tracker<R>(f: impl FnOnce(&ComponentReplayTracker) -> R) -> R {
515    RUNTIME_GLOBALS.with(|globals| f(&globals.component_replay_tracker.borrow()))
516}
517
518fn with_component_replay_tracker_mut<R>(f: impl FnOnce(&mut ComponentReplayTracker) -> R) -> R {
519    RUNTIME_GLOBALS.with(|globals| f(&mut globals.component_replay_tracker.borrow_mut()))
520}
521
522pub(crate) fn begin_frame_component_replay_tracking() {
523    with_component_replay_tracker_mut(|tracker| tracker.current_nodes.clear());
524}
525
526pub(crate) fn finalize_frame_component_replay_tracking() {
527    with_component_replay_tracker_mut(|tracker| {
528        tracker.previous_nodes = std::mem::take(&mut tracker.current_nodes);
529    });
530}
531
532pub(crate) fn finalize_frame_component_replay_tracking_partial() {
533    with_component_replay_tracker_mut(|tracker| {
534        let current = std::mem::take(&mut tracker.current_nodes);
535        tracker.previous_nodes.extend(current);
536    });
537}
538
539pub(crate) fn reset_component_replay_tracking() {
540    with_component_replay_tracker_mut(|tracker| {
541        *tracker = ComponentReplayTracker::default();
542    });
543}
544
545pub(crate) fn previous_component_replay_nodes() -> HashMap<u64, ReplayNodeSnapshot> {
546    with_component_replay_tracker(|tracker| tracker.previous_nodes.clone())
547}
548
549pub(crate) fn remove_previous_component_replay_nodes(instance_keys: &HashSet<u64>) {
550    if instance_keys.is_empty() {
551        return;
552    }
553    with_component_replay_tracker_mut(|tracker| {
554        tracker
555            .previous_nodes
556            .retain(|instance_key, _| !instance_keys.contains(instance_key));
557        tracker
558            .current_nodes
559            .retain(|instance_key, _| !instance_keys.contains(instance_key));
560    });
561}
562
563#[derive(Default)]
564struct BuildInvalidationTracker {
565    dirty_instance_keys: HashSet<u64>,
566}
567
568#[derive(Default)]
569pub(crate) struct BuildInvalidationSet {
570    pub dirty_instance_keys: HashSet<u64>,
571}
572
573#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
574struct StateReadDependencyKey {
575    // Keep generation in the dependency key to avoid ABA when a slot is recycled
576    // and later reused for another state value.
577    slot: SlotHandle,
578    generation: u64,
579}
580
581#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
582struct FocusReadDependencyKey {
583    kind: FocusReadDependencyKind,
584}
585
586#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
587enum FocusReadDependencyKind {
588    Handle(FocusHandleId),
589    Requester(FocusRequesterId),
590}
591
592#[derive(Default)]
593struct StateReadDependencyTracker {
594    readers_by_state: HashMap<StateReadDependencyKey, HashSet<u64>>,
595    states_by_reader: HashMap<u64, HashSet<StateReadDependencyKey>>,
596}
597
598#[derive(Default)]
599struct FocusReadDependencyTracker {
600    readers_by_focus: HashMap<FocusReadDependencyKey, HashSet<u64>>,
601    focus_by_reader: HashMap<u64, HashSet<FocusReadDependencyKey>>,
602}
603
604#[derive(Default)]
605struct RenderSlotReadDependencyTracker {
606    readers_by_slot: HashMap<FunctorHandle, HashSet<u64>>,
607    slots_by_reader: HashMap<u64, HashSet<FunctorHandle>>,
608}
609
610type RedrawWaker = Arc<dyn Fn() + Send + Sync + 'static>;
611
612fn with_build_invalidation_tracker<R>(f: impl FnOnce(&BuildInvalidationTracker) -> R) -> R {
613    RUNTIME_GLOBALS.with(|globals| f(&globals.build_invalidation_tracker.borrow()))
614}
615
616fn with_build_invalidation_tracker_mut<R>(f: impl FnOnce(&mut BuildInvalidationTracker) -> R) -> R {
617    RUNTIME_GLOBALS.with(|globals| f(&mut globals.build_invalidation_tracker.borrow_mut()))
618}
619
620fn with_state_read_dependency_tracker<R>(f: impl FnOnce(&StateReadDependencyTracker) -> R) -> R {
621    RUNTIME_GLOBALS.with(|globals| f(&globals.state_read_dependency_tracker.borrow()))
622}
623
624fn with_state_read_dependency_tracker_mut<R>(
625    f: impl FnOnce(&mut StateReadDependencyTracker) -> R,
626) -> R {
627    RUNTIME_GLOBALS.with(|globals| f(&mut globals.state_read_dependency_tracker.borrow_mut()))
628}
629
630fn with_focus_read_dependency_tracker<R>(f: impl FnOnce(&FocusReadDependencyTracker) -> R) -> R {
631    RUNTIME_GLOBALS.with(|globals| f(&globals.focus_read_dependency_tracker.borrow()))
632}
633
634fn with_focus_read_dependency_tracker_mut<R>(
635    f: impl FnOnce(&mut FocusReadDependencyTracker) -> R,
636) -> R {
637    RUNTIME_GLOBALS.with(|globals| f(&mut globals.focus_read_dependency_tracker.borrow_mut()))
638}
639
640fn with_render_slot_read_dependency_tracker<R>(
641    f: impl FnOnce(&RenderSlotReadDependencyTracker) -> R,
642) -> R {
643    RUNTIME_GLOBALS.with(|globals| f(&globals.render_slot_read_dependency_tracker.borrow()))
644}
645
646fn with_render_slot_read_dependency_tracker_mut<R>(
647    f: impl FnOnce(&mut RenderSlotReadDependencyTracker) -> R,
648) -> R {
649    RUNTIME_GLOBALS.with(|globals| f(&mut globals.render_slot_read_dependency_tracker.borrow_mut()))
650}
651
652fn with_redraw_waker<R>(f: impl FnOnce(&Option<RedrawWaker>) -> R) -> R {
653    RUNTIME_GLOBALS.with(|globals| f(&globals.redraw_waker.borrow()))
654}
655
656fn with_redraw_waker_mut<R>(f: impl FnOnce(&mut Option<RedrawWaker>) -> R) -> R {
657    RUNTIME_GLOBALS.with(|globals| f(&mut globals.redraw_waker.borrow_mut()))
658}
659
660fn schedule_runtime_redraw() {
661    let callback = with_redraw_waker(Clone::clone);
662    if let Some(callback) = callback {
663        callback();
664    }
665}
666
667pub(crate) fn install_redraw_waker(callback: RedrawWaker) {
668    with_redraw_waker_mut(|waker| *waker = Some(callback));
669}
670
671pub(crate) fn clear_redraw_waker() {
672    with_redraw_waker_mut(|waker| *waker = None);
673}
674
675pub(crate) fn current_replay_boundary_instance_key_from_scope() -> Option<u64> {
676    with_execution_context(|context| context.current_component_instance_stack.last().copied())
677}
678
679fn with_persistent_focus_handle_store<R>(f: impl FnOnce(&PersistentFocusHandleStore) -> R) -> R {
680    RUNTIME_GLOBALS.with(|globals| f(&globals.persistent_focus_handle_store.borrow()))
681}
682
683fn with_persistent_focus_handle_store_mut<R>(
684    f: impl FnOnce(&mut PersistentFocusHandleStore) -> R,
685) -> R {
686    RUNTIME_GLOBALS.with(|globals| f(&mut globals.persistent_focus_handle_store.borrow_mut()))
687}
688
689fn current_persistent_focus_handle_key<K: Hash>(slot_key: K) -> PersistentFocusHandleKey {
690    let Some(instance_key) = current_replay_boundary_instance_key_from_scope() else {
691        panic!("persistent focus handles must be requested during a component build");
692    };
693    let slot_hash = hash_components(&[&slot_key]);
694    PersistentFocusHandleKey {
695        instance_key,
696        slot_hash,
697    }
698}
699
700pub(crate) fn persistent_focus_target_for_current_instance<K: Hash>(slot_key: K) -> FocusNode {
701    let key = current_persistent_focus_handle_key(slot_key);
702    with_persistent_focus_handle_store_mut(|store| match store.targets.entry(key) {
703        std::collections::hash_map::Entry::Occupied(mut entry) => entry.get_mut().mark_live(),
704        std::collections::hash_map::Entry::Vacant(entry) => {
705            let value = FocusNode::new();
706            entry.insert(PersistentFocusHandleEntry::new(value));
707            value
708        }
709    })
710}
711
712pub(crate) fn persistent_focus_scope_for_current_instance<K: Hash>(slot_key: K) -> FocusScopeNode {
713    let key = current_persistent_focus_handle_key(slot_key);
714    with_persistent_focus_handle_store_mut(|store| match store.scopes.entry(key) {
715        std::collections::hash_map::Entry::Occupied(mut entry) => entry.get_mut().mark_live(),
716        std::collections::hash_map::Entry::Vacant(entry) => {
717            let value = FocusScopeNode::new();
718            entry.insert(PersistentFocusHandleEntry::new(value));
719            value
720        }
721    })
722}
723
724pub(crate) fn persistent_focus_group_for_current_instance<K: Hash>(slot_key: K) -> FocusGroupNode {
725    let key = current_persistent_focus_handle_key(slot_key);
726    with_persistent_focus_handle_store_mut(|store| match store.groups.entry(key) {
727        std::collections::hash_map::Entry::Occupied(mut entry) => entry.get_mut().mark_live(),
728        std::collections::hash_map::Entry::Vacant(entry) => {
729            let value = FocusGroupNode::new();
730            entry.insert(PersistentFocusHandleEntry::new(value));
731            value
732        }
733    })
734}
735
736pub(crate) fn has_persistent_focus_handle(handle_id: FocusHandleId) -> bool {
737    with_persistent_focus_handle_store(|store| store.contains_handle(handle_id))
738}
739
740pub(crate) fn retain_persistent_focus_handles(
741    live_instance_keys: &HashSet<u64>,
742) -> RemovedPersistentFocusHandles {
743    with_persistent_focus_handle_store_mut(|store| store.retain_instance_keys(live_instance_keys))
744}
745
746pub(crate) fn clear_persistent_focus_handles() {
747    with_persistent_focus_handle_store_mut(PersistentFocusHandleStore::clear);
748}
749
750fn take_next_node_instance_logic_id_override() -> Option<u64> {
751    with_execution_context_mut(|context| context.next_node_instance_logic_id_override.take())
752}
753
754/// Runs `f` inside a replay scope restored from a previously recorded component
755/// snapshot.
756///
757/// The replay scope restores:
758/// - the control-flow group path active at the original call site
759/// - the keyed-instance override active at the original call site
760/// - a one-shot instance-logic-id override for the replayed component root
761pub(crate) fn with_replay_scope<R>(
762    instance_logic_id: u64,
763    group_path: &[u64],
764    instance_key_override: Option<u64>,
765    f: impl FnOnce() -> R,
766) -> R {
767    struct ReplayScopeGuard {
768        previous_group_path: Option<Vec<u64>>,
769        previous_instance_key_stack: Option<Vec<u64>>,
770        previous_instance_logic_id_override: Option<Option<u64>>,
771    }
772
773    impl Drop for ReplayScopeGuard {
774        fn drop(&mut self) {
775            if let Some(previous_group_path) = self.previous_group_path.take() {
776                with_execution_context_mut(|context| {
777                    context.group_path_stack = previous_group_path;
778                });
779            }
780            if let Some(previous_instance_key_stack) = self.previous_instance_key_stack.take() {
781                with_execution_context_mut(|context| {
782                    context.instance_key_stack = previous_instance_key_stack;
783                });
784            }
785            if let Some(previous_instance_logic_id_override) =
786                self.previous_instance_logic_id_override.take()
787            {
788                with_execution_context_mut(|context| {
789                    context.next_node_instance_logic_id_override =
790                        previous_instance_logic_id_override;
791                });
792            }
793        }
794    }
795
796    let previous_group_path = with_execution_context_mut(|context| {
797        std::mem::replace(&mut context.group_path_stack, group_path.to_vec())
798    });
799    let previous_instance_key_stack = with_execution_context_mut(|context| {
800        let next_stack = instance_key_override.into_iter().collect::<Vec<_>>();
801        std::mem::replace(&mut context.instance_key_stack, next_stack)
802    });
803    let previous_instance_logic_id_override = with_execution_context_mut(|context| {
804        context
805            .next_node_instance_logic_id_override
806            .replace(instance_logic_id)
807    });
808    let _guard = ReplayScopeGuard {
809        previous_group_path: Some(previous_group_path),
810        previous_instance_key_stack: Some(previous_instance_key_stack),
811        previous_instance_logic_id_override: Some(previous_instance_logic_id_override),
812    };
813
814    f()
815}
816
817pub(crate) fn with_build_dirty_instance_keys<R>(
818    dirty_instance_keys: &HashSet<u64>,
819    f: impl FnOnce() -> R,
820) -> R {
821    struct BuildDirtyScopeGuard {
822        popped: bool,
823    }
824
825    impl Drop for BuildDirtyScopeGuard {
826        fn drop(&mut self) {
827            if self.popped {
828                return;
829            }
830            with_execution_context_mut(|context| {
831                let popped = context.build_dirty_instance_keys_stack.pop();
832                debug_assert!(
833                    popped.is_some(),
834                    "BUILD_DIRTY_INSTANCE_KEYS_STACK underflow: attempted to pop from empty stack"
835                );
836            });
837            self.popped = true;
838        }
839    }
840
841    with_execution_context_mut(|context| {
842        context
843            .build_dirty_instance_keys_stack
844            .push(Arc::new(dirty_instance_keys.clone()));
845    });
846    let _guard = BuildDirtyScopeGuard { popped: false };
847    f()
848}
849
850pub(crate) fn is_instance_key_build_dirty(instance_key: u64) -> bool {
851    with_execution_context(|context| {
852        context
853            .build_dirty_instance_keys_stack
854            .last()
855            .is_some_and(|dirty_instance_keys| dirty_instance_keys.contains(&instance_key))
856    })
857}
858
859fn consume_pending_build_invalidation(instance_key: u64) -> bool {
860    with_build_invalidation_tracker_mut(|tracker| tracker.dirty_instance_keys.remove(&instance_key))
861}
862
863pub(crate) fn record_replay_boundary_invalidation_for_instance_key(instance_key: u64) {
864    let inserted = with_build_invalidation_tracker_mut(|tracker| {
865        tracker.dirty_instance_keys.insert(instance_key)
866    });
867    if inserted {
868        schedule_runtime_redraw();
869    }
870}
871
872fn track_state_read_dependency(slot: SlotHandle, generation: u64) {
873    if !matches!(current_phase(), Some(RuntimePhase::Build)) {
874        return;
875    }
876    let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
877        return;
878    };
879
880    let key = StateReadDependencyKey { slot, generation };
881    with_state_read_dependency_tracker_mut(|tracker| {
882        if tracker
883            .readers_by_state
884            .get(&key)
885            .is_some_and(|readers| readers.contains(&reader_instance_key))
886        {
887            return;
888        }
889        tracker
890            .readers_by_state
891            .entry(key)
892            .or_default()
893            .insert(reader_instance_key);
894        tracker
895            .states_by_reader
896            .entry(reader_instance_key)
897            .or_default()
898            .insert(key);
899    });
900}
901
902fn state_read_subscribers(slot: SlotHandle, generation: u64) -> Vec<u64> {
903    let key = StateReadDependencyKey { slot, generation };
904    with_state_read_dependency_tracker(|tracker| {
905        tracker
906            .readers_by_state
907            .get(&key)
908            .map(|readers| readers.iter().copied().collect())
909            .unwrap_or_default()
910    })
911}
912
913fn track_focus_dependency(kind: FocusReadDependencyKind) {
914    if !matches!(current_phase(), Some(RuntimePhase::Build)) {
915        return;
916    }
917    let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
918        return;
919    };
920
921    let key = FocusReadDependencyKey { kind };
922    with_focus_read_dependency_tracker_mut(|tracker| {
923        if tracker
924            .readers_by_focus
925            .get(&key)
926            .is_some_and(|readers| readers.contains(&reader_instance_key))
927        {
928            return;
929        }
930        tracker
931            .readers_by_focus
932            .entry(key)
933            .or_default()
934            .insert(reader_instance_key);
935        tracker
936            .focus_by_reader
937            .entry(reader_instance_key)
938            .or_default()
939            .insert(key);
940    });
941}
942
943fn focus_read_subscribers_by_kind(kind: FocusReadDependencyKind) -> Vec<u64> {
944    let key = FocusReadDependencyKey { kind };
945    with_focus_read_dependency_tracker(|tracker| {
946        tracker
947            .readers_by_focus
948            .get(&key)
949            .map(|readers| readers.iter().copied().collect())
950            .unwrap_or_default()
951    })
952}
953
954pub(crate) fn track_focus_read_dependency(handle_id: FocusHandleId) {
955    track_focus_dependency(FocusReadDependencyKind::Handle(handle_id));
956}
957
958pub(crate) fn track_focus_requester_read_dependency(requester_id: FocusRequesterId) {
959    track_focus_dependency(FocusReadDependencyKind::Requester(requester_id));
960}
961
962pub(crate) fn focus_read_subscribers(handle_id: FocusHandleId) -> Vec<u64> {
963    focus_read_subscribers_by_kind(FocusReadDependencyKind::Handle(handle_id))
964}
965
966pub(crate) fn focus_requester_read_subscribers(requester_id: FocusRequesterId) -> Vec<u64> {
967    focus_read_subscribers_by_kind(FocusReadDependencyKind::Requester(requester_id))
968}
969
970pub(crate) fn track_render_slot_read_dependency(handle: FunctorHandle) {
971    if !matches!(current_phase(), Some(RuntimePhase::Build)) {
972        return;
973    }
974    let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
975        return;
976    };
977
978    with_render_slot_read_dependency_tracker_mut(|tracker| {
979        if tracker
980            .readers_by_slot
981            .get(&handle)
982            .is_some_and(|readers| readers.contains(&reader_instance_key))
983        {
984            return;
985        }
986        tracker
987            .readers_by_slot
988            .entry(handle)
989            .or_default()
990            .insert(reader_instance_key);
991        tracker
992            .slots_by_reader
993            .entry(reader_instance_key)
994            .or_default()
995            .insert(handle);
996    });
997}
998
999fn render_slot_read_subscribers(handle: FunctorHandle) -> Vec<u64> {
1000    with_render_slot_read_dependency_tracker(|tracker| {
1001        tracker
1002            .readers_by_slot
1003            .get(&handle)
1004            .map(|readers| readers.iter().copied().collect())
1005            .unwrap_or_default()
1006    })
1007}
1008
1009pub(crate) fn remove_state_read_dependencies(instance_keys: &HashSet<u64>) {
1010    if instance_keys.is_empty() {
1011        return;
1012    }
1013    with_state_read_dependency_tracker_mut(|tracker| {
1014        for instance_key in instance_keys {
1015            let Some(state_keys) = tracker.states_by_reader.remove(instance_key) else {
1016                continue;
1017            };
1018            for state_key in state_keys {
1019                let mut remove_entry = false;
1020                if let Some(readers) = tracker.readers_by_state.get_mut(&state_key) {
1021                    readers.remove(instance_key);
1022                    remove_entry = readers.is_empty();
1023                }
1024                if remove_entry {
1025                    tracker.readers_by_state.remove(&state_key);
1026                }
1027            }
1028        }
1029    });
1030}
1031
1032pub(crate) fn remove_focus_read_dependencies(instance_keys: &HashSet<u64>) {
1033    if instance_keys.is_empty() {
1034        return;
1035    }
1036    with_focus_read_dependency_tracker_mut(|tracker| {
1037        for instance_key in instance_keys {
1038            let Some(focus_keys) = tracker.focus_by_reader.remove(instance_key) else {
1039                continue;
1040            };
1041            for focus_key in focus_keys {
1042                let mut remove_entry = false;
1043                if let Some(readers) = tracker.readers_by_focus.get_mut(&focus_key) {
1044                    readers.remove(instance_key);
1045                    remove_entry = readers.is_empty();
1046                }
1047                if remove_entry {
1048                    tracker.readers_by_focus.remove(&focus_key);
1049                }
1050            }
1051        }
1052    });
1053}
1054
1055pub(crate) fn remove_render_slot_read_dependencies(instance_keys: &HashSet<u64>) {
1056    if instance_keys.is_empty() {
1057        return;
1058    }
1059    with_render_slot_read_dependency_tracker_mut(|tracker| {
1060        for instance_key in instance_keys {
1061            let Some(slot_keys) = tracker.slots_by_reader.remove(instance_key) else {
1062                continue;
1063            };
1064            for slot_key in slot_keys {
1065                let mut remove_entry = false;
1066                if let Some(readers) = tracker.readers_by_slot.get_mut(&slot_key) {
1067                    readers.remove(instance_key);
1068                    remove_entry = readers.is_empty();
1069                }
1070                if remove_entry {
1071                    tracker.readers_by_slot.remove(&slot_key);
1072                }
1073            }
1074        }
1075    });
1076}
1077
1078pub(crate) fn reset_state_read_dependencies() {
1079    with_state_read_dependency_tracker_mut(|tracker| {
1080        *tracker = StateReadDependencyTracker::default();
1081    });
1082}
1083
1084pub(crate) fn reset_focus_read_dependencies() {
1085    with_focus_read_dependency_tracker_mut(|tracker| {
1086        *tracker = FocusReadDependencyTracker::default();
1087    });
1088}
1089
1090pub(crate) fn reset_render_slot_read_dependencies() {
1091    with_render_slot_read_dependency_tracker_mut(|tracker| {
1092        *tracker = RenderSlotReadDependencyTracker::default();
1093    });
1094}
1095
1096pub(crate) fn take_build_invalidations() -> BuildInvalidationSet {
1097    with_build_invalidation_tracker_mut(|tracker| BuildInvalidationSet {
1098        dirty_instance_keys: std::mem::take(&mut tracker.dirty_instance_keys),
1099    })
1100}
1101
1102pub(crate) fn reset_build_invalidations() {
1103    with_build_invalidation_tracker_mut(|tracker| {
1104        *tracker = BuildInvalidationTracker::default();
1105    });
1106}
1107
1108pub(crate) fn remove_build_invalidations(instance_keys: &HashSet<u64>) {
1109    if instance_keys.is_empty() {
1110        return;
1111    }
1112    with_build_invalidation_tracker_mut(|tracker| {
1113        tracker
1114            .dirty_instance_keys
1115            .retain(|instance_key| !instance_keys.contains(instance_key));
1116    });
1117}
1118
1119pub(crate) fn has_pending_build_invalidations() -> bool {
1120    with_build_invalidation_tracker(|tracker| !tracker.dirty_instance_keys.is_empty())
1121}
1122
1123#[derive(Default)]
1124struct FrameClockTracker {
1125    frame_origin: Option<Instant>,
1126    current_frame_time: Option<Instant>,
1127    current_frame_nanos: u64,
1128    previous_frame_time: Option<Instant>,
1129    frame_delta: Duration,
1130    receivers: HashMap<FrameNanosReceiverKey, FrameNanosReceiver>,
1131}
1132
1133#[derive(Hash, Eq, PartialEq, Clone, Copy)]
1134struct FrameNanosReceiverKey {
1135    instance_logic_id: u64,
1136    receiver_hash: u64,
1137}
1138
1139/// Control flow for [`receive_frame_nanos`] callbacks.
1140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1141pub enum FrameNanosControl {
1142    /// Keep this receiver registered and run it again on the next frame.
1143    Continue,
1144    /// Unregister this receiver after the current frame tick.
1145    Stop,
1146}
1147
1148type FrameNanosReceiverCallback = Box<dyn FnMut(u64) -> FrameNanosControl + Send + 'static>;
1149
1150struct FrameNanosReceiver {
1151    owner_instance_key: u64,
1152    callback: FrameNanosReceiverCallback,
1153}
1154
1155fn with_frame_clock_tracker<R>(f: impl FnOnce(&FrameClockTracker) -> R) -> R {
1156    RUNTIME_GLOBALS.with(|globals| f(&globals.frame_clock_tracker.borrow()))
1157}
1158
1159fn with_frame_clock_tracker_mut<R>(f: impl FnOnce(&mut FrameClockTracker) -> R) -> R {
1160    RUNTIME_GLOBALS.with(|globals| f(&mut globals.frame_clock_tracker.borrow_mut()))
1161}
1162
1163pub(crate) fn begin_frame_clock(now: Instant) {
1164    with_frame_clock_tracker_mut(|tracker| {
1165        let frame_origin = *tracker.frame_origin.get_or_insert(now);
1166        tracker.previous_frame_time = tracker.current_frame_time;
1167        tracker.current_frame_time = Some(now);
1168        tracker.current_frame_nanos = now
1169            .saturating_duration_since(frame_origin)
1170            .as_nanos()
1171            .min(u64::MAX as u128) as u64;
1172        tracker.frame_delta = tracker
1173            .previous_frame_time
1174            .map(|previous| now.saturating_duration_since(previous))
1175            .unwrap_or_default();
1176    });
1177}
1178
1179pub(crate) fn reset_frame_clock() {
1180    with_frame_clock_tracker_mut(|tracker| *tracker = FrameClockTracker::default());
1181}
1182
1183pub(crate) fn has_pending_frame_nanos_receivers() -> bool {
1184    with_frame_clock_tracker(|tracker| !tracker.receivers.is_empty())
1185}
1186
1187pub(crate) fn tick_frame_nanos_receivers() {
1188    with_frame_clock_tracker_mut(|tracker| {
1189        let frame_nanos = tracker.current_frame_nanos;
1190        tracker.receivers.retain(|_, receiver| {
1191            matches!(
1192                (receiver.callback)(frame_nanos),
1193                FrameNanosControl::Continue
1194            )
1195        });
1196    });
1197}
1198
1199pub(crate) fn remove_frame_nanos_receivers(instance_keys: &HashSet<u64>) {
1200    if instance_keys.is_empty() {
1201        return;
1202    }
1203    with_frame_clock_tracker_mut(|tracker| {
1204        tracker
1205            .receivers
1206            .retain(|_, receiver| !instance_keys.contains(&receiver.owner_instance_key));
1207    });
1208}
1209
1210pub(crate) fn clear_frame_nanos_receivers() {
1211    with_frame_clock_tracker_mut(|tracker| tracker.receivers.clear());
1212}
1213
1214/// Returns the timestamp of the current frame, if available.
1215///
1216/// The value is set by the renderer at frame begin.
1217pub fn current_frame_time() -> Option<Instant> {
1218    with_frame_clock_tracker(|tracker| tracker.current_frame_time)
1219}
1220
1221/// Returns the current frame timestamp in nanoseconds from runtime origin.
1222pub fn current_frame_nanos() -> u64 {
1223    with_frame_clock_tracker(|tracker| tracker.current_frame_nanos)
1224}
1225
1226/// Returns the elapsed time since the previous frame.
1227pub fn frame_delta() -> Duration {
1228    with_frame_clock_tracker(|tracker| tracker.frame_delta)
1229}
1230
1231fn ensure_frame_receive_phase() {
1232    match current_phase() {
1233        Some(RuntimePhase::Build) => {}
1234        Some(RuntimePhase::Measure) => {
1235            panic!("receive_frame_nanos must not be called inside measure")
1236        }
1237        Some(RuntimePhase::Input) => {
1238            panic!("receive_frame_nanos must be called inside a tessera component build")
1239        }
1240        None => panic!("receive_frame_nanos must be called inside a tessera component build"),
1241    }
1242}
1243
1244fn compute_frame_nanos_receiver_key() -> FrameNanosReceiverKey {
1245    let instance_logic_id = current_instance_logic_id();
1246    let group_path_hash = current_group_path_hash();
1247
1248    let call_counter = next_order_counter(
1249        OrderCounterKind::FrameReceiver,
1250        "ORDER_FRAME_STACK is empty; receive_frame_nanos must be called inside a component",
1251    );
1252
1253    let receiver_hash = hash_components(&[&group_path_hash, &call_counter]);
1254    FrameNanosReceiverKey {
1255        instance_logic_id,
1256        receiver_hash,
1257    }
1258}
1259
1260/// Register a per-frame callback driven by the renderer's frame clock.
1261///
1262/// Registration is keyed by the current callsite identity. Repeated calls from
1263/// the same position keep the existing active callback until it returns
1264/// [`FrameNanosControl::Stop`].
1265pub fn receive_frame_nanos<F>(callback: F)
1266where
1267    F: FnMut(u64) -> FrameNanosControl + Send + 'static,
1268{
1269    ensure_frame_receive_phase();
1270    let frame_nanos_state = remember(current_frame_nanos);
1271    let _ = frame_nanos_state.get();
1272
1273    let owner_instance_key = current_replay_boundary_instance_key_from_scope()
1274        .unwrap_or_else(|| panic!("receive_frame_nanos requires an active component node context"));
1275    let key = compute_frame_nanos_receiver_key();
1276
1277    with_frame_clock_tracker_mut(|tracker| {
1278        tracker.receivers.entry(key).or_insert_with(|| {
1279            let mut callback = callback;
1280            FrameNanosReceiver {
1281                owner_instance_key,
1282                callback: Box::new(move |frame_nanos| {
1283                    if !frame_nanos_state.is_alive() {
1284                        return FrameNanosControl::Stop;
1285                    }
1286                    frame_nanos_state.set(frame_nanos);
1287                    callback(frame_nanos)
1288                }),
1289            }
1290        });
1291    });
1292}
1293
1294pub(crate) fn drop_slots_for_instance_logic_ids(instance_logic_ids: &HashSet<u64>) {
1295    if instance_logic_ids.is_empty() {
1296        return;
1297    }
1298
1299    with_slot_table_mut(|table| {
1300        let mut freed: Vec<(SlotHandle, SlotKey)> = Vec::new();
1301        for (slot, entry) in table.entries.iter() {
1302            if !instance_logic_ids.contains(&entry.key.instance_logic_id) {
1303                continue;
1304            }
1305            if entry.retained {
1306                continue;
1307            }
1308            freed.push((slot, entry.key));
1309        }
1310        for (slot, key) in freed {
1311            table.entries.remove(slot);
1312            table.key_to_slot.remove(&key);
1313        }
1314        for instance_logic_id in instance_logic_ids {
1315            table.cursors_by_instance_logic_id.remove(instance_logic_id);
1316        }
1317    });
1318}
1319
1320fn with_layout_dirty_tracker_mut<R>(f: impl FnOnce(&mut LayoutDirtyTracker) -> R) -> R {
1321    RUNTIME_GLOBALS.with(|globals| f(&mut globals.layout_dirty_tracker.borrow_mut()))
1322}
1323
1324fn record_layout_policy_dirty(
1325    instance_key: u64,
1326    layout_policy: &dyn LayoutPolicyDyn,
1327    modifier: &Modifier,
1328) {
1329    if current_phase() != Some(RuntimePhase::Build) {
1330        return;
1331    }
1332    with_layout_dirty_tracker_mut(|tracker| {
1333        let (measure_changed, placement_changed, next_layout_inputs) =
1334            match tracker.previous_layout_inputs_by_node.remove(&instance_key) {
1335                Some(previous) => {
1336                    let measure_changed = !previous.policy.dyn_measure_eq(layout_policy)
1337                        || !previous.modifier.layout_measure_eq(modifier);
1338                    let placement_changed = !previous.policy.dyn_placement_eq(layout_policy)
1339                        || !previous.modifier.layout_placement_eq(modifier);
1340                    if !measure_changed && !placement_changed {
1341                        (false, false, previous)
1342                    } else {
1343                        (
1344                            measure_changed,
1345                            placement_changed,
1346                            LayoutInputSnapshot {
1347                                policy: layout_policy.clone_box(),
1348                                modifier: modifier.clone(),
1349                            },
1350                        )
1351                    }
1352                }
1353                None => (
1354                    true,
1355                    true,
1356                    LayoutInputSnapshot {
1357                        policy: layout_policy.clone_box(),
1358                        modifier: modifier.clone(),
1359                    },
1360                ),
1361            };
1362        if measure_changed {
1363            tracker
1364                .pending_measure_self_dirty_nodes
1365                .insert(instance_key);
1366        } else if placement_changed {
1367            tracker
1368                .pending_placement_self_dirty_nodes
1369                .insert(instance_key);
1370        }
1371        tracker
1372            .frame_layout_inputs_by_node
1373            .insert(instance_key, next_layout_inputs);
1374    });
1375}
1376
1377pub(crate) fn begin_frame_layout_dirty_tracking() {
1378    with_layout_dirty_tracker_mut(|tracker| {
1379        tracker.frame_layout_inputs_by_node.clear();
1380        tracker.pending_measure_self_dirty_nodes.clear();
1381        tracker.pending_placement_self_dirty_nodes.clear();
1382    });
1383}
1384
1385pub(crate) fn finalize_frame_layout_dirty_tracking() {
1386    with_layout_dirty_tracker_mut(|tracker| {
1387        tracker.ready_measure_self_dirty_nodes =
1388            std::mem::take(&mut tracker.pending_measure_self_dirty_nodes);
1389        tracker.ready_placement_self_dirty_nodes =
1390            std::mem::take(&mut tracker.pending_placement_self_dirty_nodes);
1391        tracker.previous_layout_inputs_by_node =
1392            std::mem::take(&mut tracker.frame_layout_inputs_by_node);
1393    });
1394}
1395
1396pub(crate) fn take_layout_dirty_nodes() -> LayoutDirtyNodes {
1397    with_layout_dirty_tracker_mut(|tracker| LayoutDirtyNodes {
1398        measure_self_nodes: std::mem::take(&mut tracker.ready_measure_self_dirty_nodes),
1399        placement_self_nodes: std::mem::take(&mut tracker.ready_placement_self_dirty_nodes),
1400    })
1401}
1402
1403pub(crate) fn reset_layout_dirty_tracking() {
1404    with_layout_dirty_tracker_mut(|tracker| *tracker = LayoutDirtyTracker::default());
1405}
1406
1407fn record_component_replay_snapshot(runtime: &TesseraRuntime, node_id: NodeId) {
1408    let Some(node) = runtime.component_tree.get(node_id) else {
1409        return;
1410    };
1411    let Some(replay) = node.replay.clone() else {
1412        return;
1413    };
1414
1415    let tree = runtime.component_tree.tree();
1416    let parent_instance_key = tree
1417        .get(node_id)
1418        .and_then(|n| n.parent())
1419        .and_then(|parent_id| tree.get(parent_id))
1420        .map(|parent| parent.get().instance_key);
1421
1422    let snapshot = ReplayNodeSnapshot {
1423        instance_key: node.instance_key,
1424        parent_instance_key,
1425        instance_logic_id: node.instance_logic_id,
1426        group_path: current_group_path(),
1427        instance_key_override: current_instance_key_override(),
1428        fn_name: node.fn_name.clone(),
1429        replay,
1430    };
1431    with_component_replay_tracker_mut(|tracker| {
1432        tracker
1433            .current_nodes
1434            .insert(snapshot.instance_key, snapshot);
1435    });
1436}
1437
1438pub(crate) fn reconcile_layout_structure(
1439    current_children_by_node: &HashMap<u64, Vec<u64>>,
1440) -> StructureReconcileResult {
1441    with_layout_dirty_tracker_mut(|tracker| {
1442        let previous_children_by_node = &tracker.previous_children_by_node;
1443
1444        let mut changed_nodes = HashSet::default();
1445        let mut removed_nodes = HashSet::default();
1446
1447        for (node, current_children) in current_children_by_node {
1448            match previous_children_by_node.get(node) {
1449                Some(previous_children) if previous_children == current_children => {}
1450                _ => {
1451                    changed_nodes.insert(*node);
1452                }
1453            }
1454        }
1455
1456        for node in previous_children_by_node.keys().copied() {
1457            if !current_children_by_node.contains_key(&node) {
1458                changed_nodes.insert(node);
1459                removed_nodes.insert(node);
1460            }
1461        }
1462
1463        tracker.previous_children_by_node = current_children_by_node.clone();
1464        StructureReconcileResult {
1465            changed_nodes,
1466            removed_nodes,
1467        }
1468    })
1469}
1470
1471/// Handle to memoized state created by [`remember`] and [`remember_with_key`].
1472///
1473/// `State<T>` is `Copy + Send + Sync` and provides `with`, `with_mut`, `get`,
1474/// `set`, and `cloned` to read or update the stored value.
1475///
1476/// Handles are validated with a slot generation token so stale references fail
1477/// fast if their slot has been recycled.
1478///
1479/// # Examples
1480///
1481/// ```
1482/// use tessera_ui::{remember, tessera};
1483///
1484/// #[tessera]
1485/// fn counter() {
1486///     let count = remember(|| 0usize);
1487///     count.with_mut(|c| *c += 1);
1488///     let current = count.get();
1489///     assert!(current >= 1);
1490/// }
1491/// ```
1492pub struct State<T> {
1493    slot: SlotHandle,
1494    generation: u64,
1495    _marker: PhantomData<T>,
1496}
1497
1498impl<T> Copy for State<T> {}
1499
1500impl<T> Clone for State<T> {
1501    fn clone(&self) -> Self {
1502        *self
1503    }
1504}
1505
1506impl<T> PartialEq for State<T> {
1507    fn eq(&self, other: &Self) -> bool {
1508        self.slot == other.slot && self.generation == other.generation
1509    }
1510}
1511
1512impl<T> Eq for State<T> {}
1513
1514impl<T> Hash for State<T> {
1515    fn hash<H: Hasher>(&self, state: &mut H) {
1516        self.slot.hash(state);
1517        self.generation.hash(state);
1518    }
1519}
1520
1521impl<T> State<T> {
1522    fn new(slot: SlotHandle, generation: u64) -> Self {
1523        Self {
1524            slot,
1525            generation,
1526            _marker: PhantomData,
1527        }
1528    }
1529}
1530
1531impl<T> State<T>
1532where
1533    T: Send + Sync + 'static,
1534{
1535    fn is_alive(&self) -> bool {
1536        with_slot_table(|table| {
1537            let Some(entry) = table.entries.get(self.slot) else {
1538                return false;
1539            };
1540
1541            entry.generation == self.generation
1542                && entry.key.type_id == TypeId::of::<T>()
1543                && entry.value.is_some()
1544        })
1545    }
1546
1547    fn load_entry(&self) -> Arc<dyn Any + Send + Sync> {
1548        with_slot_table(|table| {
1549            let entry = table
1550                .entries
1551                .get(self.slot)
1552                .unwrap_or_else(|| panic!("State points to freed slot: {:?}", self.slot));
1553
1554            if entry.generation != self.generation {
1555                panic!(
1556                    "State is stale (slot {:?}, generation {}, current generation {})",
1557                    self.slot, self.generation, entry.generation
1558                );
1559            }
1560
1561            if entry.key.type_id != TypeId::of::<T>() {
1562                panic!(
1563                    "State type mismatch for slot {:?}: expected {}, stored {:?}",
1564                    self.slot,
1565                    std::any::type_name::<T>(),
1566                    entry.key.type_id
1567                );
1568            }
1569
1570            entry
1571                .value
1572                .as_ref()
1573                .unwrap_or_else(|| panic!("State slot {:?} has been cleared", self.slot))
1574                .clone()
1575        })
1576    }
1577
1578    fn load_lock(&self) -> Arc<RwLock<T>> {
1579        self.load_entry()
1580            .downcast::<RwLock<T>>()
1581            .unwrap_or_else(|_| panic!("State slot {:?} downcast failed", self.slot))
1582    }
1583
1584    /// Execute a closure with a shared reference to the stored value.
1585    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
1586        track_state_read_dependency(self.slot, self.generation);
1587        let lock = self.load_lock();
1588        let guard = lock.read();
1589        f(&guard)
1590    }
1591
1592    /// Execute a closure with a mutable reference to the stored value.
1593    #[track_caller]
1594    pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
1595        let lock = self.load_lock();
1596
1597        let result = {
1598            let mut guard = lock.write();
1599            f(&mut guard)
1600        };
1601
1602        let subscribers = state_read_subscribers(self.slot, self.generation);
1603        for instance_key in subscribers {
1604            record_replay_boundary_invalidation_for_instance_key(instance_key);
1605        }
1606        result
1607    }
1608
1609    /// Get a cloned value. Requires `T: Clone`.
1610    pub fn get(&self) -> T
1611    where
1612        T: Clone,
1613    {
1614        self.with(Clone::clone)
1615    }
1616
1617    /// Replace the stored value.
1618    #[track_caller]
1619    pub fn set(&self, value: T) {
1620        self.with_mut(|slot| *slot = value);
1621    }
1622}
1623
1624struct RuntimeGlobals {
1625    slot_table: RefCell<SlotTable>,
1626    component_replay_tracker: RefCell<ComponentReplayTracker>,
1627    build_invalidation_tracker: RefCell<BuildInvalidationTracker>,
1628    state_read_dependency_tracker: RefCell<StateReadDependencyTracker>,
1629    focus_read_dependency_tracker: RefCell<FocusReadDependencyTracker>,
1630    render_slot_read_dependency_tracker: RefCell<RenderSlotReadDependencyTracker>,
1631    redraw_waker: RefCell<Option<RedrawWaker>>,
1632    persistent_focus_handle_store: RefCell<PersistentFocusHandleStore>,
1633    frame_clock_tracker: RefCell<FrameClockTracker>,
1634    layout_dirty_tracker: RefCell<LayoutDirtyTracker>,
1635    runtime: RefCell<TesseraRuntime>,
1636}
1637
1638impl RuntimeGlobals {
1639    fn new() -> Self {
1640        Self {
1641            slot_table: RefCell::new(SlotTable::default()),
1642            component_replay_tracker: RefCell::new(ComponentReplayTracker::default()),
1643            build_invalidation_tracker: RefCell::new(BuildInvalidationTracker::default()),
1644            state_read_dependency_tracker: RefCell::new(StateReadDependencyTracker::default()),
1645            focus_read_dependency_tracker: RefCell::new(FocusReadDependencyTracker::default()),
1646            render_slot_read_dependency_tracker: RefCell::new(
1647                RenderSlotReadDependencyTracker::default(),
1648            ),
1649            redraw_waker: RefCell::new(None),
1650            persistent_focus_handle_store: RefCell::new(PersistentFocusHandleStore::default()),
1651            frame_clock_tracker: RefCell::new(FrameClockTracker::default()),
1652            layout_dirty_tracker: RefCell::new(LayoutDirtyTracker::default()),
1653            runtime: RefCell::new(TesseraRuntime::default()),
1654        }
1655    }
1656}
1657
1658thread_local! {
1659    static RUNTIME_GLOBALS: RuntimeGlobals = RuntimeGlobals::new();
1660}
1661
1662/// Runtime state container.
1663#[derive(Default)]
1664pub struct TesseraRuntime {
1665    /// Hierarchical structure of all UI components in the application.
1666    pub component_tree: ComponentTree,
1667    /// Current window dimensions in physical pixels.
1668    pub(crate) window_size: [u32; 2],
1669    /// Cursor icon change request from UI components.
1670    pub cursor_icon_request: Option<winit::window::CursorIcon>,
1671    /// Whether the window is currently minimized.
1672    pub(crate) window_minimized: bool,
1673}
1674
1675impl TesseraRuntime {
1676    /// Executes a closure with a shared, read-only reference to the runtime.
1677    pub fn with<F, R>(f: F) -> R
1678    where
1679        F: FnOnce(&Self) -> R,
1680    {
1681        RUNTIME_GLOBALS.with(|globals| f(&globals.runtime.borrow()))
1682    }
1683
1684    /// Executes a closure with an exclusive, mutable reference to the runtime.
1685    pub fn with_mut<F, R>(f: F) -> R
1686    where
1687        F: FnOnce(&mut Self) -> R,
1688    {
1689        RUNTIME_GLOBALS.with(|globals| f(&mut globals.runtime.borrow_mut()))
1690    }
1691
1692    /// Get the current window size in physical pixels.
1693    pub fn window_size(&self) -> [u32; 2] {
1694        self.window_size
1695    }
1696
1697    /// Sets identity fields for the current component node.
1698    pub(crate) fn set_current_node_identity(&mut self, instance_key: u64, instance_logic_id: u64) {
1699        if let Some(node) = self.component_tree.current_node_mut() {
1700            node.instance_key = instance_key;
1701            node.instance_logic_id = instance_logic_id;
1702        } else {
1703            debug_assert!(
1704                false,
1705                "set_current_node_identity must be called inside a component build"
1706            );
1707        }
1708    }
1709
1710    /// Stores replay metadata for the current component node.
1711    pub(crate) fn set_current_component_replay<P>(
1712        &mut self,
1713        runner: Arc<dyn ErasedComponentRunner>,
1714        props: &P,
1715    ) -> bool
1716    where
1717        P: Prop,
1718    {
1719        let current_node_info = self
1720            .component_tree
1721            .current_node()
1722            .map(|node| (node.instance_key, node.instance_logic_id));
1723        let previous_replay = current_node_info.and_then(|(instance_key, instance_logic_id)| {
1724            with_component_replay_tracker(|tracker| {
1725                let previous = tracker.previous_nodes.get(&instance_key)?;
1726                if previous.instance_logic_id != instance_logic_id {
1727                    return None;
1728                }
1729                if previous.replay.props.equals(props) {
1730                    Some(previous.replay.clone())
1731                } else {
1732                    None
1733                }
1734            })
1735        });
1736
1737        let pending_dirty = current_node_info
1738            .map(|(instance_key, _)| consume_pending_build_invalidation(instance_key))
1739            .unwrap_or(false);
1740
1741        if let Some((instance_key, instance_logic_id)) = current_node_info
1742            && let Some(replay) = previous_replay.clone()
1743            && !is_instance_key_build_dirty(instance_key)
1744            && !pending_dirty
1745            && self
1746                .component_tree
1747                .try_reuse_current_subtree(instance_key, instance_logic_id)
1748        {
1749            if let Some(node) = self.component_tree.current_node_mut() {
1750                node.replay = Some(replay);
1751                node.props_unchanged_from_previous = true;
1752            }
1753            return true;
1754        }
1755
1756        if let Some(node) = self.component_tree.current_node_mut() {
1757            if let Some(replay) = previous_replay {
1758                node.replay = Some(replay);
1759                node.props_unchanged_from_previous = true;
1760            } else {
1761                node.replay = Some(ComponentReplayData::new(runner, props));
1762                node.props_unchanged_from_previous = false;
1763            }
1764        } else {
1765            debug_assert!(
1766                false,
1767                "set_current_component_replay must be called inside a component build"
1768            );
1769            return false;
1770        }
1771        if let Some(node_id) = current_node_id() {
1772            record_component_replay_snapshot(self, node_id);
1773        }
1774        false
1775    }
1776
1777    /// Sets the layout policy for the current component node.
1778    pub(crate) fn set_current_layout_policy_boxed(&mut self, policy: Box<dyn LayoutPolicyDyn>) {
1779        if let Some(node) = self.component_tree.current_node_mut() {
1780            node.layout_policy = policy;
1781        } else {
1782            debug_assert!(
1783                false,
1784                "set_current_layout_policy_boxed must be called inside a component build"
1785            );
1786        }
1787    }
1788
1789    /// Sets the render policy for the current component node.
1790    pub(crate) fn set_current_render_policy_boxed(&mut self, policy: Box<dyn RenderPolicyDyn>) {
1791        if let Some(node) = self.component_tree.current_node_mut() {
1792            node.render_policy = policy;
1793        } else {
1794            debug_assert!(
1795                false,
1796                "set_current_render_policy_boxed must be called inside a component build"
1797            );
1798        }
1799    }
1800
1801    /// Appends a modifier chain to the current component node.
1802    pub(crate) fn append_current_modifier(&mut self, modifier: Modifier) {
1803        if let Some(node) = self.component_tree.current_node_mut() {
1804            node.modifier = node.modifier.clone().then(modifier);
1805        } else {
1806            debug_assert!(
1807                false,
1808                "append_current_modifier must be called inside a component build"
1809            );
1810        }
1811    }
1812
1813    pub(crate) fn set_current_accessibility(&mut self, accessibility: Option<AccessibilityNode>) {
1814        if let Some(node_id) = current_node_id()
1815            && let Some(metadata) = self.component_tree.metadatas_mut().get_mut(&node_id)
1816        {
1817            metadata.accessibility = accessibility;
1818        } else {
1819            debug_assert!(
1820                false,
1821                "set_current_accessibility must be called inside a component build"
1822            );
1823        }
1824    }
1825
1826    pub(crate) fn set_current_accessibility_action_handler(
1827        &mut self,
1828        handler: Option<AccessibilityActionHandler>,
1829    ) {
1830        if let Some(node_id) = current_node_id()
1831            && let Some(metadata) = self.component_tree.metadatas_mut().get_mut(&node_id)
1832        {
1833            metadata.accessibility_action_handler = handler;
1834        } else {
1835            debug_assert!(
1836                false,
1837                "set_current_accessibility_action_handler must be called inside a component build"
1838            );
1839        }
1840    }
1841
1842    pub(crate) fn bind_current_focus_requester(&mut self, requester: FocusRequester) {
1843        if let Some(current) = self.component_tree.current_node_mut() {
1844            current.focus_requester_binding = Some(requester);
1845        } else {
1846            debug_assert!(
1847                false,
1848                "bind_current_focus_requester must be called inside a component build"
1849            );
1850        }
1851    }
1852
1853    pub(crate) fn ensure_current_focus_target(&mut self, node: FocusNode) {
1854        if let Some(current) = self.component_tree.current_node_mut() {
1855            if current.focus_registration.is_none() {
1856                current.focus_registration = Some(FocusRegistration::target(node));
1857            }
1858        } else {
1859            debug_assert!(
1860                false,
1861                "ensure_current_focus_target must be called inside a component build"
1862            );
1863        }
1864    }
1865
1866    pub(crate) fn ensure_current_focus_scope(&mut self, scope: FocusScopeNode) {
1867        if let Some(current) = self.component_tree.current_node_mut() {
1868            if current.focus_registration.is_none() {
1869                current.focus_registration = Some(FocusRegistration::scope(scope));
1870            }
1871        } else {
1872            debug_assert!(
1873                false,
1874                "ensure_current_focus_scope must be called inside a component build"
1875            );
1876        }
1877    }
1878
1879    pub(crate) fn ensure_current_focus_group(&mut self, group: FocusGroupNode) {
1880        if let Some(current) = self.component_tree.current_node_mut() {
1881            if current.focus_registration.is_none() {
1882                current.focus_registration = Some(FocusRegistration::group(group));
1883            }
1884        } else {
1885            debug_assert!(
1886                false,
1887                "ensure_current_focus_group must be called inside a component build"
1888            );
1889        }
1890    }
1891
1892    pub(crate) fn current_focus_target_handle(&self) -> Option<FocusNode> {
1893        let registration = self.component_tree.current_node()?.focus_registration?;
1894        (registration.kind == FocusRegistrationKind::Target)
1895            .then(|| FocusNode::from_handle_id(registration.id))
1896    }
1897
1898    pub(crate) fn current_focus_scope_handle(&self) -> Option<FocusScopeNode> {
1899        let registration = self.component_tree.current_node()?.focus_registration?;
1900        (registration.kind == FocusRegistrationKind::Scope)
1901            .then(|| FocusScopeNode::from_handle_id(registration.id))
1902    }
1903
1904    pub(crate) fn current_focus_group_handle(&self) -> Option<FocusGroupNode> {
1905        let registration = self.component_tree.current_node()?.focus_registration?;
1906        (registration.kind == FocusRegistrationKind::Group)
1907            .then(|| FocusGroupNode::from_handle_id(registration.id))
1908    }
1909
1910    pub(crate) fn set_current_focus_properties(&mut self, properties: FocusProperties) {
1911        if let Some(current) = self.component_tree.current_node_mut() {
1912            if let Some(registration) = current.focus_registration.as_mut() {
1913                registration.properties = properties;
1914            } else {
1915                debug_assert!(
1916                    false,
1917                    "set_current_focus_properties requires focus_target, focus_scope, or focus_group first"
1918                );
1919            }
1920        } else {
1921            debug_assert!(
1922                false,
1923                "set_current_focus_properties must be called inside a component build"
1924            );
1925        }
1926    }
1927
1928    pub(crate) fn set_current_focus_traversal_policy(&mut self, policy: FocusTraversalPolicy) {
1929        if let Some(current) = self.component_tree.current_node_mut() {
1930            if current.focus_registration.is_some_and(|registration| {
1931                matches!(
1932                    registration.kind,
1933                    FocusRegistrationKind::Scope | FocusRegistrationKind::Group
1934                )
1935            }) {
1936                current.focus_traversal_policy = Some(policy);
1937            } else {
1938                debug_assert!(
1939                    false,
1940                    "set_current_focus_traversal_policy requires focus_scope or focus_group first"
1941                );
1942            }
1943        } else {
1944            debug_assert!(
1945                false,
1946                "set_current_focus_traversal_policy must be called inside a component build"
1947            );
1948        }
1949    }
1950
1951    pub(crate) fn set_current_focus_changed_handler(&mut self, handler: CallbackWith<FocusState>) {
1952        if let Some(current) = self.component_tree.current_node_mut() {
1953            current.focus_changed_handler = Some(handler);
1954        } else {
1955            debug_assert!(
1956                false,
1957                "set_current_focus_changed_handler must be called inside a component build"
1958            );
1959        }
1960    }
1961
1962    pub(crate) fn set_current_focus_event_handler(&mut self, handler: CallbackWith<FocusState>) {
1963        if let Some(current) = self.component_tree.current_node_mut() {
1964            current.focus_event_handler = Some(handler);
1965        } else {
1966            debug_assert!(
1967                false,
1968                "set_current_focus_event_handler must be called inside a component build"
1969            );
1970        }
1971    }
1972
1973    pub(crate) fn set_current_focus_beyond_bounds_handler(
1974        &mut self,
1975        handler: CallbackWith<FocusDirection, bool>,
1976    ) {
1977        if let Some(current) = self.component_tree.current_node_mut() {
1978            current.focus_beyond_bounds_handler = Some(handler);
1979        } else {
1980            debug_assert!(
1981                false,
1982                "set_current_focus_beyond_bounds_handler must be called inside a component build"
1983            );
1984        }
1985    }
1986
1987    pub(crate) fn set_current_focus_reveal_handler(
1988        &mut self,
1989        handler: CallbackWith<FocusRevealRequest, bool>,
1990    ) {
1991        if let Some(current) = self.component_tree.current_node_mut() {
1992            current.focus_reveal_handler = Some(handler);
1993        } else {
1994            debug_assert!(
1995                false,
1996                "set_current_focus_reveal_handler must be called inside a component build"
1997            );
1998        }
1999    }
2000
2001    pub(crate) fn set_current_focus_restorer_fallback(&mut self, fallback: FocusRequester) {
2002        if let Some(current) = self.component_tree.current_node_mut() {
2003            if current
2004                .focus_registration
2005                .is_some_and(|registration| registration.kind == FocusRegistrationKind::Scope)
2006            {
2007                current.focus_restorer_fallback = Some(fallback);
2008            } else {
2009                debug_assert!(
2010                    false,
2011                    "set_current_focus_restorer_fallback requires focus_scope or focus_restorer first"
2012                );
2013            }
2014        } else {
2015            debug_assert!(
2016                false,
2017                "set_current_focus_restorer_fallback must be called inside a component build"
2018            );
2019        }
2020    }
2021
2022    pub(crate) fn finalize_current_layout_policy_dirty(&mut self) {
2023        if let Some(node) = self.component_tree.current_node() {
2024            record_layout_policy_dirty(
2025                node.instance_key,
2026                node.layout_policy.as_ref(),
2027                &node.modifier,
2028            );
2029        } else {
2030            debug_assert!(
2031                false,
2032                "finalize_current_layout_policy_dirty must be called inside a component build"
2033            );
2034        }
2035    }
2036}
2037
2038/// Guard that records the current component node id for the calling thread.
2039/// Nested components push their id and pop on drop, forming a stack.
2040pub struct NodeContextGuard {
2041    popped: bool,
2042    instance_logic_id_popped: bool,
2043    #[cfg(feature = "profiling")]
2044    profiling_guard: Option<crate::profiler::ScopeGuard>,
2045}
2046
2047/// Guard that keeps the current component instance key on the execution stack.
2048pub struct CurrentComponentInstanceGuard {
2049    popped: bool,
2050}
2051
2052/// Execution phase for `remember` usage checks.
2053#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2054pub enum RuntimePhase {
2055    /// Component render/build phase (allowed for `remember`).
2056    Build,
2057    /// Measurement phase (disallowed for `remember`).
2058    Measure,
2059    /// Input handling phase (disallowed for `remember`).
2060    Input,
2061}
2062
2063/// Guard for execution phase stack.
2064pub struct PhaseGuard {
2065    popped: bool,
2066}
2067
2068impl PhaseGuard {
2069    /// Pop the current phase immediately.
2070    pub fn pop(mut self) {
2071        if !self.popped {
2072            pop_phase();
2073            self.popped = true;
2074        }
2075    }
2076}
2077
2078impl Drop for PhaseGuard {
2079    fn drop(&mut self) {
2080        if !self.popped {
2081            pop_phase();
2082            self.popped = true;
2083        }
2084    }
2085}
2086
2087impl NodeContextGuard {
2088    /// Pop the current node id immediately. Usually you rely on `Drop` instead.
2089    pub fn pop(mut self) {
2090        if !self.popped {
2091            pop_current_node();
2092            self.popped = true;
2093        }
2094        if !self.instance_logic_id_popped {
2095            pop_instance_logic_id();
2096            self.instance_logic_id_popped = true;
2097        }
2098    }
2099}
2100
2101impl Drop for NodeContextGuard {
2102    fn drop(&mut self) {
2103        #[cfg(feature = "profiling")]
2104        {
2105            let _ = self.profiling_guard.take();
2106        }
2107        if !self.popped {
2108            pop_current_node();
2109            self.popped = true;
2110        }
2111        if !self.instance_logic_id_popped {
2112            pop_instance_logic_id();
2113            self.instance_logic_id_popped = true;
2114        }
2115    }
2116}
2117
2118impl CurrentComponentInstanceGuard {
2119    /// Pop the current component instance key immediately. Usually you rely on
2120    /// `Drop` instead.
2121    pub fn pop(mut self) {
2122        if !self.popped {
2123            pop_current_component_instance_key();
2124            self.popped = true;
2125        }
2126    }
2127}
2128
2129impl Drop for CurrentComponentInstanceGuard {
2130    fn drop(&mut self) {
2131        if !self.popped {
2132            pop_current_component_instance_key();
2133            self.popped = true;
2134        }
2135    }
2136}
2137
2138/// Push the given node id as the current executing component for this thread.
2139pub fn push_current_node(
2140    node_id: NodeId,
2141    component_type_id: u64,
2142    fn_name: &str,
2143) -> NodeContextGuard {
2144    #[cfg(not(feature = "profiling"))]
2145    let _ = fn_name;
2146    #[allow(unused_variables)]
2147    let parent_node_id = with_execution_context_mut(|context| {
2148        let parent = context.node_context_stack.last().copied();
2149        context.node_context_stack.push(node_id);
2150        parent
2151    });
2152
2153    // Get the parent's call index and increment it
2154    // This distinguishes multiple calls to the same component (e.g., foo(1);
2155    // foo(2);)
2156    let parent_call_index = next_child_instance_call_index();
2157    let parent_instance_logic_id = with_execution_context(|context| {
2158        context.instance_logic_id_stack.last().copied().unwrap_or(0)
2159    });
2160
2161    let group_path_hash = current_group_path_hash();
2162    let has_group_path = with_execution_context(|context| !context.group_path_stack.is_empty());
2163
2164    // Combine component_type_id with parent_instance_logic_id, the current
2165    // control-flow group path, and the call index local to that group. This
2166    // ensures:
2167    // 1. foo(1) and foo(2) get different logic_ids (via parent_call_index)
2168    // 2. Components in different control-flow groups get different logic_ids even
2169    //    when each group starts its local call index from zero
2170    // 3. Components in different container instances get different logic_ids (via
2171    //    parent_instance_logic_id)
2172    let instance_salt = if let Some(key_hash) = current_instance_key_override() {
2173        hash_components(&[&key_hash, &group_path_hash, &parent_call_index])
2174    } else if has_group_path {
2175        hash_components(&[&group_path_hash, &parent_call_index])
2176    } else {
2177        parent_call_index
2178    };
2179
2180    let instance_logic_id =
2181        if let Some(instance_logic_id_override) = take_next_node_instance_logic_id_override() {
2182            instance_logic_id_override
2183        } else if parent_call_index == 0
2184            && parent_instance_logic_id == 0
2185            && current_instance_key_override().is_none()
2186            && !has_group_path
2187        {
2188            component_type_id
2189        } else {
2190            hash_components(&[
2191                &component_type_id,
2192                &parent_instance_logic_id,
2193                &instance_salt,
2194            ])
2195        };
2196
2197    with_execution_context_mut(|context| {
2198        context.instance_logic_id_stack.push(instance_logic_id);
2199    });
2200
2201    push_order_frame();
2202
2203    #[cfg(feature = "profiling")]
2204    let profiling_guard = match current_phase() {
2205        Some(RuntimePhase::Build) => {
2206            crate::profiler::make_build_scope_guard(node_id, parent_node_id, fn_name)
2207        }
2208        _ => None,
2209    };
2210
2211    NodeContextGuard {
2212        popped: false,
2213        instance_logic_id_popped: false,
2214        #[cfg(feature = "profiling")]
2215        profiling_guard,
2216    }
2217}
2218
2219/// Push the given node id with an already resolved instance logic id.
2220///
2221/// This is used outside build (for example input/measure), where component
2222/// identity must be restored from the recorded tree node rather than derived
2223/// from call order.
2224pub fn push_current_node_with_instance_logic_id(
2225    node_id: NodeId,
2226    instance_logic_id: u64,
2227    fn_name: &str,
2228) -> NodeContextGuard {
2229    #[cfg(not(feature = "profiling"))]
2230    let _ = fn_name;
2231    #[allow(unused_variables)]
2232    let parent_node_id = with_execution_context_mut(|context| {
2233        let parent = context.node_context_stack.last().copied();
2234        context.node_context_stack.push(node_id);
2235        parent
2236    });
2237
2238    let _ = next_child_instance_call_index();
2239
2240    with_execution_context_mut(|context| {
2241        context.instance_logic_id_stack.push(instance_logic_id);
2242    });
2243    push_order_frame();
2244
2245    #[cfg(feature = "profiling")]
2246    let profiling_guard = match current_phase() {
2247        Some(RuntimePhase::Build) => {
2248            crate::profiler::make_build_scope_guard(node_id, parent_node_id, fn_name)
2249        }
2250        _ => None,
2251    };
2252
2253    NodeContextGuard {
2254        popped: false,
2255        instance_logic_id_popped: false,
2256        #[cfg(feature = "profiling")]
2257        profiling_guard,
2258    }
2259}
2260
2261/// Push the current component instance key for the active execution scope.
2262pub fn push_current_component_instance_key(instance_key: u64) -> CurrentComponentInstanceGuard {
2263    with_execution_context_mut(|context| {
2264        context.current_component_instance_stack.push(instance_key);
2265    });
2266    CurrentComponentInstanceGuard { popped: false }
2267}
2268
2269fn pop_current_component_instance_key() {
2270    with_execution_context_mut(|context| {
2271        let popped = context.current_component_instance_stack.pop();
2272        debug_assert!(
2273            popped.is_some(),
2274            "Attempted to pop current component instance key from an empty stack"
2275        );
2276    });
2277}
2278
2279fn pop_current_node() {
2280    with_execution_context_mut(|context| {
2281        let popped = context.node_context_stack.pop();
2282        debug_assert!(
2283            popped.is_some(),
2284            "Attempted to pop current node from an empty stack"
2285        );
2286    });
2287    pop_order_frame("ORDER_FRAME_STACK underflow: attempted to pop from empty stack");
2288}
2289
2290/// Get the node id at the top of the thread-local component stack.
2291pub fn current_node_id() -> Option<NodeId> {
2292    with_execution_context(|context| context.node_context_stack.last().copied())
2293}
2294
2295fn current_instance_logic_id_opt() -> Option<u64> {
2296    with_execution_context(|context| context.instance_logic_id_stack.last().copied())
2297}
2298
2299/// Returns the current component instance logic id.
2300pub(crate) fn current_instance_logic_id() -> u64 {
2301    current_instance_logic_id_opt()
2302        .expect("current_instance_logic_id must be called inside a component")
2303}
2304
2305/// Returns the instance key for the current component call site.
2306pub(crate) fn current_instance_key() -> u64 {
2307    let instance_logic_id = current_instance_logic_id_opt()
2308        .expect("current_instance_key must be called inside a component");
2309    let group_path_hash = current_group_path_hash();
2310    hash_components(&[&instance_logic_id, &group_path_hash])
2311}
2312
2313fn pop_instance_logic_id() {
2314    with_execution_context_mut(|context| {
2315        let _ = context.instance_logic_id_stack.pop();
2316    });
2317}
2318
2319/// Push an execution phase for the current thread.
2320pub fn push_phase(phase: RuntimePhase) -> PhaseGuard {
2321    with_execution_context_mut(|context| {
2322        context.phase_stack.push(phase);
2323    });
2324    PhaseGuard { popped: false }
2325}
2326
2327fn pop_phase() {
2328    with_execution_context_mut(|context| {
2329        let popped = context.phase_stack.pop();
2330        debug_assert!(
2331            popped.is_some(),
2332            "Attempted to pop execution phase from an empty stack"
2333        );
2334    });
2335}
2336
2337pub(crate) fn current_phase() -> Option<RuntimePhase> {
2338    with_execution_context(|context| context.phase_stack.last().copied())
2339}
2340
2341/// Push a group id onto the thread-local control-flow stack.
2342pub(crate) fn push_group_id(group_id: u64) {
2343    with_execution_context_mut(|context| {
2344        context.group_path_stack.push(group_id);
2345    });
2346}
2347
2348/// Pop a group id from the thread-local control-flow stack.
2349pub(crate) fn pop_group_id(expected_group_id: u64) {
2350    with_execution_context_mut(|context| {
2351        if let Some(popped) = context.group_path_stack.pop() {
2352            debug_assert_eq!(
2353                popped, expected_group_id,
2354                "Unbalanced GroupGuard stack: expected {}, got {}",
2355                expected_group_id, popped
2356            );
2357        } else {
2358            debug_assert!(false, "Attempted to pop GroupGuard from an empty stack");
2359        }
2360    });
2361}
2362
2363/// Get a clone of the current control-flow path.
2364fn current_group_path() -> Vec<u64> {
2365    with_execution_context(|context| context.group_path_stack.clone())
2366}
2367
2368fn current_group_path_hash() -> u64 {
2369    with_execution_context(|context| hash_components(&[&context.group_path_stack[..]]))
2370}
2371
2372fn current_instance_key_override() -> Option<u64> {
2373    with_execution_context(|context| context.instance_key_stack.last().copied())
2374}
2375
2376/// RAII guard that tracks control-flow grouping for the current component node.
2377///
2378/// A guard pushes the provided group id when constructed and pops it when
2379/// dropped, ensuring grouping stays balanced even with early returns or panics.
2380pub struct GroupGuard {
2381    group_id: u64,
2382}
2383
2384impl GroupGuard {
2385    /// Push a group id onto the current component's group stack.
2386    pub fn new(group_id: u64) -> Self {
2387        push_group_id(group_id);
2388        push_order_frame();
2389        Self { group_id }
2390    }
2391}
2392
2393impl Drop for GroupGuard {
2394    fn drop(&mut self) {
2395        pop_order_frame("ORDER_FRAME_STACK underflow: attempted to pop GroupGuard frame");
2396        pop_group_id(self.group_id);
2397    }
2398}
2399
2400/// RAII guard for path-only control-flow groups, primarily used for loop
2401/// bodies.
2402///
2403/// Unlike [`GroupGuard`], this guard does not create a new local order frame.
2404/// Repeated iterations therefore continue consuming sibling call indices from
2405/// the surrounding component scope instead of restarting from zero each time.
2406pub struct PathGroupGuard {
2407    group_id: u64,
2408}
2409
2410impl PathGroupGuard {
2411    /// Push a group id onto the current component's group stack without
2412    /// resetting local call-order counters.
2413    pub fn new(group_id: u64) -> Self {
2414        push_group_id(group_id);
2415        Self { group_id }
2416    }
2417}
2418
2419impl Drop for PathGroupGuard {
2420    fn drop(&mut self) {
2421        pop_group_id(self.group_id);
2422    }
2423}
2424
2425/// RAII guard that sets a stable instance key for the duration of a block.
2426pub struct InstanceKeyGuard {
2427    key_hash: u64,
2428}
2429
2430impl InstanceKeyGuard {
2431    /// Push a key hash for instance identity.
2432    pub fn new(key_hash: u64) -> Self {
2433        with_execution_context_mut(|context| {
2434            context.instance_key_stack.push(key_hash);
2435        });
2436        Self { key_hash }
2437    }
2438}
2439
2440impl Drop for InstanceKeyGuard {
2441    fn drop(&mut self) {
2442        with_execution_context_mut(|context| {
2443            let popped = context.instance_key_stack.pop();
2444            debug_assert_eq!(
2445                popped,
2446                Some(self.key_hash),
2447                "Unbalanced InstanceKeyGuard stack"
2448            );
2449        });
2450    }
2451}
2452
2453fn hash_components<H: Hash + ?Sized>(parts: &[&H]) -> u64 {
2454    let mut hasher = rustc_hash::FxHasher::default();
2455    for part in parts {
2456        part.hash(&mut hasher);
2457    }
2458    hasher.finish()
2459}
2460
2461fn compute_slot_key<K: Hash>(key: &K) -> (u64, u64) {
2462    let instance_logic_id = current_instance_logic_id();
2463    let group_path_hash = current_group_path_hash();
2464    let key_hash = hash_components(&[key]);
2465
2466    // Get the call counter to distinguish multiple remember calls within the same
2467    // component Note: instance_logic_id already distinguishes different component
2468    // instances (foo(1) vs foo(2)) and group_path_hash handles nested control
2469    // flow (if/loop)
2470    let call_counter = next_order_counter(
2471        OrderCounterKind::Remember,
2472        "ORDER_FRAME_STACK is empty; remember must be called inside a component",
2473    );
2474
2475    let slot_hash = hash_components(&[&group_path_hash, &key_hash, &call_counter]);
2476    (instance_logic_id, slot_hash)
2477}
2478
2479fn compute_functor_slot_key<K: Hash>(key: &K) -> (u64, u64) {
2480    let instance_logic_id = current_instance_logic_id();
2481    let group_path_hash = current_group_path_hash();
2482    let key_hash = hash_components(&[key]);
2483
2484    let call_counter = next_order_counter(
2485        OrderCounterKind::Functor,
2486        "ORDER_FRAME_STACK is empty; callback constructors must be called inside a component",
2487    );
2488
2489    let slot_hash = hash_components(&[&group_path_hash, &key_hash, &call_counter]);
2490    (instance_logic_id, slot_hash)
2491}
2492
2493pub(crate) fn ensure_build_phase() {
2494    match current_phase() {
2495        Some(RuntimePhase::Build) => {}
2496        Some(RuntimePhase::Measure) => {
2497            panic!("remember must not be called inside measure; move state to component render")
2498        }
2499        Some(RuntimePhase::Input) => {
2500            panic!(
2501                "remember must not be called inside typed input handlers; move state to component render"
2502            )
2503        }
2504        None => panic!(
2505            "remember must be called inside a tessera component. Ensure you're calling this from within a function annotated with #[tessera]."
2506        ),
2507    }
2508}
2509
2510fn remember_functor_cell_with_key<K, T, F>(key: K, init: F) -> (Arc<T>, FunctorHandle)
2511where
2512    K: Hash,
2513    T: Send + Sync + 'static,
2514    F: FnOnce() -> T,
2515{
2516    ensure_build_phase();
2517    let (instance_logic_id, slot_hash) = compute_functor_slot_key(&key);
2518    let slot_key = SlotKey {
2519        instance_logic_id,
2520        slot_hash,
2521        type_id: TypeId::of::<T>(),
2522    };
2523
2524    with_slot_table_mut(|table| {
2525        let mut init_opt = Some(init);
2526        if let Some(slot) = table.try_fast_slot_lookup(slot_key) {
2527            let epoch = table.epoch;
2528            let (generation, value): (u64, Arc<dyn Any + Send + Sync>) = {
2529                let entry = table
2530                    .entries
2531                    .get_mut(slot)
2532                    .expect("functor slot entry should exist");
2533
2534                if entry.key.type_id != slot_key.type_id {
2535                    panic!(
2536                        "callback slot type mismatch: expected {}, found {:?}",
2537                        std::any::type_name::<T>(),
2538                        entry.key.type_id
2539                    );
2540                }
2541
2542                entry.last_alive_epoch = epoch;
2543                if entry.value.is_none() {
2544                    let init_fn = init_opt
2545                        .take()
2546                        .expect("callback slot init called more than once");
2547                    entry.value = Some(Arc::new(init_fn()));
2548                    entry.generation = entry.generation.wrapping_add(1);
2549                }
2550
2551                (
2552                    entry.generation,
2553                    entry
2554                        .value
2555                        .as_ref()
2556                        .expect("callback slot must contain a value")
2557                        .clone(),
2558                )
2559            };
2560
2561            (
2562                value
2563                    .downcast::<T>()
2564                    .unwrap_or_else(|_| panic!("callback slot {:?} downcast failed", slot)),
2565                FunctorHandle::new(slot, generation),
2566            )
2567        } else if let Some(slot) = table.key_to_slot.get(&slot_key).copied() {
2568            table.record_slot_usage_slow(instance_logic_id, slot);
2569            let epoch = table.epoch;
2570            let (generation, value): (u64, Arc<dyn Any + Send + Sync>) = {
2571                let entry = table
2572                    .entries
2573                    .get_mut(slot)
2574                    .expect("functor slot entry should exist");
2575
2576                if entry.key.type_id != slot_key.type_id {
2577                    panic!(
2578                        "callback slot type mismatch: expected {}, found {:?}",
2579                        std::any::type_name::<T>(),
2580                        entry.key.type_id
2581                    );
2582                }
2583
2584                entry.last_alive_epoch = epoch;
2585                if entry.value.is_none() {
2586                    let init_fn = init_opt
2587                        .take()
2588                        .expect("callback slot init called more than once");
2589                    entry.value = Some(Arc::new(init_fn()));
2590                    entry.generation = entry.generation.wrapping_add(1);
2591                }
2592
2593                (
2594                    entry.generation,
2595                    entry
2596                        .value
2597                        .as_ref()
2598                        .expect("callback slot must contain a value")
2599                        .clone(),
2600                )
2601            };
2602
2603            (
2604                value
2605                    .downcast::<T>()
2606                    .unwrap_or_else(|_| panic!("callback slot {:?} downcast failed", slot)),
2607                FunctorHandle::new(slot, generation),
2608            )
2609        } else {
2610            let epoch = table.epoch;
2611            let init_fn = init_opt
2612                .take()
2613                .expect("callback slot init called more than once");
2614            let generation = 1u64;
2615            let slot = table.entries.insert(SlotEntry {
2616                key: slot_key,
2617                generation,
2618                value: Some(Arc::new(init_fn())),
2619                last_alive_epoch: epoch,
2620                retained: false,
2621            });
2622
2623            table.key_to_slot.insert(slot_key, slot);
2624            table.record_slot_usage_slow(instance_logic_id, slot);
2625
2626            let value = table
2627                .entries
2628                .get(slot)
2629                .expect("functor slot entry should exist")
2630                .value
2631                .as_ref()
2632                .expect("callback slot must contain a value")
2633                .clone()
2634                .downcast::<T>()
2635                .unwrap_or_else(|_| panic!("callback slot {:?} downcast failed", slot));
2636
2637            (value, FunctorHandle::new(slot, generation))
2638        }
2639    })
2640}
2641
2642fn load_functor_cell<T>(handle: FunctorHandle) -> Arc<T>
2643where
2644    T: Send + Sync + 'static,
2645{
2646    with_slot_table(|table| {
2647        let entry = table
2648            .entries
2649            .get(handle.slot)
2650            .unwrap_or_else(|| panic!("Callback points to freed slot: {:?}", handle.slot));
2651
2652        if entry.generation != handle.generation {
2653            panic!(
2654                "Callback is stale (slot {:?}, generation {}, current generation {})",
2655                handle.slot, handle.generation, entry.generation
2656            );
2657        }
2658
2659        if entry.key.type_id != TypeId::of::<T>() {
2660            panic!(
2661                "Callback type mismatch for slot {:?}: expected {}, stored {:?}",
2662                handle.slot,
2663                std::any::type_name::<T>(),
2664                entry.key.type_id
2665            );
2666        }
2667
2668        entry
2669            .value
2670            .as_ref()
2671            .unwrap_or_else(|| panic!("Callback slot {:?} has been cleared", handle.slot))
2672            .clone()
2673            .downcast::<T>()
2674            .unwrap_or_else(|_| panic!("Callback slot {:?} downcast failed", handle.slot))
2675    })
2676}
2677
2678pub(crate) fn remember_callback_handle<F>(handler: F) -> FunctorHandle
2679where
2680    F: Fn() + Send + Sync + 'static,
2681{
2682    let handler = Arc::new(handler) as Arc<dyn Fn() + Send + Sync>;
2683    let (cell, handle) = remember_functor_cell_with_key((), {
2684        let handler = Arc::clone(&handler);
2685        move || CallbackCell::new(handler)
2686    });
2687    cell.update(handler);
2688    handle
2689}
2690
2691pub(crate) fn invoke_callback_handle(handle: FunctorHandle) {
2692    let callback = load_functor_cell::<CallbackCell>(handle).shared();
2693    callback();
2694}
2695
2696pub(crate) fn remember_render_slot_handle<F>(render: F) -> FunctorHandle
2697where
2698    F: Fn() + Send + Sync + 'static,
2699{
2700    let render = Arc::new(render) as Arc<dyn Fn() + Send + Sync>;
2701    let creator_instance_key = current_replay_boundary_instance_key_from_scope()
2702        .unwrap_or_else(|| panic!("RenderSlot handles must be created during a component build"));
2703    let (cell, handle) = remember_functor_cell_with_key((), {
2704        let render = Arc::clone(&render);
2705        move || RenderSlotCell::new(render)
2706    });
2707    cell.update(render);
2708    for instance_key in render_slot_read_subscribers(handle) {
2709        if instance_key != creator_instance_key && !is_instance_key_build_dirty(instance_key) {
2710            record_replay_boundary_invalidation_for_instance_key(instance_key);
2711        }
2712    }
2713    handle
2714}
2715
2716pub(crate) fn invoke_render_slot_handle(handle: FunctorHandle) {
2717    let render = load_functor_cell::<RenderSlotCell>(handle).shared();
2718    render();
2719}
2720
2721pub(crate) fn remember_render_slot_with_handle<T, F>(render: F) -> FunctorHandle
2722where
2723    T: 'static,
2724    F: Fn(T) + Send + Sync + 'static,
2725{
2726    let render = Arc::new(render) as Arc<dyn Fn(T) + Send + Sync>;
2727    let creator_instance_key =
2728        current_replay_boundary_instance_key_from_scope().unwrap_or_else(|| {
2729            panic!("RenderSlotWith handles must be created during a component build")
2730        });
2731    let (cell, handle) = remember_functor_cell_with_key((), {
2732        let render = Arc::clone(&render);
2733        move || RenderSlotWithCell::new(render)
2734    });
2735    cell.update(render);
2736    for instance_key in render_slot_read_subscribers(handle) {
2737        if instance_key != creator_instance_key && !is_instance_key_build_dirty(instance_key) {
2738            record_replay_boundary_invalidation_for_instance_key(instance_key);
2739        }
2740    }
2741    handle
2742}
2743
2744pub(crate) fn invoke_render_slot_with_handle<T>(handle: FunctorHandle, value: T)
2745where
2746    T: 'static,
2747{
2748    let render = load_functor_cell::<RenderSlotWithCell<T>>(handle).shared();
2749    render(value)
2750}
2751
2752pub(crate) fn remember_callback_with_handle<T, R, F>(handler: F) -> FunctorHandle
2753where
2754    T: 'static,
2755    R: 'static,
2756    F: Fn(T) -> R + Send + Sync + 'static,
2757{
2758    let handler = Arc::new(handler) as Arc<dyn Fn(T) -> R + Send + Sync>;
2759    let (cell, handle) = remember_functor_cell_with_key((), {
2760        let handler = Arc::clone(&handler);
2761        move || CallbackWithCell::new(handler)
2762    });
2763    cell.update(handler);
2764    handle
2765}
2766
2767pub(crate) fn invoke_callback_with_handle<T, R>(handle: FunctorHandle, value: T) -> R
2768where
2769    T: 'static,
2770    R: 'static,
2771{
2772    let callback = load_functor_cell::<CallbackWithCell<T, R>>(handle).shared();
2773    callback(value)
2774}
2775
2776/// Start a new state-slot epoch for the current recomposition pass.
2777pub fn begin_recompose_slot_epoch() {
2778    with_slot_table_mut(SlotTable::begin_epoch);
2779}
2780
2781/// Reset all slot buffers (used on suspension).
2782pub fn reset_slots() {
2783    with_slot_table_mut(SlotTable::reset);
2784}
2785
2786pub(crate) fn recycle_recomposed_slots_for_instance_logic_ids(instance_logic_ids: &HashSet<u64>) {
2787    if instance_logic_ids.is_empty() {
2788        return;
2789    }
2790
2791    with_slot_table_mut(|table| {
2792        let epoch = table.epoch;
2793        let mut freed: Vec<(SlotHandle, SlotKey)> = Vec::new();
2794
2795        for (slot, entry) in table.entries.iter() {
2796            if !instance_logic_ids.contains(&entry.key.instance_logic_id) {
2797                continue;
2798            }
2799            if entry.last_alive_epoch == epoch || entry.retained {
2800                continue;
2801            }
2802            freed.push((slot, entry.key));
2803        }
2804
2805        for (slot, key) in freed {
2806            table.entries.remove(slot);
2807            table.key_to_slot.remove(&key);
2808        }
2809    });
2810}
2811
2812pub(crate) fn live_slot_instance_logic_ids() -> HashSet<u64> {
2813    with_slot_table(|table| {
2814        table
2815            .entries
2816            .iter()
2817            .map(|(_, entry)| entry.key.instance_logic_id)
2818            .collect()
2819    })
2820}
2821
2822/// Remember a value across frames with an explicit key.
2823///
2824/// This function allows a component to "remember" state across recomposition
2825/// (build) passes, using a user-provided key to identify the state. This is
2826/// particularly useful for state generated inside loops or dynamic collections
2827/// where the execution order might change.
2828///
2829/// The `init` closure is executed only once — when the key is first
2830/// encountered. On subsequent updates with the same key, the stored value is
2831/// returned and `init` is not called.
2832///
2833/// # Interior mutability
2834///
2835/// This function returns a `State<T>` handle that internally uses an
2836/// `Arc<RwLock<T>>`. Use `with`, `with_mut`, `get`, or `set` to read or update
2837/// the value without handling synchronization primitives directly.
2838///
2839/// # Comparison with [`remember`]
2840///
2841/// Use this function when the state is generated inside a loop or dynamic
2842/// collection where the execution order might change. In other cases,
2843/// [`remember`] is sufficient.
2844///
2845/// # Panics
2846///
2847/// This function must be called during a component's build/render phase.
2848/// Calling it during the measure or input handling phases will panic.
2849pub fn remember_with_key<K, F, T>(key: K, init: F) -> State<T>
2850where
2851    K: Hash,
2852    F: FnOnce() -> T,
2853    T: Send + Sync + 'static,
2854{
2855    ensure_build_phase();
2856    let (instance_logic_id, slot_hash) = compute_slot_key(&key);
2857    let type_id = TypeId::of::<T>();
2858    let slot_key = SlotKey {
2859        instance_logic_id,
2860        slot_hash,
2861        type_id,
2862    };
2863
2864    with_slot_table_mut(|table| {
2865        let mut init_opt = Some(init);
2866        if let Some(slot) = table.try_fast_slot_lookup(slot_key) {
2867            let epoch = table.epoch;
2868            let generation = {
2869                let entry = table
2870                    .entries
2871                    .get_mut(slot)
2872                    .expect("slot entry should exist");
2873
2874                if entry.key.type_id != slot_key.type_id {
2875                    panic!(
2876                        "remember_with_key type mismatch: expected {}, found {:?}",
2877                        std::any::type_name::<T>(),
2878                        entry.key.type_id
2879                    );
2880                }
2881
2882                entry.last_alive_epoch = epoch;
2883                if entry.value.is_none() {
2884                    let init_fn = init_opt
2885                        .take()
2886                        .expect("remember_with_key init called more than once");
2887                    entry.value = Some(Arc::new(RwLock::new(init_fn())));
2888                    entry.generation = entry.generation.wrapping_add(1);
2889                }
2890                entry.generation
2891            };
2892
2893            State::new(slot, generation)
2894        } else if let Some(slot) = table.key_to_slot.get(&slot_key).copied() {
2895            table.record_slot_usage_slow(instance_logic_id, slot);
2896            let epoch = table.epoch;
2897            let generation = {
2898                let entry = table
2899                    .entries
2900                    .get_mut(slot)
2901                    .expect("slot entry should exist");
2902
2903                if entry.key.type_id != slot_key.type_id {
2904                    panic!(
2905                        "remember_with_key type mismatch: expected {}, found {:?}",
2906                        std::any::type_name::<T>(),
2907                        entry.key.type_id
2908                    );
2909                }
2910
2911                entry.last_alive_epoch = epoch;
2912                if entry.value.is_none() {
2913                    let init_fn = init_opt
2914                        .take()
2915                        .expect("remember_with_key init called more than once");
2916                    entry.value = Some(Arc::new(RwLock::new(init_fn())));
2917                    entry.generation = entry.generation.wrapping_add(1);
2918                }
2919                entry.generation
2920            };
2921
2922            State::new(slot, generation)
2923        } else {
2924            let epoch = table.epoch;
2925            let init_fn = init_opt
2926                .take()
2927                .expect("remember_with_key init called more than once");
2928            let generation = 1u64;
2929            let slot = table.entries.insert(SlotEntry {
2930                key: slot_key,
2931                generation,
2932                value: Some(Arc::new(RwLock::new(init_fn()))),
2933                last_alive_epoch: epoch,
2934                retained: false,
2935            });
2936
2937            table.key_to_slot.insert(slot_key, slot);
2938            table.record_slot_usage_slow(instance_logic_id, slot);
2939            State::new(slot, generation)
2940        }
2941    })
2942}
2943
2944/// Remember a value across recomposition (build) passes.
2945///
2946/// This function allows a component to "remember" state across recomposition
2947/// (build) passes.
2948/// The `init` closure is executed only once — when the component first runs.
2949/// On subsequent updates, the stored value is returned and `init` is not
2950/// called.
2951///
2952/// # Interior mutability
2953///
2954/// This function returns a `State<T>` handle that internally uses an
2955/// `Arc<RwLock<T>>`. Use `with`, `with_mut`, `get`, or `set` to read or update
2956/// the value without handling synchronization primitives directly.
2957///
2958/// # Comparison with [`remember_with_key`]
2959///
2960/// `remember` identifies stored state based on the component's call order and
2961/// control-flow path. It associates state by position within a component, but
2962/// this does not work reliably for dynamically generated state inside loops.
2963/// For state that is allocated dynamically in loops, consider using
2964/// [`remember_with_key`] to explicitly provide a unique key.
2965///
2966/// # Panics
2967///
2968/// This function must be called during a component's build/render phase.
2969/// Calling it during the measure or input handling phases will panic.
2970pub fn remember<F, T>(init: F) -> State<T>
2971where
2972    F: FnOnce() -> T,
2973    T: Send + Sync + 'static,
2974{
2975    remember_with_key((), init)
2976}
2977
2978/// Retain a value across recomposition (build) passes with an explicit key,
2979/// even if unused.
2980///
2981/// Unlike [`remember_with_key`], state created with this function will **not**
2982/// be recycled when the component stops calling it. This is useful for state
2983/// that should persist across navigation, such as scroll positions or form
2984/// inputs.
2985///
2986/// The `init` closure is executed only once — when the key is first
2987/// encountered. On subsequent updates with the same key, the stored value is
2988/// returned and `init` is not called.
2989///
2990/// # Use Cases
2991///
2992/// - Preserving scroll position when navigating away and returning to a page
2993/// - Retaining form input values across route changes
2994/// - Caching expensive computation results that should survive component
2995///   unmounts
2996///
2997/// # Interior mutability
2998///
2999/// This function returns a `State<T>` handle that internally uses an
3000/// `Arc<RwLock<T>>`. Use `with`, `with_mut`, `get`, or `set` to read or update
3001/// the value without handling synchronization primitives directly.
3002///
3003/// # Comparison with [`remember_with_key`]
3004///
3005/// Use [`remember_with_key`] for ephemeral component state that should be
3006/// cleaned up when the component is no longer rendered. Use `retain_with_key`
3007/// for persistent state that must survive even when a subtree is not rebuilt
3008/// for some time.
3009///
3010/// # Panics
3011///
3012/// This function must be called during a component's build/render phase.
3013/// Calling it during the measure or input handling phases will panic.
3014pub fn retain_with_key<K, F, T>(key: K, init: F) -> State<T>
3015where
3016    K: Hash,
3017    F: FnOnce() -> T,
3018    T: Send + Sync + 'static,
3019{
3020    ensure_build_phase();
3021    let (instance_logic_id, slot_hash) = compute_slot_key(&key);
3022    let type_id = TypeId::of::<T>();
3023    let slot_key = SlotKey {
3024        instance_logic_id,
3025        slot_hash,
3026        type_id,
3027    };
3028
3029    with_slot_table_mut(|table| {
3030        let mut init_opt = Some(init);
3031        if let Some(slot) = table.try_fast_slot_lookup(slot_key) {
3032            let epoch = table.epoch;
3033            let generation = {
3034                let entry = table
3035                    .entries
3036                    .get_mut(slot)
3037                    .expect("slot entry should exist");
3038
3039                if entry.key.type_id != slot_key.type_id {
3040                    panic!(
3041                        "retain_with_key type mismatch: expected {}, found {:?}",
3042                        std::any::type_name::<T>(),
3043                        entry.key.type_id
3044                    );
3045                }
3046
3047                entry.last_alive_epoch = epoch;
3048                entry.retained = true;
3049                if entry.value.is_none() {
3050                    let init_fn = init_opt
3051                        .take()
3052                        .expect("retain_with_key init called more than once");
3053                    entry.value = Some(Arc::new(RwLock::new(init_fn())));
3054                    entry.generation = entry.generation.wrapping_add(1);
3055                }
3056
3057                entry.generation
3058            };
3059
3060            State::new(slot, generation)
3061        } else if let Some(slot) = table.key_to_slot.get(&slot_key).copied() {
3062            table.record_slot_usage_slow(instance_logic_id, slot);
3063            let epoch = table.epoch;
3064            let generation = {
3065                let entry = table
3066                    .entries
3067                    .get_mut(slot)
3068                    .expect("slot entry should exist");
3069
3070                if entry.key.type_id != slot_key.type_id {
3071                    panic!(
3072                        "retain_with_key type mismatch: expected {}, found {:?}",
3073                        std::any::type_name::<T>(),
3074                        entry.key.type_id
3075                    );
3076                }
3077
3078                entry.last_alive_epoch = epoch;
3079                entry.retained = true;
3080                if entry.value.is_none() {
3081                    let init_fn = init_opt
3082                        .take()
3083                        .expect("retain_with_key init called more than once");
3084                    entry.value = Some(Arc::new(RwLock::new(init_fn())));
3085                    entry.generation = entry.generation.wrapping_add(1);
3086                }
3087
3088                entry.generation
3089            };
3090
3091            State::new(slot, generation)
3092        } else {
3093            let epoch = table.epoch;
3094            let init_fn = init_opt
3095                .take()
3096                .expect("retain_with_key init called more than once");
3097            let generation = 1u64;
3098            let slot = table.entries.insert(SlotEntry {
3099                key: slot_key,
3100                generation,
3101                value: Some(Arc::new(RwLock::new(init_fn()))),
3102                last_alive_epoch: epoch,
3103                retained: true,
3104            });
3105
3106            table.key_to_slot.insert(slot_key, slot);
3107            table.record_slot_usage_slow(instance_logic_id, slot);
3108            State::new(slot, generation)
3109        }
3110    })
3111}
3112
3113/// Retain a value across recomposition (build) passes, even if unused.
3114///
3115/// Unlike [`remember`], state created with this function will **not** be
3116/// recycled when the component stops calling it. This is useful for state that
3117/// should persist across navigation, such as scroll positions or form inputs.
3118///
3119/// The `init` closure is executed only once — when the component first runs.
3120/// On subsequent updates, the stored value is returned and `init` is not
3121/// called.
3122///
3123/// # Use Cases
3124///
3125/// - Preserving scroll position when navigating away and returning to a page
3126/// - Retaining form input values across route changes
3127/// - Caching expensive computation results that should survive component
3128///   unmounts
3129///
3130/// # Interior mutability
3131///
3132/// This function returns a `State<T>` handle that internally uses an
3133/// `Arc<RwLock<T>>`. Use `with`, `with_mut`, `get`, or `set` to read or update
3134/// the value without handling synchronization primitives directly.
3135///
3136/// # Comparison with [`retain_with_key`]
3137///
3138/// `retain` identifies stored state based on the component's call order and
3139/// control-flow path. It associates state by position within a component, but
3140/// this does not work reliably for dynamically generated state inside loops.
3141/// For state that is allocated dynamically in loops, consider using
3142/// [`retain_with_key`] to explicitly provide a unique key.
3143///
3144/// # Panics
3145///
3146/// This function must be called during a component's build/render phase.
3147/// Calling it during the measure or input handling phases will panic.
3148pub fn retain<F, T>(init: F) -> State<T>
3149where
3150    F: FnOnce() -> T,
3151    T: Send + Sync + 'static,
3152{
3153    retain_with_key((), init)
3154}
3155
3156/// Groups the execution of a block of code with a stable key.
3157///
3158/// This is useful for maintaining state identity in dynamic lists or loops
3159/// where the order of items might change.
3160///
3161/// # Examples
3162///
3163/// ```
3164/// use tessera_ui::{key, remember, tessera};
3165///
3166/// #[tessera]
3167/// fn my_list(items: Vec<String>) {
3168///     for item in items.iter() {
3169///         key(item.clone(), || {
3170///             let state = remember(|| 0);
3171///         });
3172///     }
3173/// }
3174/// ```
3175pub fn key<K, F, R>(key: K, block: F) -> R
3176where
3177    K: Hash,
3178    F: FnOnce() -> R,
3179{
3180    let key_hash = hash_components(&[&key]);
3181    let _group_guard = GroupGuard::new(key_hash);
3182    let _instance_guard = InstanceKeyGuard::new(key_hash);
3183    block()
3184}
3185
3186#[cfg(test)]
3187mod tests {
3188    use std::sync::{
3189        Arc,
3190        atomic::{AtomicU64, AtomicUsize, Ordering},
3191    };
3192
3193    use super::*;
3194    use crate::execution_context::{
3195        reset_execution_context, with_execution_context, with_execution_context_mut,
3196    };
3197    use crate::layout::{LayoutPolicy, MeasureScope};
3198    use crate::modifier::{LayoutModifierChild, LayoutModifierInput, LayoutModifierNode};
3199    use crate::prop::{
3200        Callback, CallbackWith, ComponentReplayData, RenderSlot, make_component_runner,
3201    };
3202    use crate::tessera;
3203
3204    #[tessera(crate)]
3205    fn required_value_component(value: Option<usize>, on_value: Option<CallbackWith<usize>>) {
3206        let value = value.expect("missing required prop `value` in `required_value_component`");
3207        if let Some(on_value) = on_value {
3208            on_value.call(value);
3209        }
3210    }
3211
3212    #[tessera(crate)]
3213    fn optional_value_component(
3214        value: Option<usize>,
3215        on_value: Option<CallbackWith<Option<usize>>>,
3216    ) {
3217        if let Some(on_value) = on_value {
3218            on_value.call(value);
3219        }
3220    }
3221
3222    #[tessera(crate)]
3223    fn defaulted_value_component(value: Option<usize>, on_value: Option<CallbackWith<usize>>) {
3224        let value = value.unwrap_or(7);
3225        if let Some(on_value) = on_value {
3226            on_value.call(value);
3227        }
3228    }
3229
3230    fn panic_message(err: Box<dyn std::any::Any + Send>) -> String {
3231        match err.downcast::<String>() {
3232            Ok(message) => *message,
3233            Err(err) => match err.downcast::<&'static str>() {
3234                Ok(message) => (*message).to_string(),
3235                Err(_) => "<non-string panic>".to_string(),
3236            },
3237        }
3238    }
3239
3240    fn seed_previous_replay_snapshot(instance_key: u64) {
3241        let replay = ComponentReplayData::new(make_component_runner::<()>(|_| {}), &());
3242        with_component_replay_tracker_mut(|tracker| {
3243            tracker.previous_nodes.insert(
3244                instance_key,
3245                ReplayNodeSnapshot {
3246                    instance_key,
3247                    parent_instance_key: None,
3248                    instance_logic_id: 0,
3249                    group_path: Vec::new(),
3250                    instance_key_override: None,
3251                    fn_name: "test_component".to_string(),
3252                    replay,
3253                },
3254            );
3255        });
3256    }
3257
3258    fn with_test_component_scope<R>(component_type_id: u64, f: impl FnOnce() -> R) -> R {
3259        reset_execution_context();
3260        let mut arena = crate::Arena::<()>::new();
3261        let node_id = arena.new_node(());
3262        let _phase_guard = push_phase(RuntimePhase::Build);
3263        let _node_guard = push_current_node(node_id, component_type_id, "test_component");
3264        let _instance_guard = push_current_component_instance_key(current_instance_key());
3265        f()
3266    }
3267
3268    #[test]
3269    fn frame_receiver_uses_component_scope_instance_key() {
3270        let _instance_guard = push_current_component_instance_key(7);
3271        assert_eq!(current_replay_boundary_instance_key_from_scope(), Some(7));
3272    }
3273
3274    #[test]
3275    fn receive_frame_nanos_panics_without_component_scope() {
3276        reset_frame_clock();
3277        begin_frame_clock(Instant::now());
3278
3279        let result = std::panic::catch_unwind(|| {
3280            receive_frame_nanos(|_| FrameNanosControl::Continue);
3281        });
3282        assert!(result.is_err());
3283    }
3284
3285    #[test]
3286    fn receive_frame_nanos_panics_in_input_phase() {
3287        let _phase_guard = push_phase(RuntimePhase::Input);
3288        let result = std::panic::catch_unwind(|| {
3289            receive_frame_nanos(|_| FrameNanosControl::Continue);
3290        });
3291        assert!(result.is_err());
3292    }
3293
3294    #[test]
3295    fn tick_frame_nanos_receivers_removes_stopped_receivers() {
3296        reset_frame_clock();
3297        begin_frame_clock(Instant::now());
3298
3299        with_frame_clock_tracker_mut(|tracker| {
3300            tracker.receivers.insert(
3301                FrameNanosReceiverKey {
3302                    instance_logic_id: 1,
3303                    receiver_hash: 1,
3304                },
3305                FrameNanosReceiver {
3306                    owner_instance_key: 123,
3307                    callback: Box::new(|_| FrameNanosControl::Stop),
3308                },
3309            );
3310        });
3311
3312        tick_frame_nanos_receivers();
3313        assert!(with_frame_clock_tracker(|tracker| tracker
3314            .receivers
3315            .is_empty()));
3316    }
3317
3318    #[test]
3319    fn with_build_dirty_instance_keys_marks_current_scope() {
3320        let mut outer = HashSet::default();
3321        outer.insert(7);
3322
3323        assert!(!is_instance_key_build_dirty(7));
3324        with_build_dirty_instance_keys(&outer, || {
3325            assert!(is_instance_key_build_dirty(7));
3326            assert!(!is_instance_key_build_dirty(8));
3327
3328            let mut inner = HashSet::default();
3329            inner.insert(8);
3330            with_build_dirty_instance_keys(&inner, || {
3331                assert!(!is_instance_key_build_dirty(7));
3332                assert!(is_instance_key_build_dirty(8));
3333            });
3334
3335            assert!(is_instance_key_build_dirty(7));
3336            assert!(!is_instance_key_build_dirty(8));
3337        });
3338        assert!(!is_instance_key_build_dirty(7));
3339    }
3340
3341    #[test]
3342    fn with_build_dirty_instance_keys_restores_on_panic() {
3343        let mut dirty = HashSet::default();
3344        dirty.insert(11);
3345
3346        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3347            with_build_dirty_instance_keys(&dirty, || {
3348                assert!(is_instance_key_build_dirty(11));
3349                panic!("expected panic");
3350            });
3351        }));
3352        assert!(result.is_err());
3353        assert!(!is_instance_key_build_dirty(11));
3354    }
3355
3356    #[derive(Clone, PartialEq)]
3357    struct DirtySplitPolicy {
3358        measure_key: u32,
3359        placement_key: u32,
3360    }
3361
3362    impl LayoutPolicy for DirtySplitPolicy {
3363        fn measure(
3364            &self,
3365            _input: &MeasureScope<'_>,
3366        ) -> Result<crate::LayoutResult, crate::MeasurementError> {
3367            Ok(crate::LayoutResult::new(crate::ComputedData::ZERO))
3368        }
3369
3370        fn measure_eq(&self, other: &Self) -> bool {
3371            self.measure_key == other.measure_key
3372        }
3373
3374        fn placement_eq(&self, other: &Self) -> bool {
3375            self.placement_key == other.placement_key
3376        }
3377    }
3378
3379    #[derive(Clone, Copy, PartialEq)]
3380    struct DirtyMeasureModifierNode {
3381        width: u32,
3382    }
3383
3384    impl LayoutModifierNode for DirtyMeasureModifierNode {
3385        fn measure(
3386            &self,
3387            _input: &LayoutModifierInput<'_>,
3388            child: &mut dyn LayoutModifierChild,
3389        ) -> Result<crate::LayoutModifierOutput, crate::MeasurementError> {
3390            let size = child.measure(&crate::Constraint::exact(
3391                crate::Px::new(self.width as i32),
3392                crate::Px::new(10),
3393            ))?;
3394            child.place(crate::PxPosition::ZERO);
3395            Ok(crate::LayoutModifierOutput { size })
3396        }
3397    }
3398
3399    #[test]
3400    fn layout_dirty_tracking_separates_measure_and_placement_changes() {
3401        reset_layout_dirty_tracking();
3402
3403        begin_frame_layout_dirty_tracking();
3404        {
3405            let _phase_guard = push_phase(RuntimePhase::Build);
3406            record_layout_policy_dirty(
3407                1,
3408                &DirtySplitPolicy {
3409                    measure_key: 0,
3410                    placement_key: 0,
3411                },
3412                &Modifier::new(),
3413            );
3414        }
3415        finalize_frame_layout_dirty_tracking();
3416        let dirty = take_layout_dirty_nodes();
3417        assert!(dirty.measure_self_nodes.contains(&1));
3418        assert!(dirty.placement_self_nodes.is_empty());
3419
3420        begin_frame_layout_dirty_tracking();
3421        {
3422            let _phase_guard = push_phase(RuntimePhase::Build);
3423            record_layout_policy_dirty(
3424                1,
3425                &DirtySplitPolicy {
3426                    measure_key: 0,
3427                    placement_key: 1,
3428                },
3429                &Modifier::new(),
3430            );
3431        }
3432        finalize_frame_layout_dirty_tracking();
3433        let dirty = take_layout_dirty_nodes();
3434        assert!(!dirty.measure_self_nodes.contains(&1));
3435        assert!(dirty.placement_self_nodes.contains(&1));
3436
3437        begin_frame_layout_dirty_tracking();
3438        {
3439            let _phase_guard = push_phase(RuntimePhase::Build);
3440            record_layout_policy_dirty(
3441                1,
3442                &DirtySplitPolicy {
3443                    measure_key: 1,
3444                    placement_key: 1,
3445                },
3446                &Modifier::new(),
3447            );
3448        }
3449        finalize_frame_layout_dirty_tracking();
3450        let dirty = take_layout_dirty_nodes();
3451        assert!(dirty.measure_self_nodes.contains(&1));
3452        assert!(!dirty.placement_self_nodes.contains(&1));
3453    }
3454
3455    #[test]
3456    fn layout_dirty_tracking_marks_measure_when_layout_modifier_changes() {
3457        reset_layout_dirty_tracking();
3458
3459        begin_frame_layout_dirty_tracking();
3460        {
3461            let _phase_guard = push_phase(RuntimePhase::Build);
3462            let modifier = Modifier::new().push_layout(DirtyMeasureModifierNode { width: 10 });
3463            record_layout_policy_dirty(
3464                1,
3465                &DirtySplitPolicy {
3466                    measure_key: 0,
3467                    placement_key: 0,
3468                },
3469                &modifier,
3470            );
3471        }
3472        finalize_frame_layout_dirty_tracking();
3473        let _ = take_layout_dirty_nodes();
3474
3475        begin_frame_layout_dirty_tracking();
3476        {
3477            let _phase_guard = push_phase(RuntimePhase::Build);
3478            let modifier = Modifier::new().push_layout(DirtyMeasureModifierNode { width: 20 });
3479            record_layout_policy_dirty(
3480                1,
3481                &DirtySplitPolicy {
3482                    measure_key: 0,
3483                    placement_key: 0,
3484                },
3485                &modifier,
3486            );
3487        }
3488        finalize_frame_layout_dirty_tracking();
3489        let dirty = take_layout_dirty_nodes();
3490        assert!(dirty.measure_self_nodes.contains(&1));
3491        assert!(!dirty.placement_self_nodes.contains(&1));
3492    }
3493
3494    #[test]
3495    fn with_replay_scope_restores_group_path_and_override() {
3496        with_execution_context_mut(|context| {
3497            context.group_path_stack = vec![1, 2, 3];
3498        });
3499        with_execution_context_mut(|context| {
3500            context.instance_key_stack = vec![5];
3501        });
3502        with_execution_context_mut(|context| {
3503            context.next_node_instance_logic_id_override = Some(9);
3504        });
3505
3506        with_replay_scope(42, &[7, 8], Some(11), || {
3507            assert_eq!(current_group_path(), vec![7, 8]);
3508            assert_eq!(current_instance_key_override(), Some(11));
3509            assert_eq!(take_next_node_instance_logic_id_override(), Some(42));
3510            assert_eq!(take_next_node_instance_logic_id_override(), None);
3511        });
3512
3513        assert_eq!(current_group_path(), vec![1, 2, 3]);
3514        assert_eq!(current_instance_key_override(), Some(5));
3515        let restored_override =
3516            with_execution_context(|context| context.next_node_instance_logic_id_override);
3517        assert_eq!(restored_override, Some(9));
3518    }
3519
3520    #[test]
3521    fn with_replay_scope_restores_on_panic() {
3522        with_execution_context_mut(|context| {
3523            context.group_path_stack = vec![5];
3524        });
3525        with_execution_context_mut(|context| {
3526            context.instance_key_stack = vec![13];
3527        });
3528        with_execution_context_mut(|context| {
3529            context.next_node_instance_logic_id_override = None;
3530        });
3531
3532        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3533            with_replay_scope(77, &[10], Some(17), || {
3534                assert_eq!(current_group_path(), vec![10]);
3535                assert_eq!(current_instance_key_override(), Some(17));
3536                panic!("expected panic");
3537            });
3538        }));
3539        assert!(result.is_err());
3540
3541        assert_eq!(current_group_path(), vec![5]);
3542        assert_eq!(current_instance_key_override(), Some(13));
3543        let restored_override =
3544            with_execution_context(|context| context.next_node_instance_logic_id_override);
3545        assert_eq!(restored_override, None);
3546    }
3547
3548    #[test]
3549    fn group_local_remember_does_not_shift_following_slots() {
3550        reset_slots();
3551
3552        begin_recompose_slot_epoch();
3553        with_test_component_scope(1001, || {
3554            let stable_state = remember(|| 1usize);
3555            stable_state.set(41);
3556        });
3557
3558        begin_recompose_slot_epoch();
3559        with_test_component_scope(1001, || {
3560            {
3561                let _group_guard = GroupGuard::new(7);
3562                let _branch_state = remember(|| 10usize);
3563            }
3564            let stable_state = remember(|| 1usize);
3565            assert_eq!(stable_state.get(), 41);
3566        });
3567    }
3568
3569    #[test]
3570    fn conditional_frame_receiver_does_not_shift_following_remember_slots() {
3571        reset_slots();
3572        reset_frame_clock();
3573        begin_frame_clock(Instant::now());
3574
3575        begin_recompose_slot_epoch();
3576        with_test_component_scope(1002, || {
3577            let stable_state = remember(|| 1usize);
3578            stable_state.set(99);
3579        });
3580
3581        begin_recompose_slot_epoch();
3582        with_test_component_scope(1002, || {
3583            {
3584                let _group_guard = GroupGuard::new(9);
3585                receive_frame_nanos(|_| FrameNanosControl::Stop);
3586            }
3587            let stable_state = remember(|| 1usize);
3588            assert_eq!(stable_state.get(), 99);
3589        });
3590    }
3591
3592    #[test]
3593    fn callback_handle_stays_stable_and_invokes_latest_closure() {
3594        reset_slots();
3595
3596        let calls = Arc::new(AtomicUsize::new(0));
3597
3598        begin_recompose_slot_epoch();
3599        let first = with_test_component_scope(11001, || {
3600            let calls = Arc::clone(&calls);
3601            Callback::new(move || {
3602                calls.store(1, Ordering::SeqCst);
3603            })
3604        });
3605
3606        begin_recompose_slot_epoch();
3607        let second = with_test_component_scope(11001, || {
3608            let calls = Arc::clone(&calls);
3609            Callback::new(move || {
3610                calls.store(2, Ordering::SeqCst);
3611            })
3612        });
3613
3614        assert!(first == second);
3615        first.call();
3616        assert_eq!(calls.load(Ordering::SeqCst), 2);
3617    }
3618
3619    #[test]
3620    fn tessera_panics_when_required_prop_is_missing() {
3621        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3622            with_test_component_scope(11013, || {
3623                required_value_component();
3624            });
3625        }));
3626
3627        let message = panic_message(result.expect_err("missing required prop should panic"));
3628        assert!(message.contains("missing required prop `value`"));
3629        assert!(message.contains("required_value_component"));
3630    }
3631
3632    #[test]
3633    fn tessera_uses_none_for_omitted_option_props() {
3634        let observed = Arc::new(AtomicUsize::new(99));
3635
3636        with_test_component_scope(11014, || {
3637            let observed = Arc::clone(&observed);
3638            optional_value_component().on_value(move |value: Option<usize>| {
3639                observed.store(
3640                    value.map_or(0, |next| next.saturating_add(1)),
3641                    Ordering::SeqCst,
3642                );
3643            });
3644        });
3645
3646        assert_eq!(observed.load(Ordering::SeqCst), 0);
3647    }
3648
3649    #[test]
3650    fn tessera_uses_function_body_default_for_omitted_option_props() {
3651        let observed = Arc::new(AtomicUsize::new(0));
3652
3653        with_test_component_scope(11015, || {
3654            let observed = Arc::clone(&observed);
3655            defaulted_value_component().on_value(move |value: usize| {
3656                observed.store(value, Ordering::SeqCst);
3657            });
3658        });
3659
3660        assert_eq!(observed.load(Ordering::SeqCst), 7);
3661    }
3662
3663    #[test]
3664    fn tessera_accepts_non_default_required_props_when_provided() {
3665        let observed = Arc::new(AtomicUsize::new(0));
3666
3667        with_test_component_scope(11016, || {
3668            let observed = Arc::clone(&observed);
3669            required_value_component()
3670                .value(13)
3671                .on_value(move |value: usize| {
3672                    observed.store(value, Ordering::SeqCst);
3673                });
3674        });
3675
3676        assert_eq!(observed.load(Ordering::SeqCst), 13);
3677    }
3678
3679    #[test]
3680    fn render_slot_update_invalidates_reader_instance() {
3681        reset_slots();
3682        reset_component_replay_tracking();
3683        reset_render_slot_read_dependencies();
3684        reset_build_invalidations();
3685
3686        let reader_boundary_key = Arc::new(AtomicU64::new(0));
3687
3688        begin_recompose_slot_epoch();
3689        let first = with_test_component_scope(11002, || {
3690            let reader_boundary_key = Arc::clone(&reader_boundary_key);
3691            RenderSlot::new(move || {
3692                let instance_key = current_replay_boundary_instance_key_from_scope()
3693                    .expect("slot render must run inside replay boundary");
3694                reader_boundary_key.store(instance_key, Ordering::SeqCst);
3695            })
3696        });
3697
3698        with_test_component_scope(11003, || {
3699            first.render();
3700        });
3701        let reader_instance_key = reader_boundary_key.load(Ordering::SeqCst);
3702        assert!(reader_instance_key != 0);
3703        seed_previous_replay_snapshot(reader_instance_key);
3704
3705        assert!(!has_pending_build_invalidations());
3706
3707        begin_recompose_slot_epoch();
3708        let second = with_test_component_scope(11002, || RenderSlot::new(|| {}));
3709
3710        assert!(first == second);
3711        assert!(has_pending_build_invalidations());
3712
3713        let invalidations = take_build_invalidations();
3714        let mut expected = HashSet::default();
3715        expected.insert(reader_instance_key);
3716        assert_eq!(invalidations.dirty_instance_keys, expected);
3717    }
3718
3719    #[test]
3720    fn render_slot_update_skips_reader_already_dirty_in_current_build() {
3721        reset_slots();
3722        reset_component_replay_tracking();
3723        reset_render_slot_read_dependencies();
3724        reset_build_invalidations();
3725        reset_execution_context();
3726
3727        let reader_boundary_key = Arc::new(AtomicU64::new(0));
3728
3729        begin_recompose_slot_epoch();
3730        let first = with_test_component_scope(12002, || {
3731            let reader_boundary_key = Arc::clone(&reader_boundary_key);
3732            RenderSlot::new(move || {
3733                let instance_key = current_replay_boundary_instance_key_from_scope()
3734                    .expect("slot render must run inside replay boundary");
3735                reader_boundary_key.store(instance_key, Ordering::SeqCst);
3736            })
3737        });
3738
3739        with_test_component_scope(12003, || {
3740            first.render();
3741        });
3742        let reader_instance_key = reader_boundary_key.load(Ordering::SeqCst);
3743        assert!(reader_instance_key != 0);
3744        seed_previous_replay_snapshot(reader_instance_key);
3745
3746        assert!(!has_pending_build_invalidations());
3747
3748        begin_recompose_slot_epoch();
3749        let mut dirty = HashSet::default();
3750        dirty.insert(reader_instance_key);
3751        with_build_dirty_instance_keys(&dirty, || {
3752            let mut arena = crate::Arena::<()>::new();
3753            let node_id = arena.new_node(());
3754            let _phase_guard = push_phase(RuntimePhase::Build);
3755            let _node_guard = push_current_node(node_id, 12002, "test_component");
3756            let _instance_guard = push_current_component_instance_key(current_instance_key());
3757            let second = RenderSlot::new(|| {});
3758            assert!(first == second);
3759        });
3760
3761        assert!(!has_pending_build_invalidations());
3762    }
3763
3764    #[test]
3765    fn group_local_child_identity_does_not_shift_following_siblings() {
3766        fn stable_child_instance_logic_id(with_group_child: bool) -> u64 {
3767            let mut arena = crate::Arena::<()>::new();
3768            let root_node = arena.new_node(());
3769            let stable_child_node = arena.new_node(());
3770            let group_child_node = arena.new_node(());
3771
3772            let _phase_guard = push_phase(RuntimePhase::Build);
3773            let _root_guard = push_current_node(root_node, 2001, "root_component");
3774            let _root_instance_guard = push_current_component_instance_key(current_instance_key());
3775
3776            if with_group_child {
3777                let _group_guard = GroupGuard::new(5);
3778                let _group_child_guard =
3779                    push_current_node(group_child_node, 2002, "group_child_component");
3780                let _group_child_instance_guard =
3781                    push_current_component_instance_key(current_instance_key());
3782                let _ = current_instance_logic_id();
3783            }
3784
3785            let _stable_child_guard =
3786                push_current_node(stable_child_node, 2003, "stable_child_component");
3787            current_instance_logic_id()
3788        }
3789
3790        assert_eq!(
3791            stable_child_instance_logic_id(false),
3792            stable_child_instance_logic_id(true)
3793        );
3794    }
3795
3796    #[test]
3797    fn child_components_in_different_groups_get_distinct_instance_logic_ids() {
3798        let mut arena = crate::Arena::<()>::new();
3799        let root_node = arena.new_node(());
3800        let first_child_node = arena.new_node(());
3801        let second_child_node = arena.new_node(());
3802
3803        let _phase_guard = push_phase(RuntimePhase::Build);
3804        let _root_guard = push_current_node(root_node, 3001, "root_component");
3805        let _root_instance_guard = push_current_component_instance_key(current_instance_key());
3806
3807        let first_id = {
3808            let _group_guard = GroupGuard::new(11);
3809            let _child_guard = push_current_node(first_child_node, 3002, "grouped_child");
3810            current_instance_logic_id()
3811        };
3812
3813        let second_id = {
3814            let _group_guard = GroupGuard::new(12);
3815            let _child_guard = push_current_node(second_child_node, 3002, "grouped_child");
3816            current_instance_logic_id()
3817        };
3818
3819        assert_ne!(first_id, second_id);
3820    }
3821
3822    #[test]
3823    fn child_components_in_repeated_path_groups_keep_distinct_instance_logic_ids() {
3824        let mut arena = crate::Arena::<()>::new();
3825        let root_node = arena.new_node(());
3826        let first_child_node = arena.new_node(());
3827        let second_child_node = arena.new_node(());
3828
3829        let _phase_guard = push_phase(RuntimePhase::Build);
3830        let _root_guard = push_current_node(root_node, 4001, "root_component");
3831        let _root_instance_guard = push_current_component_instance_key(current_instance_key());
3832
3833        let first_id = {
3834            let _group_guard = PathGroupGuard::new(21);
3835            let _child_guard = push_current_node(first_child_node, 4002, "loop_child");
3836            current_instance_logic_id()
3837        };
3838
3839        let second_id = {
3840            let _group_guard = PathGroupGuard::new(21);
3841            let _child_guard = push_current_node(second_child_node, 4002, "loop_child");
3842            current_instance_logic_id()
3843        };
3844
3845        assert_ne!(first_id, second_id);
3846    }
3847
3848    #[test]
3849    fn drop_slots_for_instance_logic_ids_keeps_retained_entries() {
3850        let mut table = SlotTable::default();
3851        let keep_key = SlotKey {
3852            instance_logic_id: 7,
3853            slot_hash: 11,
3854            type_id: TypeId::of::<i32>(),
3855        };
3856        let drop_key = SlotKey {
3857            instance_logic_id: 7,
3858            slot_hash: 12,
3859            type_id: TypeId::of::<i32>(),
3860        };
3861
3862        let keep_slot = table.entries.insert(SlotEntry {
3863            key: keep_key,
3864            generation: 1,
3865            value: Some(Arc::new(RwLock::new(10_i32))),
3866            last_alive_epoch: 0,
3867            retained: true,
3868        });
3869        let drop_slot = table.entries.insert(SlotEntry {
3870            key: drop_key,
3871            generation: 1,
3872            value: Some(Arc::new(RwLock::new(20_i32))),
3873            last_alive_epoch: 0,
3874            retained: false,
3875        });
3876        table.key_to_slot.insert(keep_key, keep_slot);
3877        table.key_to_slot.insert(drop_key, drop_slot);
3878        with_slot_table_mut(|slot_table| *slot_table = table);
3879
3880        let mut stale = HashSet::default();
3881        stale.insert(7_u64);
3882        drop_slots_for_instance_logic_ids(&stale);
3883
3884        with_slot_table(|table| {
3885            assert!(table.entries.get(keep_slot).is_some());
3886            assert!(table.key_to_slot.contains_key(&keep_key));
3887            assert!(table.entries.get(drop_slot).is_none());
3888            assert!(!table.key_to_slot.contains_key(&drop_key));
3889        });
3890    }
3891}