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