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