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