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