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};
16
17#[cfg(any(feature = "internal", test))]
18use crate::frame_clock::FrameClock;
19use crate::{
20 Applier, Command, FrameCallbackId, Key, MutableStateInner, NodeError, RecomposeScope,
21 RecomposeScopeInner, ScopeId,
22 collections::map::HashMap,
23 platform::{RuntimeScheduler, SchedulerRef},
24 state::{MutationPolicy, NeverEqual},
25 thread_state,
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 thread_state::runtimes(|state| {
305 let id = state.next_runtime_id.get();
306 state.next_runtime_id.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
445pub fn label_next_ui_task(label: impl Into<String>) {
446 thread_state::watch_thread_exit();
447 let label = label.into();
448 thread_state::runtimes(|state| *state.next_task_label.borrow_mut() = Some(label));
449}
450
451impl RuntimeInner {
452 fn new(scheduler: SchedulerRef) -> Self {
453 let (tx, rx) = mpsc::channel();
454 let dispatcher = UiDispatcherRef::new(UiDispatcherInner::new(scheduler.clone(), tx));
455 Self {
456 scheduler,
457 needs_frame: RefCell::new(false),
458 node_updates: RefCell::new(Vec::new()),
459 invalid_scope_count: Cell::new(0),
460 scope_queue: RefCell::new(Vec::new()),
461 frame_callbacks: RefCell::new(VecDeque::new()),
462 next_frame_callback_id: Cell::new(1),
463 last_frame_time_nanos: Cell::new(None),
464 ui_dispatcher: dispatcher,
465 ui_rx: RefCell::new(rx),
466 local_tasks: RefCell::new(VecDeque::new()),
467 ui_conts: RefCell::new(UiContinuationMap::default()),
468 next_cont_id: Cell::new(1),
469 ui_thread_id: std::thread::current().id(),
470 tasks: RefCell::new(HashMap::default()),
471 task_order: RefCell::new(Vec::new()),
472 next_task_id: Cell::new(1),
473 state_arena: StateArena::default(),
474 external_state_owners: RefCell::new(HashMap::default()),
475 live_recompose_scope_count: Cell::new(0),
476 forgotten_movables: RefCell::new(Vec::new()),
477 next_movable_content_id: Cell::new(1),
478 runtime_id: RuntimeId::next(),
479 }
480 }
481
482 fn schedule(&self) {
483 *self.needs_frame.borrow_mut() = true;
484 self.scheduler.schedule_frame();
485 }
486
487 fn enqueue_update(&self, command: Command) {
488 self.node_updates.borrow_mut().push(command);
489 self.schedule();
490 }
491
492 fn take_updates(&self) -> Vec<Command> {
493 self.node_updates.borrow_mut().drain(..).collect::<Vec<_>>()
494 }
495
496 fn has_updates(&self) -> bool {
497 !self.node_updates.borrow().is_empty() || self.has_invalid_scopes()
498 }
499
500 fn register_invalid_scope(&self, scope: Weak<RecomposeScopeInner>) {
501 self.invalid_scope_count
502 .set(self.invalid_scope_count.get() + 1);
503 self.scope_queue.borrow_mut().push(scope);
504 self.schedule();
505 }
506
507 fn requeue_invalid_scope(&self, scope: &RecomposeScope) {
508 if scope.is_enqueued() {
509 self.scope_queue.borrow_mut().push(scope.downgrade());
510 self.schedule();
511 }
512 }
513
514 fn mark_scope_recomposed(&self) {
515 self.invalid_scope_count
516 .set(self.invalid_scope_count.get().saturating_sub(1));
517 }
518
519 fn take_invalidated_scopes(&self) -> Option<Vec<RecomposeScope>> {
520 let mut pending = std::mem::take(&mut *self.scope_queue.borrow_mut());
521 if pending.is_empty() {
522 return None;
523 }
524 let scopes: Vec<RecomposeScope> = pending
525 .iter()
526 .filter_map(RecomposeScope::upgrade)
527 .filter(RecomposeScope::is_enqueued)
528 .collect();
529 pending.clear();
530 let mut queue = self.scope_queue.borrow_mut();
531 if queue.is_empty() {
532 std::mem::swap(&mut *queue, &mut pending);
533 }
534 drop(queue);
535 (!scopes.is_empty()).then_some(scopes)
536 }
537
538 fn has_invalid_scopes(&self) -> bool {
539 self.invalid_scope_count.get() != 0
540 }
541
542 fn queued_invalid_scope_ids(&self) -> Vec<ScopeId> {
543 let mut ids: Vec<ScopeId> = self
544 .scope_queue
545 .borrow()
546 .iter()
547 .filter_map(RecomposeScope::upgrade)
548 .filter(RecomposeScope::is_enqueued)
549 .map(|scope| scope.id())
550 .collect();
551 ids.sort_unstable();
552 ids.dedup();
553 ids
554 }
555
556 fn increment_live_recompose_scope_count(&self) {
557 self.live_recompose_scope_count
558 .set(self.live_recompose_scope_count.get().saturating_add(1));
559 }
560
561 fn decrement_live_recompose_scope_count(&self) {
562 self.live_recompose_scope_count
563 .set(self.live_recompose_scope_count.get().saturating_sub(1));
564 }
565
566 fn live_recompose_scope_count(&self) -> usize {
567 self.live_recompose_scope_count.get()
568 }
569
570 fn has_frame_callbacks(&self) -> bool {
571 !self.frame_callbacks.borrow().is_empty()
572 }
573
574 fn has_transient_frame_callbacks(&self) -> bool {
575 self.frame_callbacks
576 .borrow()
577 .iter()
578 .any(|entry| entry.kind == FrameCallbackKind::Transient)
579 }
580
581 fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
582 self.local_tasks.borrow_mut().push_back(task);
583 self.schedule();
584 }
585
586 fn spawn_ui_task(&self, future: Pin<Box<dyn Future<Output = ()> + 'static>>) -> u64 {
587 let id = self.next_task_id.get();
588 self.next_task_id.set(id + 1);
589 let label = thread_state::runtimes(|state| state.next_task_label.borrow_mut().take())
590 .unwrap_or_else(|| "unnamed".to_string());
591 let runnable = Arc::new(AtomicBool::new(true));
592 let waker = RuntimeTaskWaker::new(self, Arc::clone(&runnable)).into_waker();
593 self.tasks.borrow_mut().insert(
594 id,
595 TaskEntry {
596 label,
597 future: Some(future),
598 runnable,
599 waker,
600 },
601 );
602 self.task_order.borrow_mut().push(id);
603 self.schedule();
604 id
605 }
606
607 fn cancel_task(&self, id: u64) {
608 let task = self.tasks.borrow_mut().remove(&id);
609 self.task_order.borrow_mut().retain(|queued| *queued != id);
610 drop(task);
611 }
612
613 fn has_task(&self, id: u64) -> bool {
614 self.tasks
615 .try_borrow()
616 .map_or(true, |tasks| tasks.contains_key(&id))
617 }
618
619 fn poll_async_tasks(&self) -> bool {
620 let order = std::mem::take(&mut *self.task_order.borrow_mut());
621 let mut pending = Vec::with_capacity(order.len());
622 let mut made_progress = false;
623 for id in order {
624 let task = {
625 let mut tasks = self.tasks.borrow_mut();
626 let Some(entry) = tasks.get_mut(&id) else {
627 continue;
628 };
629 if entry.runnable.swap(false, Ordering::AcqRel) {
630 entry
631 .future
632 .take()
633 .map(|future| (future, entry.waker.clone()))
634 } else {
635 None
636 }
637 };
638 let Some((mut future, waker)) = task else {
639 pending.push(id);
640 continue;
641 };
642 let mut cx = Context::from_waker(&waker);
643 match future.as_mut().poll(&mut cx) {
644 Poll::Ready(()) => {
645 self.cancel_task(id);
646 made_progress = true;
647 }
648 Poll::Pending => {
649 let mut tasks = self.tasks.borrow_mut();
650 if let Some(entry) = tasks.get_mut(&id) {
651 entry.future = Some(future);
652 pending.push(id);
653 } else {
654 drop(tasks);
655 drop(future);
656 }
657 }
658 }
659 }
660 if !pending.is_empty() {
661 pending.retain(|id| self.has_task(*id));
662 self.task_order.borrow_mut().extend(pending);
663 }
664 made_progress
665 }
666
667 fn drain_ui(&self) {
668 loop {
669 let mut executed = false;
670
671 {
672 let rx = &mut *self.ui_rx.borrow_mut();
673 for message in rx.try_iter() {
674 executed = true;
675 let _guard = PendingGuard::new(&self.ui_dispatcher.pending);
676 match message {
677 UiMessage::Task(task) => {
678 task();
679 }
680 UiMessage::Invoke { id, value } => {
681 self.invoke_ui_cont(id, value);
682 }
683 }
684 }
685 }
686
687 if self.run_local_tasks() {
688 executed = true;
689 }
690
691 if self.poll_async_tasks() {
692 executed = true;
693 }
694 self.run_local_tasks();
701
702 if !executed {
703 break;
704 }
705 }
706
707 self.clear_needs_frame_if_idle();
708 }
709
710 fn run_local_tasks(&self) -> bool {
711 let mut ran = false;
712 loop {
713 let task = self.local_tasks.borrow_mut().pop_front();
714 let Some(task) = task else {
715 return ran;
716 };
717 ran = true;
718 task();
719 }
720 }
721
722 fn has_pending_ui(&self) -> bool {
723 let local_pending = self
724 .local_tasks
725 .try_borrow()
726 .map_or(true, |tasks| !tasks.is_empty());
727
728 local_pending || self.ui_dispatcher.has_pending() || self.has_runnable_tasks()
729 }
730
731 fn has_runnable_tasks(&self) -> bool {
732 self.tasks.try_borrow().map_or(true, |tasks| {
733 tasks
734 .values()
735 .any(|task| task.runnable.load(Ordering::Acquire))
736 })
737 }
738
739 fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> u64 {
740 debug_assert_eq!(
741 std::thread::current().id(),
742 self.ui_thread_id,
743 "UI continuation registered off the runtime thread",
744 );
745 let id = self.next_cont_id.get();
746 self.next_cont_id.set(id + 1);
747 let callback = RefCell::new(Some(f));
748 self.ui_conts.borrow_mut().insert(
749 id,
750 Box::new(move |value: Box<dyn Any>| {
751 let Ok(value) = value.downcast::<T>() else {
752 return false;
753 };
754 let Some(slot) = callback.borrow_mut().take() else {
755 return true;
756 };
757 slot(*value);
758 true
759 }),
760 );
761 id
762 }
763
764 fn invoke_ui_cont(&self, id: u64, value: Box<dyn Any + Send>) {
765 debug_assert_eq!(
766 std::thread::current().id(),
767 self.ui_thread_id,
768 "UI continuation invoked off the runtime thread",
769 );
770 let callback = { self.ui_conts.borrow_mut().remove(&id) };
771 if let Some(callback) = callback {
772 let value: Box<dyn Any> = value;
773 if !callback(value) {
774 self.ui_conts.borrow_mut().insert(id, callback);
775 }
776 }
777 }
778
779 fn cancel_ui_cont(&self, id: u64) {
780 let continuation = self.ui_conts.borrow_mut().remove(&id);
781 drop(continuation);
782 }
783
784 fn register_frame_callback(
785 &self,
786 kind: FrameCallbackKind,
787 callback: Box<dyn FnOnce(u64) + 'static>,
788 ) -> FrameCallbackId {
789 let id = self.next_frame_callback_id.get();
790 self.next_frame_callback_id.set(id + 1);
791 self.frame_callbacks
792 .borrow_mut()
793 .push_back(FrameCallbackEntry {
794 id,
795 kind,
796 callback: Some(callback),
797 });
798 self.schedule();
799 id
800 }
801
802 fn cancel_frame_callback(&self, id: FrameCallbackId) {
803 let removed = {
804 let mut callbacks = self.frame_callbacks.borrow_mut();
805 callbacks
806 .iter()
807 .position(|entry| entry.id == id)
808 .and_then(|index| callbacks.remove(index))
809 };
810 drop(removed);
811 self.clear_needs_frame_if_idle();
812 }
813
814 fn clear_needs_frame_if_idle(&self) {
815 if !self.has_invalid_scopes()
816 && !self.has_updates()
817 && !self.has_frame_callbacks()
818 && !self.has_pending_ui()
819 {
820 *self.needs_frame.borrow_mut() = false;
821 }
822 }
823
824 fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
825 if self
826 .last_frame_time_nanos
827 .get()
828 .is_some_and(|previous| frame_time_nanos <= previous)
829 {
830 return;
831 }
832 self.last_frame_time_nanos.set(Some(frame_time_nanos));
833 let next_frame_id = self.next_frame_callback_id.get();
834 if self.has_frame_callbacks() {
835 let _ = crate::run_in_mutable_snapshot(|| {
836 loop {
837 let entry = {
838 let mut callbacks = self.frame_callbacks.borrow_mut();
839 if callbacks
840 .front()
841 .is_some_and(|entry| entry.id < next_frame_id)
842 {
843 callbacks.pop_front()
844 } else {
845 None
846 }
847 };
848 let Some(mut entry) = entry else {
849 break;
850 };
851 if let Some(callback) = entry.callback.take() {
852 callback(frame_time_nanos);
853 }
854 }
855 });
856 }
857
858 self.clear_needs_frame_if_idle();
859 }
860
861 fn debug_stats(&self) -> RuntimeDebugStats {
862 let node_updates = self.node_updates.borrow();
863 let scope_queue = self.scope_queue.borrow();
864 let frame_callbacks = self.frame_callbacks.borrow();
865 let local_tasks = self.local_tasks.borrow();
866 let ui_conts = self.ui_conts.borrow();
867 let tasks = self.tasks.borrow();
868 let external_state_owners = self.external_state_owners.borrow();
869
870 RuntimeDebugStats {
871 node_updates_len: node_updates.len(),
872 node_updates_cap: node_updates.capacity(),
873 invalid_scopes_len: self.invalid_scope_count.get(),
874 scope_queue_len: scope_queue.len(),
875 scope_queue_cap: scope_queue.capacity(),
876 frame_callbacks_len: frame_callbacks.len(),
877 frame_callbacks_cap: frame_callbacks.capacity(),
878 local_tasks_len: local_tasks.len(),
879 local_tasks_cap: local_tasks.capacity(),
880 ui_conts_len: ui_conts.len(),
881 ui_conts_cap: ui_conts.capacity(),
882 tasks_len: tasks.len(),
883 tasks_cap: tasks.capacity(),
884 external_state_owners_len: external_state_owners.len(),
885 external_state_owners_cap: external_state_owners.capacity(),
886 ui_dispatcher_pending: self.ui_dispatcher.pending.load(Ordering::SeqCst),
887 }
888 }
889}
890
891#[derive(Clone)]
892pub struct Runtime {
893 inner: Rc<RuntimeInner>,
894}
895
896impl Runtime {
897 pub fn new(scheduler: SchedulerRef) -> Self {
898 thread_state::watch_thread_exit();
899 let inner = Rc::new(RuntimeInner::new(scheduler));
900 let runtime = Self { inner };
901 let handle = runtime.handle();
902 thread_state::runtimes(|state| {
903 state
904 .registered_runtimes
905 .borrow_mut()
906 .insert(handle.id(), handle.clone());
907 *state.last_runtime.borrow_mut() = Some(handle);
908 });
909 runtime
910 }
911
912 pub fn handle(&self) -> RuntimeHandle {
913 RuntimeHandle {
914 inner: Rc::downgrade(&self.inner),
915 dispatcher: UiDispatcher::new(self.inner.ui_dispatcher.clone()),
916 ui_thread_id: self.inner.ui_thread_id,
917 id: self.inner.runtime_id,
918 }
919 }
920
921 pub fn has_updates(&self) -> bool {
922 self.inner.has_updates()
923 }
924
925 pub fn needs_frame(&self) -> bool {
926 *self.inner.needs_frame.borrow() || self.inner.has_runnable_tasks()
927 }
928
929 pub fn set_needs_frame(&self, value: bool) {
930 *self.inner.needs_frame.borrow_mut() = value;
931 }
932
933 pub fn last_frame_time_nanos(&self) -> Option<u64> {
938 self.inner.last_frame_time_nanos.get()
939 }
940
941 #[cfg(any(feature = "internal", test))]
942 pub fn frame_clock(&self) -> FrameClock {
943 FrameClock::new(self.handle())
944 }
945}
946
947impl Drop for Runtime {
948 fn drop(&mut self) {
949 if Rc::strong_count(&self.inner) != 1 {
950 return;
951 }
952 let id = self.inner.runtime_id;
953 thread_state::try_runtimes(|state| {
954 state.registered_runtimes.borrow_mut().remove(&id);
955 let mut last = state.last_runtime.borrow_mut();
956 if last.as_ref().is_some_and(|handle| handle.id() == id) {
957 *last = None;
958 }
959 });
960 thread_state::release_when_idle();
961 }
962}
963
964#[derive(Default)]
965pub struct DefaultScheduler;
966
967impl RuntimeScheduler for DefaultScheduler {
968 fn schedule_frame(&self) {}
969}
970
971#[cfg(test)]
972#[derive(Default)]
973pub struct TestScheduler;
974
975#[cfg(test)]
976impl RuntimeScheduler for TestScheduler {
977 fn schedule_frame(&self) {}
978}
979
980#[cfg(test)]
981pub struct TestRuntime {
982 runtime: Runtime,
983}
984
985#[cfg(test)]
986impl Default for TestRuntime {
987 fn default() -> Self {
988 Self::new()
989 }
990}
991
992#[cfg(test)]
993impl TestRuntime {
994 pub fn new() -> Self {
995 Self {
996 runtime: Runtime::new(Arc::new(TestScheduler)),
997 }
998 }
999
1000 pub fn handle(&self) -> RuntimeHandle {
1001 self.runtime.handle()
1002 }
1003}
1004
1005#[derive(Clone)]
1006pub struct RuntimeHandle {
1007 inner: Weak<RuntimeInner>,
1008 dispatcher: UiDispatcher,
1009 ui_thread_id: ThreadId,
1010 id: RuntimeId,
1011}
1012
1013pub(crate) struct ScopeRuntime(Weak<RuntimeInner>);
1017
1018impl ScopeRuntime {
1019 pub(crate) fn mark_scope_recomposed(&self) {
1020 if let Some(inner) = self.0.upgrade() {
1021 inner.mark_scope_recomposed();
1022 }
1023 }
1024
1025 pub(crate) fn register_invalid_scope(&self, scope: Weak<RecomposeScopeInner>) {
1026 if let Some(inner) = self.0.upgrade() {
1027 inner.register_invalid_scope(scope);
1028 }
1029 }
1030
1031 pub(crate) fn unregister_state_scope(&self, id: StateId, scope_id: ScopeId) {
1032 if let Some(inner) = self.0.upgrade() {
1033 inner.state_arena.unregister_scope(id, scope_id);
1034 }
1035 }
1036
1037 pub(crate) fn increment_live_recompose_scope_count(&self) {
1038 if let Some(inner) = self.0.upgrade() {
1039 inner.increment_live_recompose_scope_count();
1040 }
1041 }
1042
1043 pub(crate) fn decrement_live_recompose_scope_count(&self) {
1044 if let Some(inner) = self.0.upgrade() {
1045 inner.decrement_live_recompose_scope_count();
1046 }
1047 }
1048}
1049
1050pub struct TaskHandle {
1051 id: u64,
1052 runtime: RuntimeHandle,
1053}
1054
1055pub(crate) struct DeferredStateRelease {
1056 runtime: Weak<RuntimeInner>,
1057 id: StateId,
1058}
1059
1060pub(crate) struct StateHandleLease {
1061 id: StateId,
1062 runtime: RuntimeHandle,
1063}
1064
1065impl StateHandleLease {
1066 pub(crate) fn id(&self) -> StateId {
1067 self.id
1068 }
1069
1070 pub(crate) fn runtime_id(&self) -> RuntimeId {
1071 self.runtime.id()
1072 }
1073}
1074
1075impl Drop for StateHandleLease {
1076 fn drop(&mut self) {
1077 defer_state_release(&self.runtime, self.id);
1078 }
1079}
1080
1081struct StateOwnerFrame;
1082
1083impl Drop for StateOwnerFrame {
1084 fn drop(&mut self) {
1085 thread_state::runtimes(|state| state.state_owners.borrow_mut().pop());
1086 }
1087}
1088
1089pub(crate) fn collecting_states<T>(build: impl FnOnce() -> T) -> (T, Vec<Rc<StateHandleLease>>) {
1107 thread_state::runtimes(|state| state.state_owners.borrow_mut().push(Vec::new()));
1108 let frame = StateOwnerFrame;
1109 let value = build();
1110 let states = thread_state::runtimes(|state| {
1111 state
1112 .state_owners
1113 .borrow_mut()
1114 .last_mut()
1115 .map(std::mem::take)
1116 .unwrap_or_default()
1117 });
1118 drop(frame);
1119 (value, states)
1120}
1121
1122fn hand_to_current_owner(lease: &Rc<StateHandleLease>) -> bool {
1123 thread_state::runtimes(|state| match state.state_owners.borrow_mut().last_mut() {
1124 Some(owner) => {
1125 owner.push(Rc::clone(lease));
1126 true
1127 }
1128 None => false,
1129 })
1130}
1131
1132impl RuntimeHandle {
1133 pub fn id(&self) -> RuntimeId {
1134 self.id
1135 }
1136
1137 pub(crate) fn alloc_state<T: Clone + 'static>(&self, value: T) -> Rc<StateHandleLease> {
1138 let id = self.with_state_arena(|arena| arena.alloc(value, self.clone()));
1139 let lease = Rc::new(StateHandleLease {
1140 id,
1141 runtime: self.clone(),
1142 });
1143 self.with_state_arena(|arena| arena.register_lease(id, &lease));
1144 lease
1145 }
1146
1147 pub(crate) fn alloc_state_with_policy<T: Clone + 'static>(
1148 &self,
1149 value: T,
1150 policy: Rc<dyn MutationPolicy<T>>,
1151 ) -> Rc<StateHandleLease> {
1152 let id =
1153 self.with_state_arena(|arena| arena.alloc_with_policy(value, self.clone(), policy));
1154 let lease = Rc::new(StateHandleLease {
1155 id,
1156 runtime: self.clone(),
1157 });
1158 self.with_state_arena(|arena| arena.register_lease(id, &lease));
1159 lease
1160 }
1161
1162 pub(crate) fn alloc_persistent_state<T: Clone + 'static>(
1163 &self,
1164 value: T,
1165 ) -> crate::MutableState<T> {
1166 self.hand_out(self.alloc_state(value))
1167 }
1168
1169 pub(crate) fn alloc_persistent_state_with_policy<T: Clone + 'static>(
1170 &self,
1171 value: T,
1172 policy: Rc<dyn MutationPolicy<T>>,
1173 ) -> crate::MutableState<T> {
1174 self.hand_out(self.alloc_state_with_policy(value, policy))
1175 }
1176
1177 fn hand_out<T: Clone + 'static>(&self, lease: Rc<StateHandleLease>) -> crate::MutableState<T> {
1178 if !hand_to_current_owner(&lease)
1179 && let Some(inner) = self.inner.upgrade()
1180 {
1181 inner
1182 .external_state_owners
1183 .borrow_mut()
1184 .insert(lease.id(), Rc::clone(&lease));
1185 }
1186 crate::MutableState::from_lease(&lease)
1187 }
1188
1189 pub(crate) fn retain_state_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
1190 self.with_state_arena(|arena| arena.retain_lease(id))
1191 }
1192
1193 pub(crate) fn with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> R {
1194 self.try_with_state_arena(f)
1195 .unwrap_or_else(|| panic!("runtime dropped"))
1196 }
1197
1198 pub(crate) fn try_with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> Option<R> {
1199 self.inner.upgrade().map(|inner| f(&inner.state_arena))
1200 }
1201
1202 pub fn state_arena_stats(&self) -> (usize, usize) {
1203 self.try_with_state_arena(StateArena::stats)
1204 .unwrap_or_default()
1205 }
1206
1207 pub fn state_arena_debug_stats(&self) -> StateArenaDebugStats {
1208 self.try_with_state_arena(StateArena::debug_stats)
1209 .unwrap_or_default()
1210 }
1211
1212 pub fn debug_stats(&self) -> RuntimeDebugStats {
1213 self.inner
1214 .upgrade()
1215 .map(|inner| inner.debug_stats())
1216 .unwrap_or_default()
1217 }
1218
1219 pub fn live_ui_task_labels(&self) -> Vec<(u64, String)> {
1220 self.inner
1221 .upgrade()
1222 .map(|inner| {
1223 inner
1224 .tasks
1225 .borrow()
1226 .iter()
1227 .map(|(id, entry)| (*id, entry.label.clone()))
1228 .collect()
1229 })
1230 .unwrap_or_default()
1231 }
1232
1233 pub fn schedule(&self) {
1234 if let Some(inner) = self.inner.upgrade() {
1235 inner.schedule();
1236 }
1237 }
1238
1239 pub(crate) fn enqueue_node_update(&self, command: Command) {
1240 if let Some(inner) = self.inner.upgrade() {
1241 inner.enqueue_update(command);
1242 }
1243 }
1244
1245 pub fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
1252 if let Some(inner) = self.inner.upgrade() {
1253 inner.enqueue_ui_task(task);
1254 } else {
1255 task();
1256 }
1257 }
1258
1259 pub fn spawn_ui<F>(&self, fut: F) -> Option<TaskHandle>
1260 where
1261 F: Future<Output = ()> + 'static,
1262 {
1263 self.inner.upgrade().map(|inner| {
1264 let id = inner.spawn_ui_task(Box::pin(fut));
1265 TaskHandle {
1266 id,
1267 runtime: self.clone(),
1268 }
1269 })
1270 }
1271
1272 pub fn cancel_task(&self, id: u64) {
1273 if let Some(inner) = self.inner.upgrade() {
1274 inner.cancel_task(id);
1275 }
1276 }
1277
1278 pub fn has_task(&self, id: u64) -> bool {
1280 self.inner.upgrade().is_some_and(|inner| inner.has_task(id))
1281 }
1282
1283 pub fn post_ui(&self, task: impl FnOnce() + Send + 'static) {
1288 self.dispatcher.post(task);
1289 }
1290
1291 pub fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> Option<u64> {
1292 self.inner.upgrade().map(|inner| inner.register_ui_cont(f))
1293 }
1294
1295 pub fn cancel_ui_cont(&self, id: u64) {
1296 if let Some(inner) = self.inner.upgrade() {
1297 inner.cancel_ui_cont(id);
1298 }
1299 }
1300
1301 pub fn drain_ui(&self) {
1302 if let Some(inner) = self.inner.upgrade() {
1303 inner.drain_ui();
1304 }
1305 }
1306
1307 pub fn has_pending_ui(&self) -> bool {
1308 self.inner.upgrade().map_or_else(
1309 || self.dispatcher.has_pending(),
1310 |inner| inner.has_pending_ui(),
1311 )
1312 }
1313
1314 pub fn register_frame_callback(
1315 &self,
1316 callback: impl FnOnce(u64) + 'static,
1317 ) -> Option<FrameCallbackId> {
1318 self.inner.upgrade().map(|inner| {
1319 inner.register_frame_callback(FrameCallbackKind::Transient, Box::new(callback))
1320 })
1321 }
1322
1323 pub fn register_perpetual_frame_callback(
1324 &self,
1325 callback: impl FnOnce(u64) + 'static,
1326 ) -> Option<FrameCallbackId> {
1327 self.inner.upgrade().map(|inner| {
1328 inner.register_frame_callback(FrameCallbackKind::Perpetual, Box::new(callback))
1329 })
1330 }
1331
1332 pub fn cancel_frame_callback(&self, id: FrameCallbackId) {
1333 if let Some(inner) = self.inner.upgrade() {
1334 inner.cancel_frame_callback(id);
1335 }
1336 }
1337
1338 pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1342 if let Some(inner) = self.inner.upgrade() {
1343 inner.drain_frame_callbacks(frame_time_nanos);
1344 }
1345 }
1346
1347 pub fn last_frame_time_nanos(&self) -> Option<u64> {
1350 self.inner
1351 .upgrade()
1352 .and_then(|inner| inner.last_frame_time_nanos.get())
1353 }
1354
1355 #[cfg(any(feature = "internal", test))]
1356 pub fn frame_clock(&self) -> FrameClock {
1357 FrameClock::new(self.clone())
1358 }
1359
1360 pub fn set_needs_frame(&self, value: bool) {
1361 if let Some(inner) = self.inner.upgrade() {
1362 *inner.needs_frame.borrow_mut() = value;
1363 }
1364 }
1365
1366 pub(crate) fn take_updates(&self) -> Vec<Command> {
1367 self.inner
1368 .upgrade()
1369 .map(|inner| inner.take_updates())
1370 .unwrap_or_default()
1371 }
1372
1373 pub fn has_updates(&self) -> bool {
1374 self.inner
1375 .upgrade()
1376 .is_some_and(|inner| inner.has_updates())
1377 }
1378
1379 pub(crate) fn scope_runtime(&self) -> ScopeRuntime {
1381 ScopeRuntime(Weak::clone(&self.inner))
1382 }
1383
1384 pub(crate) fn requeue_invalid_scope(&self, scope: &RecomposeScope) {
1385 if let Some(inner) = self.inner.upgrade() {
1386 inner.requeue_invalid_scope(scope);
1387 }
1388 }
1389
1390 pub(crate) fn take_invalidated_scopes(&self) -> Option<Vec<RecomposeScope>> {
1391 self.inner
1392 .upgrade()
1393 .and_then(|inner| inner.take_invalidated_scopes())
1394 }
1395
1396 pub fn forget_movable(&self, id: Key) {
1400 if let Some(inner) = self.inner.upgrade() {
1401 inner.forgotten_movables.borrow_mut().push(id);
1402 inner.schedule();
1403 }
1404 }
1405
1406 pub(crate) fn take_forgotten_movables(&self) -> Vec<Key> {
1407 self.inner
1408 .upgrade()
1409 .map(|inner| std::mem::take(&mut *inner.forgotten_movables.borrow_mut()))
1410 .unwrap_or_default()
1411 }
1412
1413 pub(crate) fn next_movable_content_id(&self) -> Key {
1418 let Some(inner) = self.inner.upgrade() else {
1419 log::error!("movable content asked a runtime that is gone for an identity");
1420 return 0;
1421 };
1422 let id = inner.next_movable_content_id.get();
1423 inner.next_movable_content_id.set(id.wrapping_add(1).max(1));
1424 id
1425 }
1426
1427 pub fn has_invalid_scopes(&self) -> bool {
1428 self.inner
1429 .upgrade()
1430 .is_some_and(|inner| inner.has_invalid_scopes())
1431 }
1432
1433 fn live_recompose_scope_count(&self) -> usize {
1434 self.inner
1435 .upgrade()
1436 .map(|inner| inner.live_recompose_scope_count())
1437 .unwrap_or_default()
1438 }
1439
1440 #[doc(hidden)]
1441 pub fn debug_invalid_scope_ids(&self) -> Vec<usize> {
1442 self.inner
1443 .upgrade()
1444 .map(|inner| inner.queued_invalid_scope_ids())
1445 .unwrap_or_default()
1446 }
1447
1448 pub fn has_frame_callbacks(&self) -> bool {
1449 self.inner
1450 .upgrade()
1451 .is_some_and(|inner| inner.has_frame_callbacks())
1452 }
1453
1454 pub fn has_transient_frame_callbacks(&self) -> bool {
1455 self.inner
1456 .upgrade()
1457 .is_some_and(|inner| inner.has_transient_frame_callbacks())
1458 }
1459
1460 pub fn assert_ui_thread(&self) {
1461 debug_assert_eq!(
1462 std::thread::current().id(),
1463 self.ui_thread_id,
1464 "state mutated off the runtime's UI thread"
1465 );
1466 }
1467
1468 pub fn dispatcher(&self) -> UiDispatcher {
1469 self.dispatcher.clone()
1470 }
1471
1472 #[doc(hidden)]
1473 pub fn with_deferred_state_releases<R>(&self, f: impl FnOnce() -> R) -> R {
1474 let _scope = enter_state_teardown_scope();
1475 f()
1476 }
1477}
1478
1479impl TaskHandle {
1480 pub fn cancel(&self) {
1481 self.runtime.cancel_task(self.id);
1482 }
1483
1484 pub fn is_finished(&self) -> bool {
1486 !self.runtime.has_task(self.id)
1487 }
1488}
1489
1490pub(crate) struct FrameCallbackEntry {
1491 id: FrameCallbackId,
1492 kind: FrameCallbackKind,
1493 callback: Option<Box<dyn FnOnce(u64) + 'static>>,
1494}
1495
1496#[cfg(not(target_arch = "wasm32"))]
1497struct RuntimeTaskWaker {
1498 scheduler: SchedulerRef,
1499 runnable: Arc<AtomicBool>,
1500}
1501
1502#[cfg(target_arch = "wasm32")]
1503struct RuntimeTaskWaker {
1504 runtime_id: RuntimeId,
1505 runnable: Arc<AtomicBool>,
1506}
1507
1508impl RuntimeTaskWaker {
1509 #[cfg(not(target_arch = "wasm32"))]
1510 fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1511 let scheduler = inner.scheduler.clone();
1512 Self {
1513 scheduler,
1514 runnable,
1515 }
1516 }
1517
1518 #[cfg(target_arch = "wasm32")]
1519 fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1520 let runtime_id = inner.runtime_id;
1521 Self {
1522 runtime_id,
1523 runnable,
1524 }
1525 }
1526
1527 fn into_waker(self) -> Waker {
1528 futures_task::waker(Arc::new(self))
1529 }
1530}
1531
1532impl futures_task::ArcWake for RuntimeTaskWaker {
1533 #[cfg(not(target_arch = "wasm32"))]
1534 fn wake_by_ref(arc_self: &Arc<Self>) {
1535 arc_self.runnable.store(true, Ordering::Release);
1536 arc_self.scheduler.schedule_frame();
1537 }
1538
1539 #[cfg(target_arch = "wasm32")]
1540 fn wake_by_ref(arc_self: &Arc<Self>) {
1541 arc_self.runnable.store(true, Ordering::Release);
1542 if let Some(handle) = runtime_handle_by_id(arc_self.runtime_id) {
1543 handle.schedule();
1544 }
1545 }
1546}
1547
1548#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1549pub struct RuntimeThreadLocalDebugStats {
1550 pub active_runtimes_len: usize,
1551 pub active_runtimes_cap: usize,
1552 pub registered_runtimes_len: usize,
1553 pub registered_runtimes_cap: usize,
1554 pub deferred_state_releases_len: usize,
1555 pub deferred_state_releases_cap: usize,
1556}
1557
1558pub fn current_runtime_handle() -> Option<RuntimeHandle> {
1563 thread_state::try_runtimes(|state| {
1564 if let Some(handle) = state.active_runtimes.borrow().last() {
1565 return Some(handle.clone());
1566 }
1567 state.last_runtime.borrow().clone()
1568 })
1569 .flatten()
1570}
1571
1572pub(crate) fn runtime_handle_by_id(id: RuntimeId) -> Option<RuntimeHandle> {
1573 thread_state::try_runtimes(|state| state.registered_runtimes.borrow().get(&id).cloned())
1574 .flatten()
1575}
1576
1577pub(crate) fn forget_registered_runtimes() {
1578 thread_state::try_runtimes(|state| state.registered_runtimes.take());
1579}
1580
1581pub(crate) fn with_state_arena_by_id<R>(
1582 id: RuntimeId,
1583 f: impl FnOnce(&StateArena) -> R,
1584) -> Option<R> {
1585 let inner = thread_state::try_runtimes(|state| {
1586 state
1587 .registered_runtimes
1588 .borrow()
1589 .get(&id)
1590 .and_then(|handle| handle.inner.upgrade())
1591 })
1592 .flatten()?;
1593 Some(f(&inner.state_arena))
1594}
1595
1596pub(crate) fn live_recompose_scope_count() -> usize {
1597 thread_state::runtimes(|state| {
1598 state
1599 .registered_runtimes
1600 .borrow()
1601 .values()
1602 .map(RuntimeHandle::live_recompose_scope_count)
1603 .sum()
1604 })
1605}
1606
1607pub fn debug_runtime_thread_local_stats() -> RuntimeThreadLocalDebugStats {
1608 thread_state::runtimes(|state| {
1609 let active = state.active_runtimes.borrow();
1610 let registered = state.registered_runtimes.borrow();
1611 let deferred = state.deferred_state_releases.borrow();
1612 RuntimeThreadLocalDebugStats {
1613 active_runtimes_len: active.len(),
1614 active_runtimes_cap: active.capacity(),
1615 registered_runtimes_len: registered.len(),
1616 registered_runtimes_cap: registered.capacity(),
1617 deferred_state_releases_len: deferred.len(),
1618 deferred_state_releases_cap: deferred.capacity(),
1619 }
1620 })
1621}
1622
1623fn defer_state_release(runtime: &RuntimeHandle, id: StateId) {
1624 let deferred = thread_state::try_runtimes(|state| {
1625 if state.state_teardown_depth.get() == 0 {
1626 return false;
1627 }
1628 state
1629 .deferred_state_releases
1630 .borrow_mut()
1631 .push(DeferredStateRelease {
1632 runtime: Weak::clone(&runtime.inner),
1633 id,
1634 });
1635 true
1636 });
1637 if deferred != Some(true) {
1638 release_state(&runtime.inner, id);
1639 }
1640}
1641
1642fn release_state(runtime: &Weak<RuntimeInner>, id: StateId) {
1643 if let Some(inner) = runtime.upgrade() {
1644 inner.state_arena.release(id);
1645 }
1646}
1647
1648pub(crate) struct StateTeardownScope;
1649
1650pub(crate) fn enter_state_teardown_scope() -> StateTeardownScope {
1651 thread_state::try_runtimes(|state| {
1652 let depth = &state.state_teardown_depth;
1653 depth.set(depth.get() + 1);
1654 });
1655 StateTeardownScope
1656}
1657
1658impl Drop for StateTeardownScope {
1659 fn drop(&mut self) {
1660 let finished = thread_state::try_runtimes(|state| {
1661 let depth = &state.state_teardown_depth;
1662 let next = depth.get().saturating_sub(1);
1663 depth.set(next);
1664 if next == 0 {
1665 let mut releases = state.deferred_state_releases.borrow_mut();
1666 while let Some(deferred) = releases.pop() {
1667 release_state(&deferred.runtime, deferred.id);
1668 }
1669 }
1670 next == 0
1671 });
1672 if finished == Some(true) {
1673 thread_state::release_when_idle();
1674 }
1675 }
1676}
1677
1678pub(crate) fn push_active_runtime(handle: &RuntimeHandle) {
1679 thread_state::runtimes(|state| {
1680 state
1681 .registered_runtimes
1682 .borrow_mut()
1683 .insert(handle.id(), handle.clone());
1684 state.active_runtimes.borrow_mut().push(handle.clone());
1685 *state.last_runtime.borrow_mut() = Some(handle.clone());
1686 });
1687}
1688
1689pub(crate) fn pop_active_runtime() {
1690 thread_state::runtimes(|state| {
1691 state.active_runtimes.borrow_mut().pop();
1692 });
1693}
1694
1695pub fn schedule_frame() {
1697 if let Some(handle) = current_runtime_handle() {
1698 handle.schedule();
1699 return;
1700 }
1701 log::debug!(
1702 target: "cranpose::runtime",
1703 "ignoring frame request without an active runtime",
1704 );
1705}
1706
1707pub fn schedule_node_update(
1709 update: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1710) {
1711 if let Some(handle) = current_runtime_handle() {
1712 handle.enqueue_node_update(Command::callback(update));
1713 } else {
1714 drop(update);
1715 log::debug!(
1716 target: "cranpose::runtime",
1717 "ignoring node update request without an active runtime",
1718 );
1719 }
1720}
1721
1722#[cfg(test)]
1723#[path = "tests/runtime_tests.rs"]
1724mod tests;