Skip to main content

cranpose_core/
runtime.rs

1use std::{
2    any::Any,
3    cell::{Cell, RefCell},
4    collections::VecDeque,
5    future::Future,
6    pin::Pin,
7    rc::{Rc, Weak},
8    sync::{
9        Arc,
10        atomic::{AtomicBool, AtomicUsize, Ordering},
11        mpsc,
12    },
13    task::{Context, Poll, Waker},
14    thread::ThreadId,
15    thread_local,
16};
17
18#[cfg(any(feature = "internal", test))]
19use crate::frame_clock::FrameClock;
20use crate::{
21    Applier, Command, FrameCallbackId, Key, MutableStateInner, NodeError, RecomposeScope,
22    RecomposeScopeInner, ScopeId,
23    collections::map::HashMap,
24    platform::{RuntimeScheduler, SchedulerRef},
25    state::{MutationPolicy, NeverEqual},
26};
27
28#[derive(Clone, Copy, PartialEq, Eq)]
29pub(crate) enum FrameCallbackKind {
30    Transient,
31    Perpetual,
32}
33
34enum UiMessage {
35    Task(Box<dyn FnOnce() + Send + 'static>),
36    Invoke { id: u64, value: Box<dyn Any + Send> },
37}
38
39type UiContinuation = Box<dyn Fn(Box<dyn Any>) -> bool + 'static>;
40type UiContinuationMap = HashMap<u64, UiContinuation>;
41
42struct TypedStateCell<T: Clone + 'static> {
43    inner: MutableStateInner<T>,
44}
45
46trait ScopeWatchCell {
47    fn unregister_scope(&self, scope_id: ScopeId);
48}
49
50impl<T: Clone + 'static> ScopeWatchCell for TypedStateCell<T> {
51    fn unregister_scope(&self, scope_id: ScopeId) {
52        self.inner.unregister_scope(scope_id);
53    }
54}
55
56struct StateArenaSlot {
57    generation: u32,
58    cell: Option<Rc<dyn Any>>,
59    watcher_cell: Option<Rc<dyn ScopeWatchCell>>,
60    lease: Option<Weak<StateHandleLease>>,
61}
62
63#[derive(Default)]
64struct StateArenaInner {
65    cells: Vec<StateArenaSlot>,
66    free: Vec<u32>,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
70pub struct StateArenaDebugStats {
71    pub cells_len: usize,
72    pub cells_cap: usize,
73    pub free_len: usize,
74    pub free_cap: usize,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub struct RuntimeDebugStats {
79    pub node_updates_len: usize,
80    pub node_updates_cap: usize,
81    pub invalid_scopes_len: usize,
82    pub 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        NEXT_RUNTIME_ID.with(|next| {
305            let id = next.get();
306            next.set(id.wrapping_add(1));
307            Self(id)
308        })
309    }
310}
311
312struct UiDispatcherInner {
313    scheduler: SchedulerRef,
314    tx: mpsc::Sender<UiMessage>,
315    pending: AtomicUsize,
316}
317
318#[cfg(not(target_arch = "wasm32"))]
319type UiDispatcherRef = Arc<UiDispatcherInner>;
320
321#[cfg(target_arch = "wasm32")]
322type UiDispatcherRef = Rc<UiDispatcherInner>;
323
324impl UiDispatcherInner {
325    fn new(scheduler: SchedulerRef, tx: mpsc::Sender<UiMessage>) -> Self {
326        Self {
327            scheduler,
328            tx,
329            pending: AtomicUsize::new(0),
330        }
331    }
332
333    fn post(&self, task: impl FnOnce() + Send + 'static) {
334        self.pending.fetch_add(1, Ordering::SeqCst);
335        if self.tx.send(UiMessage::Task(Box::new(task))).is_ok() {
336            self.scheduler.schedule_frame();
337        } else {
338            self.pending.fetch_sub(1, Ordering::SeqCst);
339        }
340    }
341
342    fn post_invoke(&self, id: u64, value: Box<dyn Any + Send>) {
343        self.pending.fetch_add(1, Ordering::SeqCst);
344        if self.tx.send(UiMessage::Invoke { id, value }).is_ok() {
345            self.scheduler.schedule_frame();
346        } else {
347            self.pending.fetch_sub(1, Ordering::SeqCst);
348        }
349    }
350
351    fn has_pending(&self) -> bool {
352        self.pending.load(Ordering::SeqCst) > 0
353    }
354}
355
356struct PendingGuard<'a> {
357    counter: &'a AtomicUsize,
358}
359
360impl<'a> PendingGuard<'a> {
361    fn new(counter: &'a AtomicUsize) -> Self {
362        Self { counter }
363    }
364}
365
366impl Drop for PendingGuard<'_> {
367    fn drop(&mut self) {
368        let mut current = self.counter.load(Ordering::SeqCst);
369        loop {
370            if current == 0 {
371                return;
372            }
373            match self.counter.compare_exchange(
374                current,
375                current - 1,
376                Ordering::SeqCst,
377                Ordering::SeqCst,
378            ) {
379                Ok(_) => return,
380                Err(next) => current = next,
381            }
382        }
383    }
384}
385
386#[derive(Clone)]
387pub struct UiDispatcher {
388    inner: UiDispatcherRef,
389}
390
391impl UiDispatcher {
392    fn new(inner: UiDispatcherRef) -> Self {
393        Self { inner }
394    }
395
396    pub fn post(&self, task: impl FnOnce() + Send + 'static) {
397        self.inner.post(task);
398    }
399
400    pub fn post_invoke<T>(&self, id: u64, value: T)
401    where
402        T: Send + 'static,
403    {
404        self.inner.post_invoke(id, Box::new(value));
405    }
406
407    pub fn has_pending(&self) -> bool {
408        self.inner.has_pending()
409    }
410}
411
412struct RuntimeInner {
413    scheduler: SchedulerRef,
414    needs_frame: RefCell<bool>,
415    node_updates: RefCell<Vec<Command>>,
416    invalid_scope_count: Cell<usize>,
417    scope_queue: RefCell<Vec<Weak<RecomposeScopeInner>>>,
418    frame_callbacks: RefCell<VecDeque<FrameCallbackEntry>>,
419    next_frame_callback_id: Cell<u64>,
420    last_frame_time_nanos: Cell<Option<u64>>,
421    ui_dispatcher: UiDispatcherRef,
422    ui_rx: RefCell<mpsc::Receiver<UiMessage>>,
423    local_tasks: RefCell<VecDeque<Box<dyn FnOnce() + 'static>>>,
424    ui_conts: RefCell<UiContinuationMap>,
425    next_cont_id: Cell<u64>,
426    ui_thread_id: ThreadId,
427    tasks: RefCell<HashMap<u64, TaskEntry>>,
428    task_order: RefCell<Vec<u64>>,
429    next_task_id: Cell<u64>,
430    state_arena: StateArena,
431    external_state_owners: RefCell<HashMap<StateId, Rc<StateHandleLease>>>,
432    live_recompose_scope_count: Cell<usize>,
433    forgotten_movables: RefCell<Vec<Key>>,
434    next_movable_content_id: Cell<u64>,
435    runtime_id: RuntimeId,
436}
437
438struct TaskEntry {
439    label: String,
440    future: Option<Pin<Box<dyn Future<Output = ()> + 'static>>>,
441    runnable: Arc<AtomicBool>,
442    waker: Waker,
443}
444
445thread_local! {
446    static NEXT_TASK_LABEL: RefCell<Option<String>> = const { RefCell::new(None) };
447}
448
449pub fn label_next_ui_task(label: impl Into<String>) {
450    NEXT_TASK_LABEL.with(|held| *held.borrow_mut() = Some(label.into()));
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        }
482    }
483
484    fn schedule(&self) {
485        *self.needs_frame.borrow_mut() = true;
486        self.scheduler.schedule_frame();
487    }
488
489    fn enqueue_update(&self, command: Command) {
490        self.node_updates.borrow_mut().push(command);
491        self.schedule();
492    }
493
494    fn take_updates(&self) -> Vec<Command> {
495        self.node_updates.borrow_mut().drain(..).collect::<Vec<_>>()
496    }
497
498    fn has_updates(&self) -> bool {
499        !self.node_updates.borrow().is_empty() || self.has_invalid_scopes()
500    }
501
502    fn register_invalid_scope(&self, scope: Weak<RecomposeScopeInner>) {
503        self.invalid_scope_count
504            .set(self.invalid_scope_count.get() + 1);
505        self.scope_queue.borrow_mut().push(scope);
506        self.schedule();
507    }
508
509    fn requeue_invalid_scope(&self, scope: &RecomposeScope) {
510        if scope.is_enqueued() {
511            self.scope_queue.borrow_mut().push(scope.downgrade());
512            self.schedule();
513        }
514    }
515
516    fn mark_scope_recomposed(&self) {
517        self.invalid_scope_count
518            .set(self.invalid_scope_count.get().saturating_sub(1));
519    }
520
521    fn take_invalidated_scopes(&self) -> Option<Vec<RecomposeScope>> {
522        let mut pending = std::mem::take(&mut *self.scope_queue.borrow_mut());
523        if pending.is_empty() {
524            return None;
525        }
526        let scopes: Vec<RecomposeScope> = pending
527            .iter()
528            .filter_map(RecomposeScope::upgrade)
529            .filter(RecomposeScope::is_enqueued)
530            .collect();
531        pending.clear();
532        let mut queue = self.scope_queue.borrow_mut();
533        if queue.is_empty() {
534            std::mem::swap(&mut *queue, &mut pending);
535        }
536        drop(queue);
537        (!scopes.is_empty()).then_some(scopes)
538    }
539
540    fn has_invalid_scopes(&self) -> bool {
541        self.invalid_scope_count.get() != 0
542    }
543
544    fn queued_invalid_scope_ids(&self) -> Vec<ScopeId> {
545        let mut ids: Vec<ScopeId> = self
546            .scope_queue
547            .borrow()
548            .iter()
549            .filter_map(RecomposeScope::upgrade)
550            .filter(RecomposeScope::is_enqueued)
551            .map(|scope| scope.id())
552            .collect();
553        ids.sort_unstable();
554        ids.dedup();
555        ids
556    }
557
558    fn increment_live_recompose_scope_count(&self) {
559        self.live_recompose_scope_count
560            .set(self.live_recompose_scope_count.get().saturating_add(1));
561    }
562
563    fn decrement_live_recompose_scope_count(&self) {
564        self.live_recompose_scope_count
565            .set(self.live_recompose_scope_count.get().saturating_sub(1));
566    }
567
568    fn live_recompose_scope_count(&self) -> usize {
569        self.live_recompose_scope_count.get()
570    }
571
572    fn has_frame_callbacks(&self) -> bool {
573        !self.frame_callbacks.borrow().is_empty()
574    }
575
576    fn has_transient_frame_callbacks(&self) -> bool {
577        self.frame_callbacks
578            .borrow()
579            .iter()
580            .any(|entry| entry.kind == FrameCallbackKind::Transient)
581    }
582
583    fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
584        self.local_tasks.borrow_mut().push_back(task);
585        self.schedule();
586    }
587
588    fn spawn_ui_task(&self, future: Pin<Box<dyn Future<Output = ()> + 'static>>) -> u64 {
589        let id = self.next_task_id.get();
590        self.next_task_id.set(id + 1);
591        let label = NEXT_TASK_LABEL
592            .with(|held| held.borrow_mut().take())
593            .unwrap_or_else(|| "unnamed".to_string());
594        let runnable = Arc::new(AtomicBool::new(true));
595        let waker = RuntimeTaskWaker::new(self, Arc::clone(&runnable)).into_waker();
596        self.tasks.borrow_mut().insert(
597            id,
598            TaskEntry {
599                label,
600                future: Some(future),
601                runnable,
602                waker,
603            },
604        );
605        self.task_order.borrow_mut().push(id);
606        self.schedule();
607        id
608    }
609
610    fn cancel_task(&self, id: u64) {
611        let task = self.tasks.borrow_mut().remove(&id);
612        self.task_order.borrow_mut().retain(|queued| *queued != id);
613        drop(task);
614    }
615
616    fn has_task(&self, id: u64) -> bool {
617        self.tasks
618            .try_borrow()
619            .map_or(true, |tasks| tasks.contains_key(&id))
620    }
621
622    fn poll_async_tasks(&self) -> bool {
623        let order = std::mem::take(&mut *self.task_order.borrow_mut());
624        let mut pending = Vec::with_capacity(order.len());
625        let mut made_progress = false;
626        for id in order {
627            let task = {
628                let mut tasks = self.tasks.borrow_mut();
629                let Some(entry) = tasks.get_mut(&id) else {
630                    continue;
631                };
632                if entry.runnable.swap(false, Ordering::AcqRel) {
633                    entry
634                        .future
635                        .take()
636                        .map(|future| (future, entry.waker.clone()))
637                } else {
638                    None
639                }
640            };
641            let Some((mut future, waker)) = task else {
642                pending.push(id);
643                continue;
644            };
645            let mut cx = Context::from_waker(&waker);
646            match future.as_mut().poll(&mut cx) {
647                Poll::Ready(()) => {
648                    self.cancel_task(id);
649                    made_progress = true;
650                }
651                Poll::Pending => {
652                    let mut tasks = self.tasks.borrow_mut();
653                    if let Some(entry) = tasks.get_mut(&id) {
654                        entry.future = Some(future);
655                        pending.push(id);
656                    } else {
657                        drop(tasks);
658                        drop(future);
659                    }
660                }
661            }
662        }
663        if !pending.is_empty() {
664            pending.retain(|id| self.has_task(*id));
665            self.task_order.borrow_mut().extend(pending);
666        }
667        made_progress
668    }
669
670    fn drain_ui(&self) {
671        loop {
672            let mut executed = false;
673
674            {
675                let rx = &mut *self.ui_rx.borrow_mut();
676                for message in rx.try_iter() {
677                    executed = true;
678                    let _guard = PendingGuard::new(&self.ui_dispatcher.pending);
679                    match message {
680                        UiMessage::Task(task) => {
681                            task();
682                        }
683                        UiMessage::Invoke { id, value } => {
684                            self.invoke_ui_cont(id, value);
685                        }
686                    }
687                }
688            }
689
690            if self.run_local_tasks() {
691                executed = true;
692            }
693
694            if self.poll_async_tasks() {
695                executed = true;
696            }
697            // The tasks just polled may have queued work of their own, such
698            // as a state observer's change notice for a state they wrote. It
699            // belongs to this drain, or a frame that resumed an animation
700            // would draw the value it wrote only on the next frame. It does
701            // not count as progress: a task writing state on every poll must
702            // not keep the drain polling it.
703            self.run_local_tasks();
704
705            if !executed {
706                break;
707            }
708        }
709
710        self.clear_needs_frame_if_idle();
711    }
712
713    fn run_local_tasks(&self) -> bool {
714        let mut ran = false;
715        loop {
716            let task = self.local_tasks.borrow_mut().pop_front();
717            let Some(task) = task else {
718                return ran;
719            };
720            ran = true;
721            task();
722        }
723    }
724
725    fn has_pending_ui(&self) -> bool {
726        let local_pending = self
727            .local_tasks
728            .try_borrow()
729            .map_or(true, |tasks| !tasks.is_empty());
730
731        local_pending || self.ui_dispatcher.has_pending() || self.has_runnable_tasks()
732    }
733
734    fn has_runnable_tasks(&self) -> bool {
735        self.tasks.try_borrow().map_or(true, |tasks| {
736            tasks
737                .values()
738                .any(|task| task.runnable.load(Ordering::Acquire))
739        })
740    }
741
742    fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> u64 {
743        debug_assert_eq!(
744            std::thread::current().id(),
745            self.ui_thread_id,
746            "UI continuation registered off the runtime thread",
747        );
748        let id = self.next_cont_id.get();
749        self.next_cont_id.set(id + 1);
750        let callback = RefCell::new(Some(f));
751        self.ui_conts.borrow_mut().insert(
752            id,
753            Box::new(move |value: Box<dyn Any>| {
754                let Ok(value) = value.downcast::<T>() else {
755                    return false;
756                };
757                let Some(slot) = callback.borrow_mut().take() else {
758                    return true;
759                };
760                slot(*value);
761                true
762            }),
763        );
764        id
765    }
766
767    fn invoke_ui_cont(&self, id: u64, value: Box<dyn Any + Send>) {
768        debug_assert_eq!(
769            std::thread::current().id(),
770            self.ui_thread_id,
771            "UI continuation invoked off the runtime thread",
772        );
773        let callback = { self.ui_conts.borrow_mut().remove(&id) };
774        if let Some(callback) = callback {
775            let value: Box<dyn Any> = value;
776            if !callback(value) {
777                self.ui_conts.borrow_mut().insert(id, callback);
778            }
779        }
780    }
781
782    fn cancel_ui_cont(&self, id: u64) {
783        let continuation = self.ui_conts.borrow_mut().remove(&id);
784        drop(continuation);
785    }
786
787    fn register_frame_callback(
788        &self,
789        kind: FrameCallbackKind,
790        callback: Box<dyn FnOnce(u64) + 'static>,
791    ) -> FrameCallbackId {
792        let id = self.next_frame_callback_id.get();
793        self.next_frame_callback_id.set(id + 1);
794        self.frame_callbacks
795            .borrow_mut()
796            .push_back(FrameCallbackEntry {
797                id,
798                kind,
799                callback: Some(callback),
800            });
801        self.schedule();
802        id
803    }
804
805    fn cancel_frame_callback(&self, id: FrameCallbackId) {
806        let removed = {
807            let mut callbacks = self.frame_callbacks.borrow_mut();
808            callbacks
809                .iter()
810                .position(|entry| entry.id == id)
811                .and_then(|index| callbacks.remove(index))
812        };
813        drop(removed);
814        self.clear_needs_frame_if_idle();
815    }
816
817    fn clear_needs_frame_if_idle(&self) {
818        if !self.has_invalid_scopes()
819            && !self.has_updates()
820            && !self.has_frame_callbacks()
821            && !self.has_pending_ui()
822        {
823            *self.needs_frame.borrow_mut() = false;
824        }
825    }
826
827    fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
828        if self
829            .last_frame_time_nanos
830            .get()
831            .is_some_and(|previous| frame_time_nanos <= previous)
832        {
833            return;
834        }
835        self.last_frame_time_nanos.set(Some(frame_time_nanos));
836        let next_frame_id = self.next_frame_callback_id.get();
837        if self.has_frame_callbacks() {
838            let _ = crate::run_in_mutable_snapshot(|| {
839                loop {
840                    let entry = {
841                        let mut callbacks = self.frame_callbacks.borrow_mut();
842                        if callbacks
843                            .front()
844                            .is_some_and(|entry| entry.id < next_frame_id)
845                        {
846                            callbacks.pop_front()
847                        } else {
848                            None
849                        }
850                    };
851                    let Some(mut entry) = entry else {
852                        break;
853                    };
854                    if let Some(callback) = entry.callback.take() {
855                        callback(frame_time_nanos);
856                    }
857                }
858            });
859        }
860
861        self.clear_needs_frame_if_idle();
862    }
863
864    fn debug_stats(&self) -> RuntimeDebugStats {
865        let node_updates = self.node_updates.borrow();
866        let scope_queue = self.scope_queue.borrow();
867        let frame_callbacks = self.frame_callbacks.borrow();
868        let local_tasks = self.local_tasks.borrow();
869        let ui_conts = self.ui_conts.borrow();
870        let tasks = self.tasks.borrow();
871        let external_state_owners = self.external_state_owners.borrow();
872
873        RuntimeDebugStats {
874            node_updates_len: node_updates.len(),
875            node_updates_cap: node_updates.capacity(),
876            invalid_scopes_len: self.invalid_scope_count.get(),
877            scope_queue_len: scope_queue.len(),
878            scope_queue_cap: scope_queue.capacity(),
879            frame_callbacks_len: frame_callbacks.len(),
880            frame_callbacks_cap: frame_callbacks.capacity(),
881            local_tasks_len: local_tasks.len(),
882            local_tasks_cap: local_tasks.capacity(),
883            ui_conts_len: ui_conts.len(),
884            ui_conts_cap: ui_conts.capacity(),
885            tasks_len: tasks.len(),
886            tasks_cap: tasks.capacity(),
887            external_state_owners_len: external_state_owners.len(),
888            external_state_owners_cap: external_state_owners.capacity(),
889            ui_dispatcher_pending: self.ui_dispatcher.pending.load(Ordering::SeqCst),
890        }
891    }
892}
893
894#[derive(Clone)]
895pub struct Runtime {
896    inner: Rc<RuntimeInner>,
897}
898
899impl Runtime {
900    pub fn new(scheduler: SchedulerRef) -> Self {
901        let inner = Rc::new(RuntimeInner::new(scheduler));
902        let runtime = Self { inner };
903        let handle = runtime.handle();
904        register_runtime_handle(&handle);
905        LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle));
906        runtime
907    }
908
909    pub fn handle(&self) -> RuntimeHandle {
910        RuntimeHandle {
911            inner: Rc::downgrade(&self.inner),
912            dispatcher: UiDispatcher::new(self.inner.ui_dispatcher.clone()),
913            ui_thread_id: self.inner.ui_thread_id,
914            id: self.inner.runtime_id,
915        }
916    }
917
918    pub fn has_updates(&self) -> bool {
919        self.inner.has_updates()
920    }
921
922    pub fn needs_frame(&self) -> bool {
923        *self.inner.needs_frame.borrow() || self.inner.has_runnable_tasks()
924    }
925
926    pub fn set_needs_frame(&self, value: bool) {
927        *self.inner.needs_frame.borrow_mut() = value;
928    }
929
930    /// Animation-clock time of the most recent frame-callback drain. This is
931    /// the same clock `Animatable`s advance on (virtual under robot exact
932    /// captures), so per-frame integrators derive dt from it instead of wall
933    /// time.
934    pub fn last_frame_time_nanos(&self) -> Option<u64> {
935        self.inner.last_frame_time_nanos.get()
936    }
937
938    #[cfg(any(feature = "internal", test))]
939    pub fn frame_clock(&self) -> FrameClock {
940        FrameClock::new(self.handle())
941    }
942}
943
944impl Drop for Runtime {
945    fn drop(&mut self) {
946        if Rc::strong_count(&self.inner) != 1 {
947            return;
948        }
949        unregister_runtime_handle(self.inner.runtime_id);
950        LAST_RUNTIME.with(|slot| {
951            let should_clear = slot
952                .borrow()
953                .as_ref()
954                .is_some_and(|handle| handle.id() == self.inner.runtime_id);
955            if should_clear {
956                *slot.borrow_mut() = None;
957            }
958        });
959    }
960}
961
962#[derive(Default)]
963pub struct DefaultScheduler;
964
965impl RuntimeScheduler for DefaultScheduler {
966    fn schedule_frame(&self) {}
967}
968
969#[cfg(test)]
970#[derive(Default)]
971pub struct TestScheduler;
972
973#[cfg(test)]
974impl RuntimeScheduler for TestScheduler {
975    fn schedule_frame(&self) {}
976}
977
978#[cfg(test)]
979pub struct TestRuntime {
980    runtime: Runtime,
981}
982
983#[cfg(test)]
984impl Default for TestRuntime {
985    fn default() -> Self {
986        Self::new()
987    }
988}
989
990#[cfg(test)]
991impl TestRuntime {
992    pub fn new() -> Self {
993        Self {
994            runtime: Runtime::new(Arc::new(TestScheduler)),
995        }
996    }
997
998    pub fn handle(&self) -> RuntimeHandle {
999        self.runtime.handle()
1000    }
1001}
1002
1003#[derive(Clone)]
1004pub struct RuntimeHandle {
1005    inner: Weak<RuntimeInner>,
1006    dispatcher: UiDispatcher,
1007    ui_thread_id: ThreadId,
1008    id: RuntimeId,
1009}
1010
1011pub struct TaskHandle {
1012    id: u64,
1013    runtime: RuntimeHandle,
1014}
1015
1016struct DeferredStateRelease {
1017    runtime: RuntimeHandle,
1018    id: StateId,
1019}
1020
1021pub(crate) struct StateHandleLease {
1022    id: StateId,
1023    runtime: RuntimeHandle,
1024}
1025
1026impl StateHandleLease {
1027    pub(crate) fn id(&self) -> StateId {
1028        self.id
1029    }
1030
1031    pub(crate) fn runtime(&self) -> RuntimeHandle {
1032        self.runtime.clone()
1033    }
1034}
1035
1036impl Drop for StateHandleLease {
1037    fn drop(&mut self) {
1038        defer_state_release(self.runtime.clone(), self.id);
1039    }
1040}
1041
1042thread_local! {
1043    static STATE_OWNERS: RefCell<Vec<Vec<Rc<StateHandleLease>>>> =
1044        const { RefCell::new(Vec::new()) };
1045}
1046
1047struct StateOwnerFrame;
1048
1049impl Drop for StateOwnerFrame {
1050    fn drop(&mut self) {
1051        STATE_OWNERS.with(|owners| owners.borrow_mut().pop());
1052    }
1053}
1054
1055/// Runs `build` with every state it creates owned by the caller, and returns
1056/// those states beside its value.
1057///
1058/// This is what makes the Jetpack Compose shape safe here:
1059///
1060/// ```rust,ignore
1061/// remember(|| Holder {
1062///     count: mutableStateOf(0),
1063/// })
1064/// ```
1065///
1066/// Kotlin leaves the state to the garbage collector, which frees it with the
1067/// object that holds it. Nothing collects here, so a state with no owner has
1068/// to be kept by the runtime for as long as the runtime lives, and a holder
1069/// built once per screen would pile up cells nobody can reach. Handing the
1070/// states to whoever is building the value puts them back on the object's
1071/// lifetime: the slot drops the value, the value drops the states.
1072pub(crate) fn collecting_states<T>(build: impl FnOnce() -> T) -> (T, Vec<Rc<StateHandleLease>>) {
1073    STATE_OWNERS.with(|owners| owners.borrow_mut().push(Vec::new()));
1074    let frame = StateOwnerFrame;
1075    let value = build();
1076    let states = STATE_OWNERS.with(|owners| {
1077        owners
1078            .borrow_mut()
1079            .last_mut()
1080            .map(std::mem::take)
1081            .unwrap_or_default()
1082    });
1083    drop(frame);
1084    (value, states)
1085}
1086
1087fn hand_to_current_owner(lease: &Rc<StateHandleLease>) -> bool {
1088    STATE_OWNERS.with(|owners| match owners.borrow_mut().last_mut() {
1089        Some(owner) => {
1090            owner.push(Rc::clone(lease));
1091            true
1092        }
1093        None => false,
1094    })
1095}
1096
1097impl RuntimeHandle {
1098    pub fn id(&self) -> RuntimeId {
1099        self.id
1100    }
1101
1102    pub(crate) fn alloc_state<T: Clone + 'static>(&self, value: T) -> Rc<StateHandleLease> {
1103        let id = self.with_state_arena(|arena| arena.alloc(value, self.clone()));
1104        let lease = Rc::new(StateHandleLease {
1105            id,
1106            runtime: self.clone(),
1107        });
1108        self.with_state_arena(|arena| arena.register_lease(id, &lease));
1109        lease
1110    }
1111
1112    pub(crate) fn alloc_state_with_policy<T: Clone + 'static>(
1113        &self,
1114        value: T,
1115        policy: Rc<dyn MutationPolicy<T>>,
1116    ) -> Rc<StateHandleLease> {
1117        let id =
1118            self.with_state_arena(|arena| arena.alloc_with_policy(value, self.clone(), policy));
1119        let lease = Rc::new(StateHandleLease {
1120            id,
1121            runtime: self.clone(),
1122        });
1123        self.with_state_arena(|arena| arena.register_lease(id, &lease));
1124        lease
1125    }
1126
1127    pub(crate) fn alloc_persistent_state<T: Clone + 'static>(
1128        &self,
1129        value: T,
1130    ) -> crate::MutableState<T> {
1131        self.hand_out(self.alloc_state(value))
1132    }
1133
1134    pub(crate) fn alloc_persistent_state_with_policy<T: Clone + 'static>(
1135        &self,
1136        value: T,
1137        policy: Rc<dyn MutationPolicy<T>>,
1138    ) -> crate::MutableState<T> {
1139        self.hand_out(self.alloc_state_with_policy(value, policy))
1140    }
1141
1142    fn hand_out<T: Clone + 'static>(&self, lease: Rc<StateHandleLease>) -> crate::MutableState<T> {
1143        if !hand_to_current_owner(&lease)
1144            && let Some(inner) = self.inner.upgrade()
1145        {
1146            inner
1147                .external_state_owners
1148                .borrow_mut()
1149                .insert(lease.id(), Rc::clone(&lease));
1150        }
1151        crate::MutableState::from_lease(&lease)
1152    }
1153
1154    pub(crate) fn retain_state_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
1155        self.with_state_arena(|arena| arena.retain_lease(id))
1156    }
1157
1158    pub(crate) fn with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> R {
1159        self.try_with_state_arena(f)
1160            .unwrap_or_else(|| panic!("runtime dropped"))
1161    }
1162
1163    pub(crate) fn try_with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> Option<R> {
1164        self.inner.upgrade().map(|inner| f(&inner.state_arena))
1165    }
1166
1167    fn release_state_immediate(&self, id: StateId) {
1168        if let Some(inner) = self.inner.upgrade() {
1169            inner.state_arena.release(id);
1170        }
1171    }
1172
1173    pub fn state_arena_stats(&self) -> (usize, usize) {
1174        self.try_with_state_arena(StateArena::stats)
1175            .unwrap_or_default()
1176    }
1177
1178    pub fn state_arena_debug_stats(&self) -> StateArenaDebugStats {
1179        self.try_with_state_arena(StateArena::debug_stats)
1180            .unwrap_or_default()
1181    }
1182
1183    pub fn debug_stats(&self) -> RuntimeDebugStats {
1184        self.inner
1185            .upgrade()
1186            .map(|inner| inner.debug_stats())
1187            .unwrap_or_default()
1188    }
1189
1190    pub fn live_ui_task_labels(&self) -> Vec<(u64, String)> {
1191        self.inner
1192            .upgrade()
1193            .map(|inner| {
1194                inner
1195                    .tasks
1196                    .borrow()
1197                    .iter()
1198                    .map(|(id, entry)| (*id, entry.label.clone()))
1199                    .collect()
1200            })
1201            .unwrap_or_default()
1202    }
1203
1204    pub(crate) fn unregister_state_scope(&self, id: StateId, scope_id: ScopeId) {
1205        if let Some(inner) = self.inner.upgrade() {
1206            inner.state_arena.unregister_scope(id, scope_id);
1207        }
1208    }
1209
1210    pub fn schedule(&self) {
1211        if let Some(inner) = self.inner.upgrade() {
1212            inner.schedule();
1213        }
1214    }
1215
1216    pub(crate) fn enqueue_node_update(&self, command: Command) {
1217        if let Some(inner) = self.inner.upgrade() {
1218            inner.enqueue_update(command);
1219        }
1220    }
1221
1222    /// Schedules work that must run on the runtime thread.
1223    ///
1224    /// The closure executes on the UI thread immediately when the runtime
1225    /// drains its local queue, so it may capture `Rc`/`RefCell` values. Calling
1226    /// this from any other thread is a logic error and will panic in debug
1227    /// builds via the inner assertion.
1228    pub fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
1229        if let Some(inner) = self.inner.upgrade() {
1230            inner.enqueue_ui_task(task);
1231        } else {
1232            task();
1233        }
1234    }
1235
1236    pub fn spawn_ui<F>(&self, fut: F) -> Option<TaskHandle>
1237    where
1238        F: Future<Output = ()> + 'static,
1239    {
1240        self.inner.upgrade().map(|inner| {
1241            let id = inner.spawn_ui_task(Box::pin(fut));
1242            TaskHandle {
1243                id,
1244                runtime: self.clone(),
1245            }
1246        })
1247    }
1248
1249    pub fn cancel_task(&self, id: u64) {
1250        if let Some(inner) = self.inner.upgrade() {
1251            inner.cancel_task(id);
1252        }
1253    }
1254
1255    /// Whether the runtime still holds the spawned task `id`.
1256    pub fn has_task(&self, id: u64) -> bool {
1257        self.inner.upgrade().is_some_and(|inner| inner.has_task(id))
1258    }
1259
1260    /// Enqueues work from any thread to run on the UI thread.
1261    ///
1262    /// The closure must be `Send` because it may cross threads before executing
1263    /// on the runtime thread. Use this when posting from background work.
1264    pub fn post_ui(&self, task: impl FnOnce() + Send + 'static) {
1265        self.dispatcher.post(task);
1266    }
1267
1268    pub fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> Option<u64> {
1269        self.inner.upgrade().map(|inner| inner.register_ui_cont(f))
1270    }
1271
1272    pub fn cancel_ui_cont(&self, id: u64) {
1273        if let Some(inner) = self.inner.upgrade() {
1274            inner.cancel_ui_cont(id);
1275        }
1276    }
1277
1278    pub fn drain_ui(&self) {
1279        if let Some(inner) = self.inner.upgrade() {
1280            inner.drain_ui();
1281        }
1282    }
1283
1284    pub fn has_pending_ui(&self) -> bool {
1285        self.inner.upgrade().map_or_else(
1286            || self.dispatcher.has_pending(),
1287            |inner| inner.has_pending_ui(),
1288        )
1289    }
1290
1291    pub fn register_frame_callback(
1292        &self,
1293        callback: impl FnOnce(u64) + 'static,
1294    ) -> Option<FrameCallbackId> {
1295        self.inner.upgrade().map(|inner| {
1296            inner.register_frame_callback(FrameCallbackKind::Transient, Box::new(callback))
1297        })
1298    }
1299
1300    pub fn register_perpetual_frame_callback(
1301        &self,
1302        callback: impl FnOnce(u64) + 'static,
1303    ) -> Option<FrameCallbackId> {
1304        self.inner.upgrade().map(|inner| {
1305            inner.register_frame_callback(FrameCallbackKind::Perpetual, Box::new(callback))
1306        })
1307    }
1308
1309    pub fn cancel_frame_callback(&self, id: FrameCallbackId) {
1310        if let Some(inner) = self.inner.upgrade() {
1311            inner.cancel_frame_callback(id);
1312        }
1313    }
1314
1315    /// Delivers callbacks registered before this display frame. Timestamps use
1316    /// one monotonic nanosecond epoch; duplicate or older timestamps are ignored.
1317    /// A callback registered during delivery waits for a newer frame timestamp.
1318    pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1319        if let Some(inner) = self.inner.upgrade() {
1320            inner.drain_frame_callbacks(frame_time_nanos);
1321        }
1322    }
1323
1324    /// Animation-clock time of the most recent frame-callback drain (see
1325    /// [`Runtime::last_frame_time_nanos`]).
1326    pub fn last_frame_time_nanos(&self) -> Option<u64> {
1327        self.inner
1328            .upgrade()
1329            .and_then(|inner| inner.last_frame_time_nanos.get())
1330    }
1331
1332    #[cfg(any(feature = "internal", test))]
1333    pub fn frame_clock(&self) -> FrameClock {
1334        FrameClock::new(self.clone())
1335    }
1336
1337    pub fn set_needs_frame(&self, value: bool) {
1338        if let Some(inner) = self.inner.upgrade() {
1339            *inner.needs_frame.borrow_mut() = value;
1340        }
1341    }
1342
1343    pub(crate) fn take_updates(&self) -> Vec<Command> {
1344        self.inner
1345            .upgrade()
1346            .map(|inner| inner.take_updates())
1347            .unwrap_or_default()
1348    }
1349
1350    pub fn has_updates(&self) -> bool {
1351        self.inner
1352            .upgrade()
1353            .is_some_and(|inner| inner.has_updates())
1354    }
1355
1356    pub(crate) fn mark_scope_recomposed(&self) {
1357        if let Some(inner) = self.inner.upgrade() {
1358            inner.mark_scope_recomposed();
1359        }
1360    }
1361
1362    pub(crate) fn register_invalid_scope(&self, scope: Weak<RecomposeScopeInner>) {
1363        if let Some(inner) = self.inner.upgrade() {
1364            inner.register_invalid_scope(scope);
1365        }
1366    }
1367
1368    pub(crate) fn requeue_invalid_scope(&self, scope: &RecomposeScope) {
1369        if let Some(inner) = self.inner.upgrade() {
1370            inner.requeue_invalid_scope(scope);
1371        }
1372    }
1373
1374    pub(crate) fn take_invalidated_scopes(&self) -> Option<Vec<RecomposeScope>> {
1375        self.inner
1376            .upgrade()
1377            .and_then(|inner| inner.take_invalidated_scopes())
1378    }
1379
1380    /// Releases the retained state of the movable content with identity
1381    /// `id` at the composition's next opportunity. See
1382    /// [`crate::forget_movable`].
1383    pub fn forget_movable(&self, id: Key) {
1384        if let Some(inner) = self.inner.upgrade() {
1385            inner.forgotten_movables.borrow_mut().push(id);
1386            inner.schedule();
1387        }
1388    }
1389
1390    pub(crate) fn take_forgotten_movables(&self) -> Vec<Key> {
1391        self.inner
1392            .upgrade()
1393            .map(|inner| std::mem::take(&mut *inner.forgotten_movables.borrow_mut()))
1394            .unwrap_or_default()
1395    }
1396
1397    /// An identity for a piece of movable content, unique within this
1398    /// runtime. Owned by the runtime instance rather than a process-wide
1399    /// counter, so two compositions in one process cannot collide and a
1400    /// test cannot be made to pass by the order it happens to run in.
1401    pub(crate) fn next_movable_content_id(&self) -> Key {
1402        let Some(inner) = self.inner.upgrade() else {
1403            log::error!("movable content asked a runtime that is gone for an identity");
1404            return 0;
1405        };
1406        let id = inner.next_movable_content_id.get();
1407        inner.next_movable_content_id.set(id.wrapping_add(1).max(1));
1408        id
1409    }
1410
1411    pub fn has_invalid_scopes(&self) -> bool {
1412        self.inner
1413            .upgrade()
1414            .is_some_and(|inner| inner.has_invalid_scopes())
1415    }
1416
1417    pub(crate) fn increment_live_recompose_scope_count(&self) {
1418        if let Some(inner) = self.inner.upgrade() {
1419            inner.increment_live_recompose_scope_count();
1420        }
1421    }
1422
1423    pub(crate) fn decrement_live_recompose_scope_count(&self) {
1424        if let Some(inner) = self.inner.upgrade() {
1425            inner.decrement_live_recompose_scope_count();
1426        }
1427    }
1428
1429    fn live_recompose_scope_count(&self) -> usize {
1430        self.inner
1431            .upgrade()
1432            .map(|inner| inner.live_recompose_scope_count())
1433            .unwrap_or_default()
1434    }
1435
1436    #[doc(hidden)]
1437    pub fn debug_invalid_scope_ids(&self) -> Vec<usize> {
1438        self.inner
1439            .upgrade()
1440            .map(|inner| inner.queued_invalid_scope_ids())
1441            .unwrap_or_default()
1442    }
1443
1444    pub fn has_frame_callbacks(&self) -> bool {
1445        self.inner
1446            .upgrade()
1447            .is_some_and(|inner| inner.has_frame_callbacks())
1448    }
1449
1450    pub fn has_transient_frame_callbacks(&self) -> bool {
1451        self.inner
1452            .upgrade()
1453            .is_some_and(|inner| inner.has_transient_frame_callbacks())
1454    }
1455
1456    pub fn assert_ui_thread(&self) {
1457        debug_assert_eq!(
1458            std::thread::current().id(),
1459            self.ui_thread_id,
1460            "state mutated off the runtime's UI thread"
1461        );
1462    }
1463
1464    pub fn dispatcher(&self) -> UiDispatcher {
1465        self.dispatcher.clone()
1466    }
1467
1468    #[doc(hidden)]
1469    pub fn with_deferred_state_releases<R>(&self, f: impl FnOnce() -> R) -> R {
1470        let _scope = enter_state_teardown_scope();
1471        f()
1472    }
1473}
1474
1475impl TaskHandle {
1476    pub fn cancel(&self) {
1477        self.runtime.cancel_task(self.id);
1478    }
1479
1480    /// Whether the spawned future has finished or been cancelled.
1481    pub fn is_finished(&self) -> bool {
1482        !self.runtime.has_task(self.id)
1483    }
1484}
1485
1486pub(crate) struct FrameCallbackEntry {
1487    id: FrameCallbackId,
1488    kind: FrameCallbackKind,
1489    callback: Option<Box<dyn FnOnce(u64) + 'static>>,
1490}
1491
1492#[cfg(not(target_arch = "wasm32"))]
1493struct RuntimeTaskWaker {
1494    scheduler: SchedulerRef,
1495    runnable: Arc<AtomicBool>,
1496}
1497
1498#[cfg(target_arch = "wasm32")]
1499struct RuntimeTaskWaker {
1500    runtime_id: RuntimeId,
1501    runnable: Arc<AtomicBool>,
1502}
1503
1504impl RuntimeTaskWaker {
1505    #[cfg(not(target_arch = "wasm32"))]
1506    fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1507        let scheduler = inner.scheduler.clone();
1508        Self {
1509            scheduler,
1510            runnable,
1511        }
1512    }
1513
1514    #[cfg(target_arch = "wasm32")]
1515    fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1516        let runtime_id = inner.runtime_id;
1517        Self {
1518            runtime_id,
1519            runnable,
1520        }
1521    }
1522
1523    fn into_waker(self) -> Waker {
1524        futures_task::waker(Arc::new(self))
1525    }
1526}
1527
1528impl futures_task::ArcWake for RuntimeTaskWaker {
1529    #[cfg(not(target_arch = "wasm32"))]
1530    fn wake_by_ref(arc_self: &Arc<Self>) {
1531        arc_self.runnable.store(true, Ordering::Release);
1532        arc_self.scheduler.schedule_frame();
1533    }
1534
1535    #[cfg(target_arch = "wasm32")]
1536    fn wake_by_ref(arc_self: &Arc<Self>) {
1537        arc_self.runnable.store(true, Ordering::Release);
1538        REGISTERED_RUNTIMES.with(|registry| {
1539            if let Some(handle) = registry.borrow().get(&arc_self.runtime_id).cloned() {
1540                handle.schedule();
1541            }
1542        });
1543    }
1544}
1545
1546thread_local! {
1547    static NEXT_RUNTIME_ID: Cell<u32> = const { Cell::new(1) };
1548    static ACTIVE_RUNTIMES: RefCell<Vec<RuntimeHandle>> = const { RefCell::new(Vec::new()) };
1549    static LAST_RUNTIME: RefCell<Option<RuntimeHandle>> = const { RefCell::new(None) };
1550    static REGISTERED_RUNTIMES: RefCell<HashMap<RuntimeId, RuntimeHandle>> = RefCell::new(HashMap::default());
1551    static STATE_TEARDOWN_DEPTH: Cell<usize> = const { Cell::new(0) };
1552    static DEFERRED_STATE_RELEASES: RefCell<Vec<DeferredStateRelease>> = const { RefCell::new(Vec::new()) };
1553}
1554
1555#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1556pub struct RuntimeThreadLocalDebugStats {
1557    pub active_runtimes_len: usize,
1558    pub active_runtimes_cap: usize,
1559    pub registered_runtimes_len: usize,
1560    pub registered_runtimes_cap: usize,
1561    pub deferred_state_releases_len: usize,
1562    pub deferred_state_releases_cap: usize,
1563}
1564
1565/// Gets the current runtime handle from thread-local storage.
1566///
1567/// Returns the most recently pushed active runtime, or the last known runtime.
1568/// Used by fling animation and other components that need access to the runtime.
1569pub fn current_runtime_handle() -> Option<RuntimeHandle> {
1570    if let Some(handle) = ACTIVE_RUNTIMES.with(|stack| stack.borrow().last().cloned()) {
1571        return Some(handle);
1572    }
1573    LAST_RUNTIME.with(|slot| slot.borrow().clone())
1574}
1575
1576pub(crate) fn runtime_handle_by_id(id: RuntimeId) -> Option<RuntimeHandle> {
1577    REGISTERED_RUNTIMES.with(|registry| registry.borrow().get(&id).cloned())
1578}
1579
1580pub(crate) fn with_state_arena_by_id<R>(
1581    id: RuntimeId,
1582    f: impl FnOnce(&StateArena) -> R,
1583) -> Option<R> {
1584    let inner = REGISTERED_RUNTIMES.with(|registry| {
1585        registry
1586            .borrow()
1587            .get(&id)
1588            .and_then(|handle| handle.inner.upgrade())
1589    })?;
1590    Some(f(&inner.state_arena))
1591}
1592
1593pub(crate) fn live_recompose_scope_count() -> usize {
1594    REGISTERED_RUNTIMES.with(|registry| {
1595        registry
1596            .borrow()
1597            .values()
1598            .map(RuntimeHandle::live_recompose_scope_count)
1599            .sum()
1600    })
1601}
1602
1603pub fn debug_runtime_thread_local_stats() -> RuntimeThreadLocalDebugStats {
1604    let (active_runtimes_len, active_runtimes_cap) = ACTIVE_RUNTIMES.with(|stack| {
1605        let stack = stack.borrow();
1606        (stack.len(), stack.capacity())
1607    });
1608    let (registered_runtimes_len, registered_runtimes_cap) = REGISTERED_RUNTIMES.with(|registry| {
1609        let registry = registry.borrow();
1610        (registry.len(), registry.capacity())
1611    });
1612    let (deferred_state_releases_len, deferred_state_releases_cap) =
1613        DEFERRED_STATE_RELEASES.with(|releases| {
1614            let releases = releases.borrow();
1615            (releases.len(), releases.capacity())
1616        });
1617
1618    RuntimeThreadLocalDebugStats {
1619        active_runtimes_len,
1620        active_runtimes_cap,
1621        registered_runtimes_len,
1622        registered_runtimes_cap,
1623        deferred_state_releases_len,
1624        deferred_state_releases_cap,
1625    }
1626}
1627
1628fn register_runtime_handle(handle: &RuntimeHandle) {
1629    REGISTERED_RUNTIMES.with(|registry| {
1630        registry.borrow_mut().insert(handle.id(), handle.clone());
1631    });
1632}
1633
1634fn unregister_runtime_handle(id: RuntimeId) {
1635    REGISTERED_RUNTIMES.with(|registry| {
1636        registry.borrow_mut().remove(&id);
1637    });
1638}
1639
1640fn defer_state_release(runtime: RuntimeHandle, id: StateId) {
1641    let teardown_active = STATE_TEARDOWN_DEPTH.with(|depth| depth.get() > 0);
1642    if teardown_active {
1643        DEFERRED_STATE_RELEASES.with(|releases| {
1644            releases
1645                .borrow_mut()
1646                .push(DeferredStateRelease { runtime, id });
1647        });
1648    } else {
1649        runtime.release_state_immediate(id);
1650    }
1651}
1652
1653fn flush_deferred_state_releases() {
1654    DEFERRED_STATE_RELEASES.with(|releases| {
1655        let mut releases = releases.borrow_mut();
1656        while let Some(deferred) = releases.pop() {
1657            deferred.runtime.release_state_immediate(deferred.id);
1658        }
1659    });
1660}
1661
1662pub(crate) struct StateTeardownScope;
1663
1664pub(crate) fn enter_state_teardown_scope() -> StateTeardownScope {
1665    STATE_TEARDOWN_DEPTH.with(|depth| depth.set(depth.get() + 1));
1666    StateTeardownScope
1667}
1668
1669impl Drop for StateTeardownScope {
1670    fn drop(&mut self) {
1671        STATE_TEARDOWN_DEPTH.with(|depth| {
1672            let next = depth.get().saturating_sub(1);
1673            depth.set(next);
1674            if next == 0 {
1675                flush_deferred_state_releases();
1676            }
1677        });
1678    }
1679}
1680
1681pub(crate) fn push_active_runtime(handle: &RuntimeHandle) {
1682    register_runtime_handle(handle);
1683    ACTIVE_RUNTIMES.with(|stack| stack.borrow_mut().push(handle.clone()));
1684    LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle.clone()));
1685}
1686
1687pub(crate) fn pop_active_runtime() {
1688    ACTIVE_RUNTIMES.with(|stack| {
1689        stack.borrow_mut().pop();
1690    });
1691}
1692
1693/// Schedule a new frame render using the most recently active runtime handle.
1694pub fn schedule_frame() {
1695    if let Some(handle) = current_runtime_handle() {
1696        handle.schedule();
1697        return;
1698    }
1699    log::debug!(
1700        target: "cranpose::runtime",
1701        "ignoring frame request without an active runtime",
1702    );
1703}
1704
1705/// Schedule an in-place node update using the most recently active runtime.
1706pub fn schedule_node_update(
1707    update: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1708) {
1709    if let Some(handle) = current_runtime_handle() {
1710        handle.enqueue_node_update(Command::callback(update));
1711    } else {
1712        drop(update);
1713        log::debug!(
1714            target: "cranpose::runtime",
1715            "ignoring node update request without an active runtime",
1716        );
1717    }
1718}
1719
1720#[cfg(test)]
1721#[path = "tests/runtime_tests.rs"]
1722mod tests;