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, 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 Drop for PendingGuard<'_> {
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_or(true, |tasks| tasks.contains_key(&id))
602 }
603
604 fn poll_async_tasks(&self) -> bool {
605 let order = std::mem::take(&mut *self.task_order.borrow_mut());
606 let mut pending = Vec::with_capacity(order.len());
607 let mut made_progress = false;
608 for id in order {
609 let task = {
610 let mut tasks = self.tasks.borrow_mut();
611 let Some(entry) = tasks.get_mut(&id) else {
612 continue;
613 };
614 if entry.runnable.swap(false, Ordering::AcqRel) {
615 entry
616 .future
617 .take()
618 .map(|future| (future, entry.waker.clone()))
619 } else {
620 None
621 }
622 };
623 let Some((mut future, waker)) = task else {
624 pending.push(id);
625 continue;
626 };
627 let mut cx = Context::from_waker(&waker);
628 match future.as_mut().poll(&mut cx) {
629 Poll::Ready(()) => {
630 self.cancel_task(id);
631 made_progress = true;
632 }
633 Poll::Pending => {
634 let mut tasks = self.tasks.borrow_mut();
635 if let Some(entry) = tasks.get_mut(&id) {
636 entry.future = Some(future);
637 pending.push(id);
638 } else {
639 drop(tasks);
640 drop(future);
641 }
642 }
643 }
644 }
645 if !pending.is_empty() {
646 pending.retain(|id| self.has_task(*id));
647 self.task_order.borrow_mut().extend(pending);
648 }
649 made_progress
650 }
651
652 fn drain_ui(&self) {
653 loop {
654 let mut executed = false;
655
656 {
657 let rx = &mut *self.ui_rx.borrow_mut();
658 for message in rx.try_iter() {
659 executed = true;
660 let _guard = PendingGuard::new(&self.ui_dispatcher.pending);
661 match message {
662 UiMessage::Task(task) => {
663 task();
664 }
665 UiMessage::Invoke { id, value } => {
666 self.invoke_ui_cont(id, value);
667 }
668 }
669 }
670 }
671
672 loop {
673 let task = {
674 let mut local = self.local_tasks.borrow_mut();
675 local.pop_front()
676 };
677
678 match task {
679 Some(task) => {
680 executed = true;
681 task();
682 }
683 None => break,
684 }
685 }
686
687 if self.poll_async_tasks() {
688 executed = true;
689 }
690
691 if !executed {
692 break;
693 }
694 }
695
696 self.clear_needs_frame_if_idle();
697 }
698
699 fn has_pending_ui(&self) -> bool {
700 let local_pending = self
701 .local_tasks
702 .try_borrow()
703 .map_or(true, |tasks| !tasks.is_empty());
704
705 local_pending || self.ui_dispatcher.has_pending() || self.has_runnable_tasks()
706 }
707
708 fn has_runnable_tasks(&self) -> bool {
709 self.tasks.try_borrow().map_or(true, |tasks| {
710 tasks
711 .values()
712 .any(|task| task.runnable.load(Ordering::Acquire))
713 })
714 }
715
716 fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> u64 {
717 debug_assert_eq!(
718 std::thread::current().id(),
719 self.ui_thread_id,
720 "UI continuation registered off the runtime thread",
721 );
722 let id = self.next_cont_id.get();
723 self.next_cont_id.set(id + 1);
724 let callback = RefCell::new(Some(f));
725 self.ui_conts.borrow_mut().insert(
726 id,
727 Box::new(move |value: Box<dyn Any>| {
728 let Ok(value) = value.downcast::<T>() else {
729 return false;
730 };
731 let Some(slot) = callback.borrow_mut().take() else {
732 return true;
733 };
734 slot(*value);
735 true
736 }),
737 );
738 id
739 }
740
741 fn invoke_ui_cont(&self, id: u64, value: Box<dyn Any + Send>) {
742 debug_assert_eq!(
743 std::thread::current().id(),
744 self.ui_thread_id,
745 "UI continuation invoked off the runtime thread",
746 );
747 let callback = { self.ui_conts.borrow_mut().remove(&id) };
748 if let Some(callback) = callback {
749 let value: Box<dyn Any> = value;
750 if !callback(value) {
751 self.ui_conts.borrow_mut().insert(id, callback);
752 }
753 }
754 }
755
756 fn cancel_ui_cont(&self, id: u64) {
757 let continuation = self.ui_conts.borrow_mut().remove(&id);
758 drop(continuation);
759 }
760
761 fn register_frame_callback(
762 &self,
763 kind: FrameCallbackKind,
764 callback: Box<dyn FnOnce(u64) + 'static>,
765 ) -> FrameCallbackId {
766 let id = self.next_frame_callback_id.get();
767 self.next_frame_callback_id.set(id + 1);
768 self.frame_callbacks
769 .borrow_mut()
770 .push_back(FrameCallbackEntry {
771 id,
772 kind,
773 callback: Some(callback),
774 });
775 self.schedule();
776 id
777 }
778
779 fn cancel_frame_callback(&self, id: FrameCallbackId) {
780 let removed = {
781 let mut callbacks = self.frame_callbacks.borrow_mut();
782 callbacks
783 .iter()
784 .position(|entry| entry.id == id)
785 .and_then(|index| callbacks.remove(index))
786 };
787 drop(removed);
788 self.clear_needs_frame_if_idle();
789 }
790
791 fn clear_needs_frame_if_idle(&self) {
792 if !self.has_invalid_scopes()
793 && !self.has_updates()
794 && !self.has_frame_callbacks()
795 && !self.has_pending_ui()
796 {
797 *self.needs_frame.borrow_mut() = false;
798 }
799 }
800
801 fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
802 self.last_frame_time_nanos.set(Some(frame_time_nanos));
803 let next_frame_id = self.next_frame_callback_id.get();
804 if self.has_frame_callbacks() {
805 let _ = crate::run_in_mutable_snapshot(|| {
806 loop {
807 let entry = {
808 let mut callbacks = self.frame_callbacks.borrow_mut();
809 if callbacks
810 .front()
811 .is_some_and(|entry| entry.id < next_frame_id)
812 {
813 callbacks.pop_front()
814 } else {
815 None
816 }
817 };
818 let Some(mut entry) = entry else {
819 break;
820 };
821 if let Some(callback) = entry.callback.take() {
822 callback(frame_time_nanos);
823 }
824 }
825 });
826 }
827
828 self.clear_needs_frame_if_idle();
829 }
830
831 fn debug_stats(&self) -> RuntimeDebugStats {
832 let node_updates = self.node_updates.borrow();
833 let invalid_scopes = self.invalid_scopes.borrow();
834 let scope_queue = self.scope_queue.borrow();
835 let frame_callbacks = self.frame_callbacks.borrow();
836 let local_tasks = self.local_tasks.borrow();
837 let ui_conts = self.ui_conts.borrow();
838 let tasks = self.tasks.borrow();
839 let external_state_owners = self.external_state_owners.borrow();
840
841 RuntimeDebugStats {
842 node_updates_len: node_updates.len(),
843 node_updates_cap: node_updates.capacity(),
844 invalid_scopes_len: invalid_scopes.len(),
845 invalid_scopes_cap: invalid_scopes.capacity(),
846 scope_queue_len: scope_queue.len(),
847 scope_queue_cap: scope_queue.capacity(),
848 frame_callbacks_len: frame_callbacks.len(),
849 frame_callbacks_cap: frame_callbacks.capacity(),
850 local_tasks_len: local_tasks.len(),
851 local_tasks_cap: local_tasks.capacity(),
852 ui_conts_len: ui_conts.len(),
853 ui_conts_cap: ui_conts.capacity(),
854 tasks_len: tasks.len(),
855 tasks_cap: tasks.capacity(),
856 external_state_owners_len: external_state_owners.len(),
857 external_state_owners_cap: external_state_owners.capacity(),
858 ui_dispatcher_pending: self.ui_dispatcher.pending.load(Ordering::SeqCst),
859 }
860 }
861}
862
863#[derive(Clone)]
864pub struct Runtime {
865 inner: Rc<RuntimeInner>,
866}
867
868impl Runtime {
869 pub fn new(scheduler: SchedulerRef) -> Self {
870 let inner = Rc::new(RuntimeInner::new(scheduler));
871 let runtime = Self { inner };
872 let handle = runtime.handle();
873 register_runtime_handle(&handle);
874 LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle));
875 runtime
876 }
877
878 pub fn handle(&self) -> RuntimeHandle {
879 RuntimeHandle {
880 inner: Rc::downgrade(&self.inner),
881 dispatcher: UiDispatcher::new(self.inner.ui_dispatcher.clone()),
882 ui_thread_id: self.inner.ui_thread_id,
883 id: self.inner.runtime_id,
884 }
885 }
886
887 pub fn has_updates(&self) -> bool {
888 self.inner.has_updates()
889 }
890
891 pub fn needs_frame(&self) -> bool {
892 *self.inner.needs_frame.borrow() || self.inner.has_runnable_tasks()
893 }
894
895 pub fn set_needs_frame(&self, value: bool) {
896 *self.inner.needs_frame.borrow_mut() = value;
897 }
898
899 pub fn last_frame_time_nanos(&self) -> Option<u64> {
904 self.inner.last_frame_time_nanos.get()
905 }
906
907 #[cfg(any(feature = "internal", test))]
908 pub fn frame_clock(&self) -> FrameClock {
909 FrameClock::new(self.handle())
910 }
911}
912
913impl Drop for Runtime {
914 fn drop(&mut self) {
915 if Rc::strong_count(&self.inner) != 1 {
916 return;
917 }
918 unregister_runtime_handle(self.inner.runtime_id);
919 LAST_RUNTIME.with(|slot| {
920 let should_clear = slot
921 .borrow()
922 .as_ref()
923 .is_some_and(|handle| handle.id() == self.inner.runtime_id);
924 if should_clear {
925 *slot.borrow_mut() = None;
926 }
927 });
928 }
929}
930
931#[derive(Default)]
932pub struct DefaultScheduler;
933
934impl RuntimeScheduler for DefaultScheduler {
935 fn schedule_frame(&self) {}
936}
937
938#[cfg(test)]
939#[derive(Default)]
940pub struct TestScheduler;
941
942#[cfg(test)]
943impl RuntimeScheduler for TestScheduler {
944 fn schedule_frame(&self) {}
945}
946
947#[cfg(test)]
948pub struct TestRuntime {
949 runtime: Runtime,
950}
951
952#[cfg(test)]
953impl Default for TestRuntime {
954 fn default() -> Self {
955 Self::new()
956 }
957}
958
959#[cfg(test)]
960impl TestRuntime {
961 pub fn new() -> Self {
962 Self {
963 runtime: Runtime::new(Arc::new(TestScheduler)),
964 }
965 }
966
967 pub fn handle(&self) -> RuntimeHandle {
968 self.runtime.handle()
969 }
970}
971
972#[derive(Clone)]
973pub struct RuntimeHandle {
974 inner: Weak<RuntimeInner>,
975 dispatcher: UiDispatcher,
976 ui_thread_id: ThreadId,
977 id: RuntimeId,
978}
979
980pub struct TaskHandle {
981 id: u64,
982 runtime: RuntimeHandle,
983}
984
985struct DeferredStateRelease {
986 runtime: RuntimeHandle,
987 id: StateId,
988}
989
990pub(crate) struct StateHandleLease {
991 id: StateId,
992 runtime: RuntimeHandle,
993}
994
995impl StateHandleLease {
996 pub(crate) fn id(&self) -> StateId {
997 self.id
998 }
999
1000 pub(crate) fn runtime(&self) -> RuntimeHandle {
1001 self.runtime.clone()
1002 }
1003}
1004
1005impl Drop for StateHandleLease {
1006 fn drop(&mut self) {
1007 defer_state_release(self.runtime.clone(), self.id);
1008 }
1009}
1010
1011thread_local! {
1012 static STATE_OWNERS: RefCell<Vec<Vec<Rc<StateHandleLease>>>> =
1013 const { RefCell::new(Vec::new()) };
1014}
1015
1016struct StateOwnerFrame;
1017
1018impl Drop for StateOwnerFrame {
1019 fn drop(&mut self) {
1020 STATE_OWNERS.with(|owners| owners.borrow_mut().pop());
1021 }
1022}
1023
1024pub(crate) fn collecting_states<T>(build: impl FnOnce() -> T) -> (T, Vec<Rc<StateHandleLease>>) {
1042 STATE_OWNERS.with(|owners| owners.borrow_mut().push(Vec::new()));
1043 let frame = StateOwnerFrame;
1044 let value = build();
1045 let states = STATE_OWNERS.with(|owners| {
1046 owners
1047 .borrow_mut()
1048 .last_mut()
1049 .map(std::mem::take)
1050 .unwrap_or_default()
1051 });
1052 drop(frame);
1053 (value, states)
1054}
1055
1056fn hand_to_current_owner(lease: &Rc<StateHandleLease>) -> bool {
1057 STATE_OWNERS.with(|owners| match owners.borrow_mut().last_mut() {
1058 Some(owner) => {
1059 owner.push(Rc::clone(lease));
1060 true
1061 }
1062 None => false,
1063 })
1064}
1065
1066impl RuntimeHandle {
1067 pub fn id(&self) -> RuntimeId {
1068 self.id
1069 }
1070
1071 pub(crate) fn alloc_state<T: Clone + 'static>(&self, value: T) -> Rc<StateHandleLease> {
1072 let id = self.with_state_arena(|arena| arena.alloc(value, self.clone()));
1073 let lease = Rc::new(StateHandleLease {
1074 id,
1075 runtime: self.clone(),
1076 });
1077 self.with_state_arena(|arena| arena.register_lease(id, &lease));
1078 lease
1079 }
1080
1081 pub(crate) fn alloc_state_with_policy<T: Clone + 'static>(
1082 &self,
1083 value: T,
1084 policy: Arc<dyn MutationPolicy<T>>,
1085 ) -> Rc<StateHandleLease> {
1086 let id =
1087 self.with_state_arena(|arena| arena.alloc_with_policy(value, self.clone(), policy));
1088 let lease = Rc::new(StateHandleLease {
1089 id,
1090 runtime: self.clone(),
1091 });
1092 self.with_state_arena(|arena| arena.register_lease(id, &lease));
1093 lease
1094 }
1095
1096 pub(crate) fn alloc_persistent_state<T: Clone + 'static>(
1097 &self,
1098 value: T,
1099 ) -> crate::MutableState<T> {
1100 self.hand_out(self.alloc_state(value))
1101 }
1102
1103 pub(crate) fn alloc_persistent_state_with_policy<T: Clone + 'static>(
1104 &self,
1105 value: T,
1106 policy: Arc<dyn MutationPolicy<T>>,
1107 ) -> crate::MutableState<T> {
1108 self.hand_out(self.alloc_state_with_policy(value, policy))
1109 }
1110
1111 fn hand_out<T: Clone + 'static>(&self, lease: Rc<StateHandleLease>) -> crate::MutableState<T> {
1112 if !hand_to_current_owner(&lease)
1113 && let Some(inner) = self.inner.upgrade()
1114 {
1115 inner
1116 .external_state_owners
1117 .borrow_mut()
1118 .insert(lease.id(), Rc::clone(&lease));
1119 }
1120 crate::MutableState::from_lease(&lease)
1121 }
1122
1123 pub(crate) fn retain_state_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
1124 self.with_state_arena(|arena| arena.retain_lease(id))
1125 }
1126
1127 pub(crate) fn with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> R {
1128 self.try_with_state_arena(f)
1129 .unwrap_or_else(|| panic!("runtime dropped"))
1130 }
1131
1132 pub(crate) fn try_with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> Option<R> {
1133 self.inner.upgrade().map(|inner| f(&inner.state_arena))
1134 }
1135
1136 fn release_state_immediate(&self, id: StateId) {
1137 if let Some(inner) = self.inner.upgrade() {
1138 inner.state_arena.release(id);
1139 }
1140 }
1141
1142 pub fn state_arena_stats(&self) -> (usize, usize) {
1143 self.try_with_state_arena(StateArena::stats)
1144 .unwrap_or_default()
1145 }
1146
1147 pub fn state_arena_debug_stats(&self) -> StateArenaDebugStats {
1148 self.try_with_state_arena(StateArena::debug_stats)
1149 .unwrap_or_default()
1150 }
1151
1152 pub fn debug_stats(&self) -> RuntimeDebugStats {
1153 self.inner
1154 .upgrade()
1155 .map(|inner| inner.debug_stats())
1156 .unwrap_or_default()
1157 }
1158
1159 pub fn live_ui_task_labels(&self) -> Vec<(u64, String)> {
1160 self.inner
1161 .upgrade()
1162 .map(|inner| {
1163 inner
1164 .tasks
1165 .borrow()
1166 .iter()
1167 .map(|(id, entry)| (*id, entry.label.clone()))
1168 .collect()
1169 })
1170 .unwrap_or_default()
1171 }
1172
1173 pub(crate) fn unregister_state_scope(&self, id: StateId, scope_id: ScopeId) {
1174 if let Some(inner) = self.inner.upgrade() {
1175 inner.state_arena.unregister_scope(id, scope_id);
1176 }
1177 }
1178
1179 pub fn schedule(&self) {
1180 if let Some(inner) = self.inner.upgrade() {
1181 inner.schedule();
1182 }
1183 }
1184
1185 pub(crate) fn enqueue_node_update(&self, command: Command) {
1186 if let Some(inner) = self.inner.upgrade() {
1187 inner.enqueue_update(command);
1188 }
1189 }
1190
1191 pub fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
1198 if let Some(inner) = self.inner.upgrade() {
1199 inner.enqueue_ui_task(task);
1200 } else {
1201 task();
1202 }
1203 }
1204
1205 pub fn spawn_ui<F>(&self, fut: F) -> Option<TaskHandle>
1206 where
1207 F: Future<Output = ()> + 'static,
1208 {
1209 self.inner.upgrade().map(|inner| {
1210 let id = inner.spawn_ui_task(Box::pin(fut));
1211 TaskHandle {
1212 id,
1213 runtime: self.clone(),
1214 }
1215 })
1216 }
1217
1218 pub fn cancel_task(&self, id: u64) {
1219 if let Some(inner) = self.inner.upgrade() {
1220 inner.cancel_task(id);
1221 }
1222 }
1223
1224 pub fn has_task(&self, id: u64) -> bool {
1226 self.inner.upgrade().is_some_and(|inner| inner.has_task(id))
1227 }
1228
1229 pub fn post_ui(&self, task: impl FnOnce() + Send + 'static) {
1234 self.dispatcher.post(task);
1235 }
1236
1237 pub fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> Option<u64> {
1238 self.inner.upgrade().map(|inner| inner.register_ui_cont(f))
1239 }
1240
1241 pub fn cancel_ui_cont(&self, id: u64) {
1242 if let Some(inner) = self.inner.upgrade() {
1243 inner.cancel_ui_cont(id);
1244 }
1245 }
1246
1247 pub fn drain_ui(&self) {
1248 if let Some(inner) = self.inner.upgrade() {
1249 inner.drain_ui();
1250 }
1251 }
1252
1253 pub fn has_pending_ui(&self) -> bool {
1254 self.inner.upgrade().map_or_else(
1255 || self.dispatcher.has_pending(),
1256 |inner| inner.has_pending_ui(),
1257 )
1258 }
1259
1260 pub fn register_frame_callback(
1261 &self,
1262 callback: impl FnOnce(u64) + 'static,
1263 ) -> Option<FrameCallbackId> {
1264 self.inner.upgrade().map(|inner| {
1265 inner.register_frame_callback(FrameCallbackKind::Transient, Box::new(callback))
1266 })
1267 }
1268
1269 pub fn register_perpetual_frame_callback(
1270 &self,
1271 callback: impl FnOnce(u64) + 'static,
1272 ) -> Option<FrameCallbackId> {
1273 self.inner.upgrade().map(|inner| {
1274 inner.register_frame_callback(FrameCallbackKind::Perpetual, Box::new(callback))
1275 })
1276 }
1277
1278 pub fn cancel_frame_callback(&self, id: FrameCallbackId) {
1279 if let Some(inner) = self.inner.upgrade() {
1280 inner.cancel_frame_callback(id);
1281 }
1282 }
1283
1284 pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1285 if let Some(inner) = self.inner.upgrade() {
1286 inner.drain_frame_callbacks(frame_time_nanos);
1287 }
1288 }
1289
1290 pub fn last_frame_time_nanos(&self) -> Option<u64> {
1293 self.inner
1294 .upgrade()
1295 .and_then(|inner| inner.last_frame_time_nanos.get())
1296 }
1297
1298 #[cfg(any(feature = "internal", test))]
1299 pub fn frame_clock(&self) -> FrameClock {
1300 FrameClock::new(self.clone())
1301 }
1302
1303 pub fn set_needs_frame(&self, value: bool) {
1304 if let Some(inner) = self.inner.upgrade() {
1305 *inner.needs_frame.borrow_mut() = value;
1306 }
1307 }
1308
1309 pub(crate) fn take_updates(&self) -> Vec<Command> {
1310 self.inner
1311 .upgrade()
1312 .map(|inner| inner.take_updates())
1313 .unwrap_or_default()
1314 }
1315
1316 pub fn has_updates(&self) -> bool {
1317 self.inner
1318 .upgrade()
1319 .is_some_and(|inner| inner.has_updates())
1320 }
1321
1322 pub(crate) fn mark_scope_recomposed(&self, id: ScopeId) {
1323 if let Some(inner) = self.inner.upgrade() {
1324 inner.mark_scope_recomposed(id);
1325 }
1326 }
1327
1328 pub(crate) fn register_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
1329 if let Some(inner) = self.inner.upgrade() {
1330 inner.register_invalid_scope(id, scope);
1331 }
1332 }
1333
1334 pub(crate) fn requeue_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
1335 if let Some(inner) = self.inner.upgrade() {
1336 inner.requeue_invalid_scope(id, scope);
1337 }
1338 }
1339
1340 pub(crate) fn take_invalidated_scopes(&self) -> Vec<(ScopeId, Weak<RecomposeScopeInner>)> {
1341 self.inner
1342 .upgrade()
1343 .map(|inner| inner.take_invalidated_scopes())
1344 .unwrap_or_default()
1345 }
1346
1347 pub fn forget_movable(&self, id: Key) {
1351 if let Some(inner) = self.inner.upgrade() {
1352 inner.forgotten_movables.borrow_mut().push(id);
1353 inner.schedule();
1354 }
1355 }
1356
1357 pub(crate) fn take_forgotten_movables(&self) -> Vec<Key> {
1358 self.inner
1359 .upgrade()
1360 .map(|inner| std::mem::take(&mut *inner.forgotten_movables.borrow_mut()))
1361 .unwrap_or_default()
1362 }
1363
1364 pub(crate) fn next_movable_content_id(&self) -> Key {
1369 let Some(inner) = self.inner.upgrade() else {
1370 log::error!("movable content asked a runtime that is gone for an identity");
1371 return 0;
1372 };
1373 let id = inner.next_movable_content_id.get();
1374 inner.next_movable_content_id.set(id.wrapping_add(1).max(1));
1375 id
1376 }
1377
1378 pub fn has_invalid_scopes(&self) -> bool {
1379 self.inner
1380 .upgrade()
1381 .is_some_and(|inner| inner.has_invalid_scopes())
1382 }
1383
1384 pub(crate) fn increment_live_recompose_scope_count(&self) {
1385 if let Some(inner) = self.inner.upgrade() {
1386 inner.increment_live_recompose_scope_count();
1387 }
1388 }
1389
1390 pub(crate) fn decrement_live_recompose_scope_count(&self) {
1391 if let Some(inner) = self.inner.upgrade() {
1392 inner.decrement_live_recompose_scope_count();
1393 }
1394 }
1395
1396 fn live_recompose_scope_count(&self) -> usize {
1397 self.inner
1398 .upgrade()
1399 .map(|inner| inner.live_recompose_scope_count())
1400 .unwrap_or_default()
1401 }
1402
1403 #[doc(hidden)]
1404 pub fn debug_invalid_scope_ids(&self) -> Vec<usize> {
1405 self.inner
1406 .upgrade()
1407 .map(|inner| inner.invalid_scopes.borrow().iter().copied().collect())
1408 .unwrap_or_default()
1409 }
1410
1411 pub fn has_frame_callbacks(&self) -> bool {
1412 self.inner
1413 .upgrade()
1414 .is_some_and(|inner| inner.has_frame_callbacks())
1415 }
1416
1417 pub fn has_transient_frame_callbacks(&self) -> bool {
1418 self.inner
1419 .upgrade()
1420 .is_some_and(|inner| inner.has_transient_frame_callbacks())
1421 }
1422
1423 pub fn assert_ui_thread(&self) {
1424 debug_assert_eq!(
1425 std::thread::current().id(),
1426 self.ui_thread_id,
1427 "state mutated off the runtime's UI thread"
1428 );
1429 }
1430
1431 pub fn dispatcher(&self) -> UiDispatcher {
1432 self.dispatcher.clone()
1433 }
1434
1435 #[doc(hidden)]
1436 pub fn with_deferred_state_releases<R>(&self, f: impl FnOnce() -> R) -> R {
1437 let _scope = enter_state_teardown_scope();
1438 f()
1439 }
1440}
1441
1442impl TaskHandle {
1443 pub fn cancel(&self) {
1444 self.runtime.cancel_task(self.id);
1445 }
1446
1447 pub fn is_finished(&self) -> bool {
1449 !self.runtime.has_task(self.id)
1450 }
1451}
1452
1453pub(crate) struct FrameCallbackEntry {
1454 id: FrameCallbackId,
1455 kind: FrameCallbackKind,
1456 callback: Option<Box<dyn FnOnce(u64) + 'static>>,
1457}
1458
1459#[cfg(not(target_arch = "wasm32"))]
1460struct RuntimeTaskWaker {
1461 scheduler: SchedulerRef,
1462 runnable: Arc<AtomicBool>,
1463}
1464
1465#[cfg(target_arch = "wasm32")]
1466struct RuntimeTaskWaker {
1467 runtime_id: RuntimeId,
1468 runnable: Arc<AtomicBool>,
1469}
1470
1471impl RuntimeTaskWaker {
1472 #[cfg(not(target_arch = "wasm32"))]
1473 fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1474 let scheduler = inner.scheduler.clone();
1475 Self {
1476 scheduler,
1477 runnable,
1478 }
1479 }
1480
1481 #[cfg(target_arch = "wasm32")]
1482 fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1483 let runtime_id = inner.runtime_id;
1484 Self {
1485 runtime_id,
1486 runnable,
1487 }
1488 }
1489
1490 fn into_waker(self) -> Waker {
1491 futures_task::waker(Arc::new(self))
1492 }
1493}
1494
1495impl futures_task::ArcWake for RuntimeTaskWaker {
1496 #[cfg(not(target_arch = "wasm32"))]
1497 fn wake_by_ref(arc_self: &Arc<Self>) {
1498 arc_self.runnable.store(true, Ordering::Release);
1499 arc_self.scheduler.schedule_frame();
1500 }
1501
1502 #[cfg(target_arch = "wasm32")]
1503 fn wake_by_ref(arc_self: &Arc<Self>) {
1504 arc_self.runnable.store(true, Ordering::Release);
1505 REGISTERED_RUNTIMES.with(|registry| {
1506 if let Some(handle) = registry.borrow().get(&arc_self.runtime_id).cloned() {
1507 handle.schedule();
1508 }
1509 });
1510 }
1511}
1512
1513thread_local! {
1514 static NEXT_RUNTIME_ID: Cell<u32> = const { Cell::new(1) };
1515 static ACTIVE_RUNTIMES: RefCell<Vec<RuntimeHandle>> = const { RefCell::new(Vec::new()) };
1516 static LAST_RUNTIME: RefCell<Option<RuntimeHandle>> = const { RefCell::new(None) };
1517 static REGISTERED_RUNTIMES: RefCell<HashMap<RuntimeId, RuntimeHandle>> = RefCell::new(HashMap::default());
1518 static STATE_TEARDOWN_DEPTH: Cell<usize> = const { Cell::new(0) };
1519 static DEFERRED_STATE_RELEASES: RefCell<Vec<DeferredStateRelease>> = const { RefCell::new(Vec::new()) };
1520}
1521
1522#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1523pub struct RuntimeThreadLocalDebugStats {
1524 pub active_runtimes_len: usize,
1525 pub active_runtimes_cap: usize,
1526 pub registered_runtimes_len: usize,
1527 pub registered_runtimes_cap: usize,
1528 pub deferred_state_releases_len: usize,
1529 pub deferred_state_releases_cap: usize,
1530}
1531
1532pub fn current_runtime_handle() -> Option<RuntimeHandle> {
1537 if let Some(handle) = ACTIVE_RUNTIMES.with(|stack| stack.borrow().last().cloned()) {
1538 return Some(handle);
1539 }
1540 LAST_RUNTIME.with(|slot| slot.borrow().clone())
1541}
1542
1543pub(crate) fn runtime_handle_by_id(id: RuntimeId) -> Option<RuntimeHandle> {
1544 REGISTERED_RUNTIMES.with(|registry| registry.borrow().get(&id).cloned())
1545}
1546
1547pub(crate) fn live_recompose_scope_count() -> usize {
1548 REGISTERED_RUNTIMES.with(|registry| {
1549 registry
1550 .borrow()
1551 .values()
1552 .map(RuntimeHandle::live_recompose_scope_count)
1553 .sum()
1554 })
1555}
1556
1557pub fn debug_runtime_thread_local_stats() -> RuntimeThreadLocalDebugStats {
1558 let (active_runtimes_len, active_runtimes_cap) = ACTIVE_RUNTIMES.with(|stack| {
1559 let stack = stack.borrow();
1560 (stack.len(), stack.capacity())
1561 });
1562 let (registered_runtimes_len, registered_runtimes_cap) = REGISTERED_RUNTIMES.with(|registry| {
1563 let registry = registry.borrow();
1564 (registry.len(), registry.capacity())
1565 });
1566 let (deferred_state_releases_len, deferred_state_releases_cap) =
1567 DEFERRED_STATE_RELEASES.with(|releases| {
1568 let releases = releases.borrow();
1569 (releases.len(), releases.capacity())
1570 });
1571
1572 RuntimeThreadLocalDebugStats {
1573 active_runtimes_len,
1574 active_runtimes_cap,
1575 registered_runtimes_len,
1576 registered_runtimes_cap,
1577 deferred_state_releases_len,
1578 deferred_state_releases_cap,
1579 }
1580}
1581
1582fn register_runtime_handle(handle: &RuntimeHandle) {
1583 REGISTERED_RUNTIMES.with(|registry| {
1584 registry.borrow_mut().insert(handle.id(), handle.clone());
1585 });
1586}
1587
1588fn unregister_runtime_handle(id: RuntimeId) {
1589 REGISTERED_RUNTIMES.with(|registry| {
1590 registry.borrow_mut().remove(&id);
1591 });
1592}
1593
1594fn defer_state_release(runtime: RuntimeHandle, id: StateId) {
1595 let teardown_active = STATE_TEARDOWN_DEPTH.with(|depth| depth.get() > 0);
1596 if teardown_active {
1597 DEFERRED_STATE_RELEASES.with(|releases| {
1598 releases
1599 .borrow_mut()
1600 .push(DeferredStateRelease { runtime, id });
1601 });
1602 } else {
1603 runtime.release_state_immediate(id);
1604 }
1605}
1606
1607fn flush_deferred_state_releases() {
1608 DEFERRED_STATE_RELEASES.with(|releases| {
1609 let mut releases = releases.borrow_mut();
1610 while let Some(deferred) = releases.pop() {
1611 deferred.runtime.release_state_immediate(deferred.id);
1612 }
1613 });
1614}
1615
1616pub(crate) struct StateTeardownScope;
1617
1618pub(crate) fn enter_state_teardown_scope() -> StateTeardownScope {
1619 STATE_TEARDOWN_DEPTH.with(|depth| depth.set(depth.get() + 1));
1620 StateTeardownScope
1621}
1622
1623impl Drop for StateTeardownScope {
1624 fn drop(&mut self) {
1625 STATE_TEARDOWN_DEPTH.with(|depth| {
1626 let next = depth.get().saturating_sub(1);
1627 depth.set(next);
1628 if next == 0 {
1629 flush_deferred_state_releases();
1630 }
1631 });
1632 }
1633}
1634
1635pub(crate) fn push_active_runtime(handle: &RuntimeHandle) {
1636 register_runtime_handle(handle);
1637 ACTIVE_RUNTIMES.with(|stack| stack.borrow_mut().push(handle.clone()));
1638 LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle.clone()));
1639}
1640
1641pub(crate) fn pop_active_runtime() {
1642 ACTIVE_RUNTIMES.with(|stack| {
1643 stack.borrow_mut().pop();
1644 });
1645}
1646
1647pub fn schedule_frame() {
1649 if let Some(handle) = current_runtime_handle() {
1650 handle.schedule();
1651 return;
1652 }
1653 log::debug!(
1654 target: "cranpose::runtime",
1655 "ignoring frame request without an active runtime",
1656 );
1657}
1658
1659pub fn schedule_node_update(
1661 update: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1662) {
1663 if let Some(handle) = current_runtime_handle() {
1664 handle.enqueue_node_update(Command::callback(update));
1665 } else {
1666 drop(update);
1667 log::debug!(
1668 target: "cranpose::runtime",
1669 "ignoring node update request without an active runtime",
1670 );
1671 }
1672}
1673
1674#[cfg(test)]
1675#[path = "tests/runtime_tests.rs"]
1676mod tests;