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