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