1use crate::collections::map::HashMap;
2use crate::collections::map::HashSet;
3use crate::state::{MutationPolicy, NeverEqual};
4use crate::MutableStateInner;
5use std::any::Any;
6use std::cell::{Cell, RefCell};
7use std::collections::VecDeque;
8use std::future::Future;
9use std::pin::Pin;
10use std::rc::{Rc, Weak};
11use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
12use std::sync::{mpsc, Arc};
13use std::task::{Context, Poll, Waker};
14use std::thread::ThreadId;
15use std::thread_local;
16
17#[cfg(any(feature = "internal", test))]
18use crate::frame_clock::FrameClock;
19use crate::platform::RuntimeScheduler;
20use crate::{Applier, Command, FrameCallbackId, NodeError, RecomposeScopeInner, ScopeId};
21
22#[derive(Clone, Copy, PartialEq, Eq)]
23pub(crate) enum FrameCallbackKind {
24 Transient,
25 Perpetual,
26}
27
28enum UiMessage {
29 Task(Box<dyn FnOnce() + Send + 'static>),
30 Invoke { id: u64, value: Box<dyn Any + Send> },
31}
32
33type UiContinuation = Box<dyn Fn(Box<dyn Any>) -> bool + 'static>;
34type UiContinuationMap = HashMap<u64, UiContinuation>;
35
36struct TypedStateCell<T: Clone + 'static> {
37 inner: MutableStateInner<T>,
38}
39
40trait ScopeWatchCell {
41 fn unregister_scope(&self, scope_id: ScopeId);
42}
43
44impl<T: Clone + 'static> ScopeWatchCell for TypedStateCell<T> {
45 fn unregister_scope(&self, scope_id: ScopeId) {
46 self.inner.unregister_scope(scope_id);
47 }
48}
49
50struct StateArenaSlot {
51 generation: u32,
52 cell: Option<Rc<dyn Any>>,
53 watcher_cell: Option<Rc<dyn ScopeWatchCell>>,
54 lease: Option<Weak<StateHandleLease>>,
55}
56
57#[derive(Default)]
58struct StateArenaInner {
59 cells: Vec<StateArenaSlot>,
60 free: Vec<u32>,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub struct StateArenaDebugStats {
65 pub cells_len: usize,
66 pub cells_cap: usize,
67 pub free_len: usize,
68 pub free_cap: usize,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub struct RuntimeDebugStats {
73 pub node_updates_len: usize,
74 pub node_updates_cap: usize,
75 pub invalid_scopes_len: usize,
76 pub invalid_scopes_cap: usize,
77 pub scope_queue_len: usize,
78 pub scope_queue_cap: usize,
79 pub frame_callbacks_len: usize,
80 pub frame_callbacks_cap: usize,
81 pub local_tasks_len: usize,
82 pub local_tasks_cap: usize,
83 pub ui_conts_len: usize,
84 pub ui_conts_cap: usize,
85 pub tasks_len: usize,
86 pub tasks_cap: usize,
87 pub external_state_owners_len: usize,
88 pub external_state_owners_cap: usize,
89 pub ui_dispatcher_pending: usize,
90}
91
92#[derive(Default)]
93pub(crate) struct StateArena {
94 inner: RefCell<StateArenaInner>,
95}
96
97impl StateArena {
98 pub(crate) fn alloc<T: Clone + 'static>(&self, value: T, runtime: RuntimeHandle) -> StateId {
99 self.alloc_with_policy(value, runtime, Arc::new(NeverEqual))
100 }
101
102 pub(crate) fn alloc_with_policy<T: Clone + 'static>(
103 &self,
104 value: T,
105 runtime: RuntimeHandle,
106 policy: Arc<dyn MutationPolicy<T>>,
107 ) -> StateId {
108 let (slot, generation) = {
109 let mut inner = self.inner.borrow_mut();
110 loop {
111 let Some(slot) = inner.free.pop() else {
112 let slot = inner.cells.len() as u32;
113 inner.cells.push(StateArenaSlot {
114 generation: 0,
115 cell: None,
116 watcher_cell: None,
117 lease: None,
118 });
119 break (slot, 0);
120 };
121
122 let Some(entry) = inner.cells.get_mut(slot as usize) else {
123 continue;
124 };
125 if entry.cell.is_some() {
126 continue;
127 }
128
129 entry.watcher_cell = None;
130 entry.lease = None;
131 entry.generation = entry.generation.wrapping_add(1);
132 break (slot, entry.generation);
133 }
134 };
135 let id = StateId::new(slot, generation);
136 let inner = MutableStateInner::new_with_policy(value, runtime.clone(), policy);
137 inner.install_snapshot_observer(id);
138 let typed_cell = Rc::new(TypedStateCell { inner });
139 let cell: Rc<dyn Any> = typed_cell.clone();
140 let watcher_cell: Rc<dyn ScopeWatchCell> = typed_cell;
141 let mut arena = self.inner.borrow_mut();
142 let slot_entry = &mut arena.cells[slot as usize];
143 slot_entry.cell = Some(cell);
144 slot_entry.watcher_cell = Some(watcher_cell);
145 id
146 }
147
148 fn get_cell_opt(&self, id: StateId) -> Option<Rc<dyn Any>> {
149 self.inner
150 .borrow()
151 .cells
152 .get(id.slot_index())
153 .filter(|cell| cell.generation == id.generation())
154 .and_then(|cell| cell.cell.as_ref())
155 .cloned()
156 }
157
158 fn get_typed<T: Clone + 'static>(&self, id: StateId) -> Rc<TypedStateCell<T>> {
159 match self.get_cell_opt(id) {
160 None => panic!(
161 "state cell missing: slot={}, gen={}, expected={}",
162 id.slot(),
163 id.generation(),
164 std::any::type_name::<T>(),
165 ),
166 Some(cell) => Rc::downcast::<TypedStateCell<T>>(cell).unwrap_or_else(|_| {
167 panic!(
168 "state cell type mismatch: slot={}, gen={}, expected={}",
169 id.slot(),
170 id.generation(),
171 std::any::type_name::<T>(),
172 )
173 }),
174 }
175 }
176
177 fn get_typed_opt<T: Clone + 'static>(&self, id: StateId) -> Option<Rc<TypedStateCell<T>>> {
178 Rc::downcast::<TypedStateCell<T>>(self.get_cell_opt(id)?).ok()
179 }
180
181 pub(crate) fn with_typed<T: Clone + 'static, R>(
182 &self,
183 id: StateId,
184 f: impl FnOnce(&MutableStateInner<T>) -> R,
185 ) -> R {
186 let cell = self.get_typed::<T>(id);
187 f(&cell.inner)
188 }
189
190 pub(crate) fn with_typed_opt<T: Clone + 'static, R>(
191 &self,
192 id: StateId,
193 f: impl FnOnce(&MutableStateInner<T>) -> R,
194 ) -> Option<R> {
195 let cell = self.get_typed_opt::<T>(id)?;
196 Some(f(&cell.inner))
197 }
198
199 pub(crate) fn release(&self, id: StateId) {
200 let cell = {
201 let mut inner = self.inner.borrow_mut();
202 let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
203 return;
204 };
205 if slot.generation != id.generation() {
206 return;
207 }
208 slot.lease = None;
209 slot.watcher_cell = None;
210 let cell = slot.cell.take();
211 if cell.is_some() {
212 inner.free.push(id.slot());
213 }
214 cell
215 };
216 drop(cell);
217 }
218
219 pub(crate) fn stats(&self) -> (usize, usize) {
220 let inner = self.inner.borrow();
221 (inner.cells.len(), inner.free.len())
222 }
223
224 pub(crate) fn debug_stats(&self) -> StateArenaDebugStats {
225 let inner = self.inner.borrow();
226 StateArenaDebugStats {
227 cells_len: inner.cells.len(),
228 cells_cap: inner.cells.capacity(),
229 free_len: inner.free.len(),
230 free_cap: inner.free.capacity(),
231 }
232 }
233
234 pub(crate) fn unregister_scope(&self, id: StateId, scope_id: ScopeId) {
235 let watcher_cell = {
236 let inner = self.inner.borrow();
237 inner
238 .cells
239 .get(id.slot_index())
240 .filter(|slot| slot.generation == id.generation())
241 .and_then(|slot| slot.watcher_cell.as_ref())
242 .cloned()
243 };
244 if let Some(watcher_cell) = watcher_cell {
245 watcher_cell.unregister_scope(scope_id);
246 }
247 }
248
249 pub(crate) fn register_lease(&self, id: StateId, lease: &Rc<StateHandleLease>) {
250 let mut inner = self.inner.borrow_mut();
251 let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
252 return;
253 };
254 if slot.generation != id.generation() {
255 return;
256 }
257 slot.lease = Some(Rc::downgrade(lease));
258 }
259
260 pub(crate) fn retain_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
261 let inner = self.inner.borrow();
262 let slot = inner.cells.get(id.slot_index())?;
263 if slot.generation != id.generation() {
264 return None;
265 }
266 slot.lease.as_ref()?.upgrade()
267 }
268}
269
270#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
271pub struct StateId {
272 slot: u32,
273 generation: u32,
274}
275
276impl StateId {
277 const fn new(slot: u32, generation: u32) -> Self {
278 Self { slot, generation }
279 }
280
281 pub(crate) const fn slot(self) -> u32 {
282 self.slot
283 }
284
285 pub(crate) const fn slot_index(self) -> usize {
286 self.slot as usize
287 }
288
289 pub(crate) const fn generation(self) -> u32 {
290 self.generation
291 }
292}
293
294#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
295pub struct RuntimeId(u32);
296
297impl RuntimeId {
298 fn next() -> Self {
299 NEXT_RUNTIME_ID.with(|next| {
300 let id = next.get();
301 next.set(id.wrapping_add(1));
302 Self(id)
303 })
304 }
305}
306
307struct UiDispatcherInner {
308 scheduler: Arc<dyn RuntimeScheduler>,
309 tx: mpsc::Sender<UiMessage>,
310 pending: AtomicUsize,
311}
312
313impl UiDispatcherInner {
314 fn new(scheduler: Arc<dyn RuntimeScheduler>, tx: mpsc::Sender<UiMessage>) -> Self {
315 Self {
316 scheduler,
317 tx,
318 pending: AtomicUsize::new(0),
319 }
320 }
321
322 fn post(&self, task: impl FnOnce() + Send + 'static) {
323 self.pending.fetch_add(1, Ordering::SeqCst);
324 if self.tx.send(UiMessage::Task(Box::new(task))).is_ok() {
325 self.scheduler.schedule_frame();
326 } else {
327 self.pending.fetch_sub(1, Ordering::SeqCst);
328 }
329 }
330
331 fn post_invoke(&self, id: u64, value: Box<dyn Any + Send>) {
332 self.pending.fetch_add(1, Ordering::SeqCst);
333 if self.tx.send(UiMessage::Invoke { id, value }).is_ok() {
334 self.scheduler.schedule_frame();
335 } else {
336 self.pending.fetch_sub(1, Ordering::SeqCst);
337 }
338 }
339
340 fn has_pending(&self) -> bool {
341 self.pending.load(Ordering::SeqCst) > 0
342 }
343}
344
345struct PendingGuard<'a> {
346 counter: &'a AtomicUsize,
347}
348
349impl<'a> PendingGuard<'a> {
350 fn new(counter: &'a AtomicUsize) -> Self {
351 Self { counter }
352 }
353}
354
355impl<'a> Drop for PendingGuard<'a> {
356 fn drop(&mut self) {
357 let mut current = self.counter.load(Ordering::SeqCst);
358 loop {
359 if current == 0 {
360 return;
361 }
362 match self.counter.compare_exchange(
363 current,
364 current - 1,
365 Ordering::SeqCst,
366 Ordering::SeqCst,
367 ) {
368 Ok(_) => return,
369 Err(next) => current = next,
370 }
371 }
372 }
373}
374
375#[derive(Clone)]
376pub struct UiDispatcher {
377 inner: Arc<UiDispatcherInner>,
378}
379
380impl UiDispatcher {
381 fn new(inner: Arc<UiDispatcherInner>) -> Self {
382 Self { inner }
383 }
384
385 pub fn post(&self, task: impl FnOnce() + Send + 'static) {
386 self.inner.post(task);
387 }
388
389 pub fn post_invoke<T>(&self, id: u64, value: T)
390 where
391 T: Send + 'static,
392 {
393 self.inner.post_invoke(id, Box::new(value));
394 }
395
396 pub fn has_pending(&self) -> bool {
397 self.inner.has_pending()
398 }
399}
400
401struct RuntimeInner {
402 scheduler: Arc<dyn RuntimeScheduler>,
403 needs_frame: RefCell<bool>,
404 node_updates: RefCell<Vec<Command>>,
405 invalid_scopes: RefCell<HashSet<ScopeId>>,
406 scope_queue: RefCell<Vec<(ScopeId, Weak<RecomposeScopeInner>)>>,
407 frame_callbacks: RefCell<VecDeque<FrameCallbackEntry>>,
408 next_frame_callback_id: Cell<u64>,
409 last_frame_time_nanos: Cell<Option<u64>>,
410 ui_dispatcher: Arc<UiDispatcherInner>,
411 ui_rx: RefCell<mpsc::Receiver<UiMessage>>,
412 local_tasks: RefCell<VecDeque<Box<dyn FnOnce() + 'static>>>,
413 ui_conts: RefCell<UiContinuationMap>,
414 next_cont_id: Cell<u64>,
415 ui_thread_id: ThreadId,
416 tasks: RefCell<Vec<TaskEntry>>,
417 next_task_id: Cell<u64>,
418 state_arena: StateArena,
419 external_state_owners: RefCell<HashMap<StateId, Rc<StateHandleLease>>>,
420 live_recompose_scope_count: Cell<usize>,
421 runtime_id: RuntimeId,
422}
423
424struct TaskEntry {
425 id: u64,
426 label: String,
427 future: Pin<Box<dyn Future<Output = ()> + 'static>>,
428 runnable: Arc<AtomicBool>,
439 waker: Waker,
442}
443
444thread_local! {
445 static NEXT_TASK_LABEL: RefCell<Option<String>> = const { RefCell::new(None) };
446}
447
448pub fn label_next_ui_task(label: impl Into<String>) {
449 NEXT_TASK_LABEL.with(|held| *held.borrow_mut() = Some(label.into()));
450}
451
452impl RuntimeInner {
453 fn new(scheduler: Arc<dyn RuntimeScheduler>) -> Self {
454 let (tx, rx) = mpsc::channel();
455 let dispatcher = Arc::new(UiDispatcherInner::new(scheduler.clone(), tx));
456 Self {
457 scheduler,
458 needs_frame: RefCell::new(false),
459 node_updates: RefCell::new(Vec::new()),
460 invalid_scopes: RefCell::new(HashSet::default()),
461 scope_queue: RefCell::new(Vec::new()),
462 frame_callbacks: RefCell::new(VecDeque::new()),
463 next_frame_callback_id: Cell::new(1),
464 last_frame_time_nanos: Cell::new(None),
465 ui_dispatcher: dispatcher,
466 ui_rx: RefCell::new(rx),
467 local_tasks: RefCell::new(VecDeque::new()),
468 ui_conts: RefCell::new(UiContinuationMap::default()),
469 next_cont_id: Cell::new(1),
470 ui_thread_id: std::thread::current().id(),
471 tasks: RefCell::new(Vec::new()),
472 next_task_id: Cell::new(1),
473 state_arena: StateArena::default(),
474 external_state_owners: RefCell::new(HashMap::default()),
475 live_recompose_scope_count: Cell::new(0),
476 runtime_id: RuntimeId::next(),
477 }
478 }
479
480 fn schedule(&self) {
481 *self.needs_frame.borrow_mut() = true;
482 self.scheduler.schedule_frame();
483 }
484
485 fn enqueue_update(&self, command: Command) {
486 self.node_updates.borrow_mut().push(command);
487 self.schedule(); }
489
490 fn take_updates(&self) -> Vec<Command> {
491 let updates = self.node_updates.borrow_mut().drain(..).collect::<Vec<_>>();
492 updates
493 }
494
495 fn has_updates(&self) -> bool {
496 !self.node_updates.borrow().is_empty() || self.has_invalid_scopes()
497 }
498
499 fn register_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
500 let mut invalid = self.invalid_scopes.borrow_mut();
501 if invalid.insert(id) {
502 self.scope_queue.borrow_mut().push((id, scope));
503 self.schedule();
504 }
505 }
506
507 fn requeue_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
508 if self.invalid_scopes.borrow().contains(&id) {
509 self.scope_queue.borrow_mut().push((id, scope));
510 self.schedule();
511 }
512 }
513
514 fn mark_scope_recomposed(&self, id: ScopeId) {
515 self.invalid_scopes.borrow_mut().remove(&id);
516 }
517
518 fn take_invalidated_scopes(&self) -> Vec<(ScopeId, Weak<RecomposeScopeInner>)> {
519 let mut queue = self.scope_queue.borrow_mut();
520 if queue.is_empty() {
521 return Vec::new();
522 }
523 let pending: Vec<_> = queue.drain(..).collect();
524 drop(queue);
525 let invalid = self.invalid_scopes.borrow();
526 pending
527 .into_iter()
528 .filter(|(id, _)| invalid.contains(id))
529 .collect()
530 }
531
532 fn has_invalid_scopes(&self) -> bool {
533 !self.invalid_scopes.borrow().is_empty()
534 }
535
536 fn increment_live_recompose_scope_count(&self) {
537 self.live_recompose_scope_count
538 .set(self.live_recompose_scope_count.get().saturating_add(1));
539 }
540
541 fn decrement_live_recompose_scope_count(&self) {
542 self.live_recompose_scope_count
543 .set(self.live_recompose_scope_count.get().saturating_sub(1));
544 }
545
546 fn live_recompose_scope_count(&self) -> usize {
547 self.live_recompose_scope_count.get()
548 }
549
550 fn has_frame_callbacks(&self) -> bool {
551 !self.frame_callbacks.borrow().is_empty()
552 }
553
554 fn has_transient_frame_callbacks(&self) -> bool {
555 self.frame_callbacks
556 .borrow()
557 .iter()
558 .any(|entry| entry.kind == FrameCallbackKind::Transient)
559 }
560
561 fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
566 self.local_tasks.borrow_mut().push_back(task);
567 self.schedule();
568 }
569
570 fn spawn_ui_task(&self, future: Pin<Box<dyn Future<Output = ()> + 'static>>) -> u64 {
571 let id = self.next_task_id.get();
572 self.next_task_id.set(id + 1);
573 let label = NEXT_TASK_LABEL
574 .with(|held| held.borrow_mut().take())
575 .unwrap_or_else(|| "unnamed".to_string());
576 let runnable = Arc::new(AtomicBool::new(true));
577 let waker = RuntimeTaskWaker::new(self, Arc::clone(&runnable)).into_waker();
578 self.tasks.borrow_mut().push(TaskEntry {
579 id,
580 label,
581 future,
582 runnable,
583 waker,
584 });
585 self.schedule();
586 id
587 }
588
589 fn cancel_task(&self, id: u64) {
590 let mut tasks = self.tasks.borrow_mut();
591 if tasks.iter().any(|entry| entry.id == id) {
592 tasks.retain(|entry| entry.id != id);
593 }
594 }
595
596 fn has_task(&self, id: u64) -> bool {
599 self.tasks
603 .try_borrow()
604 .map(|tasks| tasks.iter().any(|entry| entry.id == id))
605 .unwrap_or(true)
606 }
607
608 fn poll_async_tasks(&self) -> bool {
609 let mut tasks_ref = self.tasks.borrow_mut();
610 let tasks = std::mem::take(&mut *tasks_ref);
611 drop(tasks_ref);
612 let mut pending = Vec::with_capacity(tasks.len());
613 let mut made_progress = false;
614 for mut entry in tasks.into_iter() {
615 if !entry.runnable.swap(false, Ordering::AcqRel) {
619 pending.push(entry);
620 continue;
621 }
622 let mut cx = Context::from_waker(&entry.waker);
623 match entry.future.as_mut().poll(&mut cx) {
624 Poll::Ready(()) => {
625 made_progress = true;
626 }
627 Poll::Pending => {
628 pending.push(entry);
629 }
630 }
631 }
632 if !pending.is_empty() {
633 self.tasks.borrow_mut().extend(pending);
634 }
635 made_progress
636 }
637
638 fn drain_ui(&self) {
639 loop {
640 let mut executed = false;
641
642 {
643 let rx = &mut *self.ui_rx.borrow_mut();
644 for message in rx.try_iter() {
645 executed = true;
646 let _guard = PendingGuard::new(&self.ui_dispatcher.pending);
647 match message {
648 UiMessage::Task(task) => {
649 task();
650 }
651 UiMessage::Invoke { id, value } => {
652 self.invoke_ui_cont(id, value);
653 }
654 }
655 }
656 }
657
658 loop {
659 let task = {
660 let mut local = self.local_tasks.borrow_mut();
661 local.pop_front()
662 };
663
664 match task {
665 Some(task) => {
666 executed = true;
667 task();
668 }
669 None => break,
670 }
671 }
672
673 if self.poll_async_tasks() {
674 executed = true;
675 }
676
677 if !executed {
678 break;
679 }
680 }
681
682 self.clear_needs_frame_if_idle();
687 }
688
689 fn has_pending_ui(&self) -> bool {
690 let local_pending = self
691 .local_tasks
692 .try_borrow()
693 .map(|tasks| !tasks.is_empty())
694 .unwrap_or(true);
695
696 local_pending || self.ui_dispatcher.has_pending() || self.has_runnable_tasks()
697 }
698
699 fn has_runnable_tasks(&self) -> bool {
711 self.tasks
712 .try_borrow()
713 .map(|tasks| {
714 tasks
715 .iter()
716 .any(|task| task.runnable.load(Ordering::Acquire))
717 })
718 .unwrap_or(true)
719 }
720
721 fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> u64 {
722 debug_assert_eq!(
723 std::thread::current().id(),
724 self.ui_thread_id,
725 "UI continuation registered off the runtime thread",
726 );
727 let id = self.next_cont_id.get();
728 self.next_cont_id.set(id + 1);
729 let callback = RefCell::new(Some(f));
730 self.ui_conts.borrow_mut().insert(
731 id,
732 Box::new(move |value: Box<dyn Any>| {
733 let Ok(value) = value.downcast::<T>() else {
734 return false;
735 };
736 let Some(slot) = callback.borrow_mut().take() else {
737 return true;
738 };
739 slot(*value);
740 true
741 }),
742 );
743 id
744 }
745
746 fn invoke_ui_cont(&self, id: u64, value: Box<dyn Any + Send>) {
747 debug_assert_eq!(
748 std::thread::current().id(),
749 self.ui_thread_id,
750 "UI continuation invoked off the runtime thread",
751 );
752 let callback = { self.ui_conts.borrow_mut().remove(&id) };
753 if let Some(callback) = callback {
754 let value: Box<dyn Any> = value;
755 if !callback(value) {
756 self.ui_conts.borrow_mut().insert(id, callback);
757 }
758 }
759 }
760
761 fn cancel_ui_cont(&self, id: u64) {
762 self.ui_conts.borrow_mut().remove(&id);
763 }
764
765 fn register_frame_callback(
766 &self,
767 kind: FrameCallbackKind,
768 callback: Box<dyn FnOnce(u64) + 'static>,
769 ) -> FrameCallbackId {
770 let id = self.next_frame_callback_id.get();
771 self.next_frame_callback_id.set(id + 1);
772 self.frame_callbacks
773 .borrow_mut()
774 .push_back(FrameCallbackEntry {
775 id,
776 kind,
777 callback: Some(callback),
778 });
779 self.schedule();
780 id
781 }
782
783 fn cancel_frame_callback(&self, id: FrameCallbackId) {
784 let mut callbacks = self.frame_callbacks.borrow_mut();
785 if let Some(index) = callbacks.iter().position(|entry| entry.id == id) {
786 callbacks.remove(index);
787 }
788 drop(callbacks);
789 self.clear_needs_frame_if_idle();
790 }
791
792 fn clear_needs_frame_if_idle(&self) {
799 if !self.has_invalid_scopes()
800 && !self.has_updates()
801 && !self.has_frame_callbacks()
802 && !self.has_pending_ui()
803 {
804 *self.needs_frame.borrow_mut() = false;
805 }
806 }
807
808 fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
809 self.last_frame_time_nanos.set(Some(frame_time_nanos));
810 let mut callbacks = self.frame_callbacks.borrow_mut();
811 let mut pending: Vec<Box<dyn FnOnce(u64) + 'static>> = Vec::with_capacity(callbacks.len());
812 while let Some(mut entry) = callbacks.pop_front() {
813 if let Some(callback) = entry.callback.take() {
814 pending.push(callback);
815 }
816 }
817 drop(callbacks);
818
819 if !pending.is_empty() {
824 let _ = crate::run_in_mutable_snapshot(|| {
825 for callback in pending {
826 callback(frame_time_nanos);
827 }
828 });
829 }
830
831 self.clear_needs_frame_if_idle();
832 }
833
834 fn debug_stats(&self) -> RuntimeDebugStats {
835 let node_updates = self.node_updates.borrow();
836 let invalid_scopes = self.invalid_scopes.borrow();
837 let scope_queue = self.scope_queue.borrow();
838 let frame_callbacks = self.frame_callbacks.borrow();
839 let local_tasks = self.local_tasks.borrow();
840 let ui_conts = self.ui_conts.borrow();
841 let tasks = self.tasks.borrow();
842 let external_state_owners = self.external_state_owners.borrow();
843
844 RuntimeDebugStats {
845 node_updates_len: node_updates.len(),
846 node_updates_cap: node_updates.capacity(),
847 invalid_scopes_len: invalid_scopes.len(),
848 invalid_scopes_cap: invalid_scopes.capacity(),
849 scope_queue_len: scope_queue.len(),
850 scope_queue_cap: scope_queue.capacity(),
851 frame_callbacks_len: frame_callbacks.len(),
852 frame_callbacks_cap: frame_callbacks.capacity(),
853 local_tasks_len: local_tasks.len(),
854 local_tasks_cap: local_tasks.capacity(),
855 ui_conts_len: ui_conts.len(),
856 ui_conts_cap: ui_conts.capacity(),
857 tasks_len: tasks.len(),
858 tasks_cap: tasks.capacity(),
859 external_state_owners_len: external_state_owners.len(),
860 external_state_owners_cap: external_state_owners.capacity(),
861 ui_dispatcher_pending: self.ui_dispatcher.pending.load(Ordering::SeqCst),
862 }
863 }
864}
865
866#[derive(Clone)]
867pub struct Runtime {
868 inner: Rc<RuntimeInner>,
869}
870
871impl Runtime {
872 pub fn new(scheduler: Arc<dyn RuntimeScheduler>) -> Self {
873 let inner = Rc::new(RuntimeInner::new(scheduler));
874 let runtime = Self { inner };
875 let handle = runtime.handle();
876 register_runtime_handle(&handle);
877 LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle));
878 runtime
879 }
880
881 pub fn handle(&self) -> RuntimeHandle {
882 RuntimeHandle {
883 inner: Rc::downgrade(&self.inner),
884 dispatcher: UiDispatcher::new(self.inner.ui_dispatcher.clone()),
885 ui_thread_id: self.inner.ui_thread_id,
886 id: self.inner.runtime_id,
887 }
888 }
889
890 pub fn has_updates(&self) -> bool {
891 self.inner.has_updates()
892 }
893
894 pub fn needs_frame(&self) -> bool {
895 *self.inner.needs_frame.borrow() || self.inner.has_runnable_tasks()
904 }
905
906 pub fn set_needs_frame(&self, value: bool) {
907 *self.inner.needs_frame.borrow_mut() = value;
908 }
909
910 pub fn last_frame_time_nanos(&self) -> Option<u64> {
915 self.inner.last_frame_time_nanos.get()
916 }
917
918 #[cfg(any(feature = "internal", test))]
919 pub fn frame_clock(&self) -> FrameClock {
920 FrameClock::new(self.handle())
921 }
922}
923
924impl Drop for Runtime {
925 fn drop(&mut self) {
926 if Rc::strong_count(&self.inner) != 1 {
927 return;
928 }
929 unregister_runtime_handle(self.inner.runtime_id);
930 LAST_RUNTIME.with(|slot| {
931 let should_clear = slot
932 .borrow()
933 .as_ref()
934 .is_some_and(|handle| handle.id() == self.inner.runtime_id);
935 if should_clear {
936 *slot.borrow_mut() = None;
937 }
938 });
939 }
940}
941
942#[derive(Default)]
943pub struct DefaultScheduler;
944
945impl RuntimeScheduler for DefaultScheduler {
946 fn schedule_frame(&self) {}
947}
948
949#[cfg(test)]
950#[derive(Default)]
951pub struct TestScheduler;
952
953#[cfg(test)]
954impl RuntimeScheduler for TestScheduler {
955 fn schedule_frame(&self) {}
956}
957
958#[cfg(test)]
959pub struct TestRuntime {
960 runtime: Runtime,
961}
962
963#[cfg(test)]
964impl Default for TestRuntime {
965 fn default() -> Self {
966 Self::new()
967 }
968}
969
970#[cfg(test)]
971impl TestRuntime {
972 pub fn new() -> Self {
973 Self {
974 runtime: Runtime::new(Arc::new(TestScheduler)),
975 }
976 }
977
978 pub fn handle(&self) -> RuntimeHandle {
979 self.runtime.handle()
980 }
981}
982
983#[derive(Clone)]
984pub struct RuntimeHandle {
985 inner: Weak<RuntimeInner>,
986 dispatcher: UiDispatcher,
987 ui_thread_id: ThreadId,
988 id: RuntimeId,
989}
990
991pub struct TaskHandle {
992 id: u64,
993 runtime: RuntimeHandle,
994}
995
996struct DeferredStateRelease {
997 runtime: RuntimeHandle,
998 id: StateId,
999}
1000
1001pub(crate) struct StateHandleLease {
1002 id: StateId,
1003 runtime: RuntimeHandle,
1004}
1005
1006impl StateHandleLease {
1007 pub(crate) fn id(&self) -> StateId {
1008 self.id
1009 }
1010
1011 pub(crate) fn runtime(&self) -> RuntimeHandle {
1012 self.runtime.clone()
1013 }
1014}
1015
1016impl Drop for StateHandleLease {
1017 fn drop(&mut self) {
1018 defer_state_release(self.runtime.clone(), self.id);
1019 }
1020}
1021
1022impl RuntimeHandle {
1023 pub fn id(&self) -> RuntimeId {
1024 self.id
1025 }
1026
1027 pub(crate) fn alloc_state<T: Clone + 'static>(&self, value: T) -> Rc<StateHandleLease> {
1028 let id = self.with_state_arena(|arena| arena.alloc(value, self.clone()));
1029 let lease = Rc::new(StateHandleLease {
1030 id,
1031 runtime: self.clone(),
1032 });
1033 self.with_state_arena(|arena| arena.register_lease(id, &lease));
1034 lease
1035 }
1036
1037 pub(crate) fn alloc_state_with_policy<T: Clone + 'static>(
1038 &self,
1039 value: T,
1040 policy: Arc<dyn MutationPolicy<T>>,
1041 ) -> Rc<StateHandleLease> {
1042 let id =
1043 self.with_state_arena(|arena| arena.alloc_with_policy(value, self.clone(), policy));
1044 let lease = Rc::new(StateHandleLease {
1045 id,
1046 runtime: self.clone(),
1047 });
1048 self.with_state_arena(|arena| arena.register_lease(id, &lease));
1049 lease
1050 }
1051
1052 pub(crate) fn alloc_persistent_state<T: Clone + 'static>(
1053 &self,
1054 value: T,
1055 ) -> crate::MutableState<T> {
1056 let lease = self.alloc_state(value);
1057 if let Some(inner) = self.inner.upgrade() {
1058 inner
1059 .external_state_owners
1060 .borrow_mut()
1061 .insert(lease.id(), Rc::clone(&lease));
1062 }
1063 crate::MutableState::from_lease(&lease)
1064 }
1065
1066 pub(crate) fn retain_state_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
1067 self.with_state_arena(|arena| arena.retain_lease(id))
1068 }
1069
1070 pub(crate) fn with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> R {
1071 self.try_with_state_arena(f)
1072 .unwrap_or_else(|| panic!("runtime dropped"))
1073 }
1074
1075 pub(crate) fn try_with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> Option<R> {
1076 self.inner.upgrade().map(|inner| f(&inner.state_arena))
1077 }
1078
1079 fn release_state_immediate(&self, id: StateId) {
1080 if let Some(inner) = self.inner.upgrade() {
1081 inner.state_arena.release(id);
1082 }
1083 }
1084
1085 pub fn state_arena_stats(&self) -> (usize, usize) {
1086 self.try_with_state_arena(StateArena::stats)
1087 .unwrap_or_default()
1088 }
1089
1090 pub fn state_arena_debug_stats(&self) -> StateArenaDebugStats {
1091 self.try_with_state_arena(StateArena::debug_stats)
1092 .unwrap_or_default()
1093 }
1094
1095 pub fn debug_stats(&self) -> RuntimeDebugStats {
1096 self.inner
1097 .upgrade()
1098 .map(|inner| inner.debug_stats())
1099 .unwrap_or_default()
1100 }
1101
1102 pub fn live_ui_task_labels(&self) -> Vec<(u64, String)> {
1103 self.inner
1104 .upgrade()
1105 .map(|inner| {
1106 inner
1107 .tasks
1108 .borrow()
1109 .iter()
1110 .map(|entry| (entry.id, entry.label.clone()))
1111 .collect()
1112 })
1113 .unwrap_or_default()
1114 }
1115
1116 pub(crate) fn unregister_state_scope(&self, id: StateId, scope_id: ScopeId) {
1117 if let Some(inner) = self.inner.upgrade() {
1118 inner.state_arena.unregister_scope(id, scope_id);
1119 }
1120 }
1121
1122 pub fn schedule(&self) {
1123 if let Some(inner) = self.inner.upgrade() {
1124 inner.schedule();
1125 }
1126 }
1127
1128 pub(crate) fn enqueue_node_update(&self, command: Command) {
1129 if let Some(inner) = self.inner.upgrade() {
1130 inner.enqueue_update(command);
1131 }
1132 }
1133
1134 pub fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
1141 if let Some(inner) = self.inner.upgrade() {
1142 inner.enqueue_ui_task(task);
1143 } else {
1144 task();
1145 }
1146 }
1147
1148 pub fn spawn_ui<F>(&self, fut: F) -> Option<TaskHandle>
1149 where
1150 F: Future<Output = ()> + 'static,
1151 {
1152 self.inner.upgrade().map(|inner| {
1153 let id = inner.spawn_ui_task(Box::pin(fut));
1154 TaskHandle {
1155 id,
1156 runtime: self.clone(),
1157 }
1158 })
1159 }
1160
1161 pub fn cancel_task(&self, id: u64) {
1162 if let Some(inner) = self.inner.upgrade() {
1163 inner.cancel_task(id);
1164 }
1165 }
1166
1167 pub fn has_task(&self, id: u64) -> bool {
1169 self.inner
1170 .upgrade()
1171 .map(|inner| inner.has_task(id))
1172 .unwrap_or(false)
1173 }
1174
1175 pub fn post_ui(&self, task: impl FnOnce() + Send + 'static) {
1180 self.dispatcher.post(task);
1181 }
1182
1183 pub fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> Option<u64> {
1184 self.inner.upgrade().map(|inner| inner.register_ui_cont(f))
1185 }
1186
1187 pub fn cancel_ui_cont(&self, id: u64) {
1188 if let Some(inner) = self.inner.upgrade() {
1189 inner.cancel_ui_cont(id);
1190 }
1191 }
1192
1193 pub fn drain_ui(&self) {
1194 if let Some(inner) = self.inner.upgrade() {
1195 inner.drain_ui();
1196 }
1197 }
1198
1199 pub fn has_pending_ui(&self) -> bool {
1200 self.inner
1201 .upgrade()
1202 .map(|inner| inner.has_pending_ui())
1203 .unwrap_or_else(|| self.dispatcher.has_pending())
1204 }
1205
1206 pub fn register_frame_callback(
1207 &self,
1208 callback: impl FnOnce(u64) + 'static,
1209 ) -> Option<FrameCallbackId> {
1210 self.inner.upgrade().map(|inner| {
1211 inner.register_frame_callback(FrameCallbackKind::Transient, Box::new(callback))
1212 })
1213 }
1214
1215 pub fn register_perpetual_frame_callback(
1216 &self,
1217 callback: impl FnOnce(u64) + 'static,
1218 ) -> Option<FrameCallbackId> {
1219 self.inner.upgrade().map(|inner| {
1220 inner.register_frame_callback(FrameCallbackKind::Perpetual, Box::new(callback))
1221 })
1222 }
1223
1224 pub fn cancel_frame_callback(&self, id: FrameCallbackId) {
1225 if let Some(inner) = self.inner.upgrade() {
1226 inner.cancel_frame_callback(id);
1227 }
1228 }
1229
1230 pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1231 if let Some(inner) = self.inner.upgrade() {
1232 inner.drain_frame_callbacks(frame_time_nanos);
1233 }
1234 }
1235
1236 pub fn last_frame_time_nanos(&self) -> Option<u64> {
1239 self.inner
1240 .upgrade()
1241 .and_then(|inner| inner.last_frame_time_nanos.get())
1242 }
1243
1244 #[cfg(any(feature = "internal", test))]
1245 pub fn frame_clock(&self) -> FrameClock {
1246 FrameClock::new(self.clone())
1247 }
1248
1249 pub fn set_needs_frame(&self, value: bool) {
1250 if let Some(inner) = self.inner.upgrade() {
1251 *inner.needs_frame.borrow_mut() = value;
1252 }
1253 }
1254
1255 pub(crate) fn take_updates(&self) -> Vec<Command> {
1256 self.inner
1257 .upgrade()
1258 .map(|inner| inner.take_updates())
1259 .unwrap_or_default()
1260 }
1261
1262 pub fn has_updates(&self) -> bool {
1263 self.inner
1264 .upgrade()
1265 .map(|inner| inner.has_updates())
1266 .unwrap_or(false)
1267 }
1268
1269 pub(crate) fn mark_scope_recomposed(&self, id: ScopeId) {
1270 if let Some(inner) = self.inner.upgrade() {
1271 inner.mark_scope_recomposed(id);
1272 }
1273 }
1274
1275 pub(crate) fn register_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
1276 if let Some(inner) = self.inner.upgrade() {
1277 inner.register_invalid_scope(id, scope);
1278 }
1279 }
1280
1281 pub(crate) fn requeue_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
1282 if let Some(inner) = self.inner.upgrade() {
1283 inner.requeue_invalid_scope(id, scope);
1284 }
1285 }
1286
1287 pub(crate) fn take_invalidated_scopes(&self) -> Vec<(ScopeId, Weak<RecomposeScopeInner>)> {
1288 self.inner
1289 .upgrade()
1290 .map(|inner| inner.take_invalidated_scopes())
1291 .unwrap_or_default()
1292 }
1293
1294 pub fn has_invalid_scopes(&self) -> bool {
1295 self.inner
1296 .upgrade()
1297 .map(|inner| inner.has_invalid_scopes())
1298 .unwrap_or(false)
1299 }
1300
1301 pub(crate) fn increment_live_recompose_scope_count(&self) {
1302 if let Some(inner) = self.inner.upgrade() {
1303 inner.increment_live_recompose_scope_count();
1304 }
1305 }
1306
1307 pub(crate) fn decrement_live_recompose_scope_count(&self) {
1308 if let Some(inner) = self.inner.upgrade() {
1309 inner.decrement_live_recompose_scope_count();
1310 }
1311 }
1312
1313 fn live_recompose_scope_count(&self) -> usize {
1314 self.inner
1315 .upgrade()
1316 .map(|inner| inner.live_recompose_scope_count())
1317 .unwrap_or_default()
1318 }
1319
1320 #[doc(hidden)]
1321 pub fn debug_invalid_scope_ids(&self) -> Vec<usize> {
1322 self.inner
1323 .upgrade()
1324 .map(|inner| inner.invalid_scopes.borrow().iter().copied().collect())
1325 .unwrap_or_default()
1326 }
1327
1328 pub fn has_frame_callbacks(&self) -> bool {
1329 self.inner
1330 .upgrade()
1331 .map(|inner| inner.has_frame_callbacks())
1332 .unwrap_or(false)
1333 }
1334
1335 pub fn has_transient_frame_callbacks(&self) -> bool {
1336 self.inner
1337 .upgrade()
1338 .map(|inner| inner.has_transient_frame_callbacks())
1339 .unwrap_or(false)
1340 }
1341
1342 pub fn assert_ui_thread(&self) {
1343 debug_assert_eq!(
1344 std::thread::current().id(),
1345 self.ui_thread_id,
1346 "state mutated off the runtime's UI thread"
1347 );
1348 }
1349
1350 pub fn dispatcher(&self) -> UiDispatcher {
1351 self.dispatcher.clone()
1352 }
1353
1354 #[doc(hidden)]
1355 pub fn with_deferred_state_releases<R>(&self, f: impl FnOnce() -> R) -> R {
1356 let _scope = enter_state_teardown_scope();
1357 f()
1358 }
1359}
1360
1361impl TaskHandle {
1362 pub fn cancel(&self) {
1363 self.runtime.cancel_task(self.id);
1364 }
1365
1366 pub fn is_finished(&self) -> bool {
1368 !self.runtime.has_task(self.id)
1369 }
1370}
1371
1372pub(crate) struct FrameCallbackEntry {
1373 id: FrameCallbackId,
1374 kind: FrameCallbackKind,
1375 callback: Option<Box<dyn FnOnce(u64) + 'static>>,
1376}
1377
1378#[cfg(not(target_arch = "wasm32"))]
1390struct RuntimeTaskWaker {
1391 scheduler: Arc<dyn RuntimeScheduler>,
1392 runnable: Arc<AtomicBool>,
1393}
1394
1395#[cfg(target_arch = "wasm32")]
1396struct RuntimeTaskWaker {
1397 runtime_id: RuntimeId,
1398 runnable: Arc<AtomicBool>,
1399}
1400
1401impl RuntimeTaskWaker {
1402 #[cfg(not(target_arch = "wasm32"))]
1403 fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1404 let scheduler = inner.scheduler.clone();
1405 Self {
1406 scheduler,
1407 runnable,
1408 }
1409 }
1410
1411 #[cfg(target_arch = "wasm32")]
1412 fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1413 let runtime_id = inner.runtime_id;
1414 Self {
1415 runtime_id,
1416 runnable,
1417 }
1418 }
1419
1420 fn into_waker(self) -> Waker {
1421 futures_task::waker(Arc::new(self))
1422 }
1423}
1424
1425impl futures_task::ArcWake for RuntimeTaskWaker {
1426 #[cfg(not(target_arch = "wasm32"))]
1427 fn wake_by_ref(arc_self: &Arc<Self>) {
1428 arc_self.runnable.store(true, Ordering::Release);
1429 arc_self.scheduler.schedule_frame();
1430 }
1431
1432 #[cfg(target_arch = "wasm32")]
1433 fn wake_by_ref(arc_self: &Arc<Self>) {
1434 arc_self.runnable.store(true, Ordering::Release);
1435 REGISTERED_RUNTIMES.with(|registry| {
1436 if let Some(handle) = registry.borrow().get(&arc_self.runtime_id).cloned() {
1437 handle.schedule();
1438 }
1439 });
1440 }
1441}
1442
1443thread_local! {
1444 static NEXT_RUNTIME_ID: Cell<u32> = const { Cell::new(1) };
1445 static ACTIVE_RUNTIMES: RefCell<Vec<RuntimeHandle>> = const { RefCell::new(Vec::new()) };
1446 static LAST_RUNTIME: RefCell<Option<RuntimeHandle>> = const { RefCell::new(None) };
1447 static REGISTERED_RUNTIMES: RefCell<HashMap<RuntimeId, RuntimeHandle>> = RefCell::new(HashMap::default());
1448 static STATE_TEARDOWN_DEPTH: Cell<usize> = const { Cell::new(0) };
1449 static DEFERRED_STATE_RELEASES: RefCell<Vec<DeferredStateRelease>> = const { RefCell::new(Vec::new()) };
1450}
1451
1452#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1453pub struct RuntimeThreadLocalDebugStats {
1454 pub active_runtimes_len: usize,
1455 pub active_runtimes_cap: usize,
1456 pub registered_runtimes_len: usize,
1457 pub registered_runtimes_cap: usize,
1458 pub deferred_state_releases_len: usize,
1459 pub deferred_state_releases_cap: usize,
1460}
1461
1462pub fn current_runtime_handle() -> Option<RuntimeHandle> {
1467 if let Some(handle) = ACTIVE_RUNTIMES.with(|stack| stack.borrow().last().cloned()) {
1468 return Some(handle);
1469 }
1470 LAST_RUNTIME.with(|slot| slot.borrow().clone())
1471}
1472
1473pub(crate) fn runtime_handle_by_id(id: RuntimeId) -> Option<RuntimeHandle> {
1474 REGISTERED_RUNTIMES.with(|registry| registry.borrow().get(&id).cloned())
1475}
1476
1477pub(crate) fn live_recompose_scope_count() -> usize {
1478 REGISTERED_RUNTIMES.with(|registry| {
1479 registry
1480 .borrow()
1481 .values()
1482 .map(RuntimeHandle::live_recompose_scope_count)
1483 .sum()
1484 })
1485}
1486
1487pub fn debug_runtime_thread_local_stats() -> RuntimeThreadLocalDebugStats {
1488 let (active_runtimes_len, active_runtimes_cap) = ACTIVE_RUNTIMES.with(|stack| {
1489 let stack = stack.borrow();
1490 (stack.len(), stack.capacity())
1491 });
1492 let (registered_runtimes_len, registered_runtimes_cap) = REGISTERED_RUNTIMES.with(|registry| {
1493 let registry = registry.borrow();
1494 (registry.len(), registry.capacity())
1495 });
1496 let (deferred_state_releases_len, deferred_state_releases_cap) =
1497 DEFERRED_STATE_RELEASES.with(|releases| {
1498 let releases = releases.borrow();
1499 (releases.len(), releases.capacity())
1500 });
1501
1502 RuntimeThreadLocalDebugStats {
1503 active_runtimes_len,
1504 active_runtimes_cap,
1505 registered_runtimes_len,
1506 registered_runtimes_cap,
1507 deferred_state_releases_len,
1508 deferred_state_releases_cap,
1509 }
1510}
1511
1512fn register_runtime_handle(handle: &RuntimeHandle) {
1513 REGISTERED_RUNTIMES.with(|registry| {
1514 registry.borrow_mut().insert(handle.id(), handle.clone());
1515 });
1516}
1517
1518fn unregister_runtime_handle(id: RuntimeId) {
1519 REGISTERED_RUNTIMES.with(|registry| {
1520 registry.borrow_mut().remove(&id);
1521 });
1522}
1523
1524fn defer_state_release(runtime: RuntimeHandle, id: StateId) {
1525 let teardown_active = STATE_TEARDOWN_DEPTH.with(|depth| depth.get() > 0);
1526 if teardown_active {
1527 DEFERRED_STATE_RELEASES.with(|releases| {
1528 releases
1529 .borrow_mut()
1530 .push(DeferredStateRelease { runtime, id });
1531 });
1532 } else {
1533 runtime.release_state_immediate(id);
1534 }
1535}
1536
1537fn flush_deferred_state_releases() {
1538 DEFERRED_STATE_RELEASES.with(|releases| {
1539 let mut releases = releases.borrow_mut();
1540 while let Some(deferred) = releases.pop() {
1541 deferred.runtime.release_state_immediate(deferred.id);
1542 }
1543 });
1544}
1545
1546pub(crate) struct StateTeardownScope;
1547
1548pub(crate) fn enter_state_teardown_scope() -> StateTeardownScope {
1549 STATE_TEARDOWN_DEPTH.with(|depth| depth.set(depth.get() + 1));
1550 StateTeardownScope
1551}
1552
1553impl Drop for StateTeardownScope {
1554 fn drop(&mut self) {
1555 STATE_TEARDOWN_DEPTH.with(|depth| {
1556 let next = depth.get().saturating_sub(1);
1557 depth.set(next);
1558 if next == 0 {
1559 flush_deferred_state_releases();
1560 }
1561 });
1562 }
1563}
1564
1565pub(crate) fn push_active_runtime(handle: &RuntimeHandle) {
1566 register_runtime_handle(handle);
1567 ACTIVE_RUNTIMES.with(|stack| stack.borrow_mut().push(handle.clone()));
1568 LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle.clone()));
1569}
1570
1571pub(crate) fn pop_active_runtime() {
1572 ACTIVE_RUNTIMES.with(|stack| {
1573 stack.borrow_mut().pop();
1574 });
1575}
1576
1577pub fn schedule_frame() {
1579 if let Some(handle) = current_runtime_handle() {
1580 handle.schedule();
1581 return;
1582 }
1583 log::debug!(
1584 target: "cranpose::runtime",
1585 "ignoring frame request without an active runtime",
1586 );
1587}
1588
1589pub fn schedule_node_update(
1591 update: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1592) {
1593 if let Some(handle) = current_runtime_handle() {
1594 handle.enqueue_node_update(Command::callback(update));
1595 } else {
1596 drop(update);
1597 log::debug!(
1598 target: "cranpose::runtime",
1599 "ignoring node update request without an active runtime",
1600 );
1601 }
1602}
1603
1604#[cfg(test)]
1605mod tests {
1606 use super::*;
1607
1608 #[test]
1609 fn state_arena_alloc_skips_free_slot_outside_cell_storage() {
1610 let runtime = TestRuntime::new();
1611 let arena = StateArena::default();
1612 let first = arena.alloc(1_i32, runtime.handle());
1613 arena.inner.borrow_mut().free.push(u32::MAX);
1614
1615 let second = arena.alloc(2_i32, runtime.handle());
1616
1617 assert_eq!(first.slot(), 0);
1618 assert_eq!(second.slot(), 1);
1619 assert!(arena.get_typed_opt::<i32>(first).is_some());
1620 assert!(arena.get_typed_opt::<i32>(second).is_some());
1621 }
1622
1623 #[test]
1624 fn state_arena_alloc_skips_occupied_free_slot() {
1625 let runtime = TestRuntime::new();
1626 let arena = StateArena::default();
1627 let first = arena.alloc(1_i32, runtime.handle());
1628 arena.inner.borrow_mut().free.push(first.slot());
1629
1630 let second = arena.alloc(2_i32, runtime.handle());
1631
1632 assert_ne!(first.slot(), second.slot());
1633 assert_eq!(second.slot(), 1);
1634 assert!(arena.get_typed_opt::<i32>(first).is_some());
1635 assert!(arena.get_typed_opt::<i32>(second).is_some());
1636 }
1637
1638 #[test]
1639 fn state_arena_register_lease_ignores_stale_id() {
1640 let runtime = TestRuntime::new();
1641 let arena = StateArena::default();
1642 let stale_id = StateId::new(99, 0);
1643 let lease = Rc::new(StateHandleLease {
1644 id: stale_id,
1645 runtime: runtime.handle(),
1646 });
1647
1648 arena.register_lease(stale_id, &lease);
1649
1650 assert!(arena.retain_lease(stale_id).is_none());
1651 }
1652
1653 #[test]
1654 fn ui_continuation_type_mismatch_is_ignored_until_matching_payload() {
1655 let runtime = TestRuntime::new();
1656 let handle = runtime.handle();
1657 let received = Rc::new(Cell::new(None));
1658 let received_for_continuation = Rc::clone(&received);
1659 let cont_id = handle
1660 .register_ui_cont(move |value: u32| {
1661 received_for_continuation.set(Some(value));
1662 })
1663 .expect("test runtime is alive");
1664
1665 handle.dispatcher().post_invoke(cont_id, "wrong payload");
1666 handle.drain_ui();
1667
1668 assert_eq!(received.get(), None);
1669 assert_eq!(handle.debug_stats().ui_conts_len, 1);
1670
1671 handle.dispatcher().post_invoke(cont_id, 42_u32);
1672 handle.drain_ui();
1673
1674 assert_eq!(received.get(), Some(42));
1675 assert_eq!(handle.debug_stats().ui_conts_len, 0);
1676 }
1677
1678 #[test]
1679 fn ui_dispatcher_failed_send_does_not_leave_pending_work() {
1680 let runtime = Runtime::new(Arc::new(TestScheduler));
1681 let dispatcher = runtime.handle().dispatcher();
1682
1683 drop(runtime);
1684
1685 assert!(!dispatcher.has_pending());
1686 dispatcher.post(|| {});
1687 assert!(!dispatcher.has_pending());
1688 dispatcher.post_invoke(404, 12_u32);
1689 assert!(!dispatcher.has_pending());
1690 }
1691
1692 #[test]
1693 fn pending_guard_does_not_wrap_on_underflow() {
1694 let counter = AtomicUsize::new(0);
1695
1696 {
1697 let _guard = PendingGuard::new(&counter);
1698 }
1699
1700 assert_eq!(counter.load(Ordering::SeqCst), 0);
1701 }
1702
1703 #[test]
1704 fn pending_guard_decrements_pending_count() {
1705 let counter = AtomicUsize::new(2);
1706
1707 {
1708 let _guard = PendingGuard::new(&counter);
1709 }
1710
1711 assert_eq!(counter.load(Ordering::SeqCst), 1);
1712 }
1713
1714 #[test]
1715 fn schedule_frame_without_runtime_is_ignored() {
1716 let ok = std::thread::spawn(|| std::panic::catch_unwind(super::schedule_frame).is_ok())
1717 .join()
1718 .expect("test thread should join");
1719
1720 assert!(ok);
1721 }
1722
1723 #[test]
1724 fn schedule_node_update_without_runtime_is_ignored() {
1725 let ok = std::thread::spawn(|| {
1726 std::panic::catch_unwind(|| {
1727 super::schedule_node_update(|_| Ok(()));
1728 })
1729 .is_ok()
1730 })
1731 .join()
1732 .expect("test thread should join");
1733
1734 assert!(ok);
1735 }
1736}