1use super::composer::{Composer, Effect, GroupId, RuntimeSignal};
17use super::{group_guard, with_composer, with_composer_if_idle};
18use std::any::Any;
19use std::cell::{Cell, RefCell};
20use std::future::Future;
21use std::pin::Pin;
22use std::rc::Rc;
23use std::sync::atomic::{AtomicBool, Ordering};
24use std::sync::{Arc, Mutex, Weak};
25use std::task::{Context, Poll, Wake, Waker};
26use std::time::Duration;
27
28type LocalFuture = Pin<Box<dyn Future<Output = ()>>>;
29
30struct Task {
32 future: Option<LocalFuture>,
33 waker: Waker,
34}
35
36struct TaskWake {
37 id: usize,
38 queued: AtomicBool,
39 signal: Weak<RuntimeSignal>,
40}
41
42impl Wake for TaskWake {
43 fn wake(self: Arc<Self>) {
44 self.wake_by_ref();
45 }
46
47 fn wake_by_ref(self: &Arc<Self>) {
48 if self.queued.swap(true, Ordering::AcqRel) {
49 return;
50 }
51 if let Some(signal) = self.signal.upgrade() {
52 signal.push_woken(self.id);
53 if !crate::boundary::in_host_call() {
54 crate::boundary::request_frame_from_worker();
55 }
56 }
57 }
58}
59
60#[derive(Default)]
62pub(crate) struct Executor {
63 tasks: Vec<Option<Task>>,
64 wakes: Vec<Option<Arc<TaskWake>>>,
65 free: Vec<usize>,
66 frame_waiters: Vec<(Waker, Rc<Cell<Option<u64>>>)>,
68 pub(crate) frame_time: Option<u64>,
70}
71
72impl Executor {
73 pub(crate) fn spawn(&mut self, future: LocalFuture, signal: &Arc<RuntimeSignal>) -> usize {
75 let id = self.free.pop().unwrap_or(self.tasks.len());
76 let wake = Arc::new(TaskWake {
77 id,
78 queued: AtomicBool::new(true),
79 signal: Arc::downgrade(signal),
80 });
81 let task = Task {
82 future: Some(future),
83 waker: Waker::from(Arc::clone(&wake)),
84 };
85 if id == self.tasks.len() {
86 self.tasks.push(Some(task));
87 self.wakes.push(Some(Arc::clone(&wake)));
88 } else {
89 self.tasks[id] = Some(task);
90 self.wakes[id] = Some(Arc::clone(&wake));
91 }
92 signal.push_woken(id);
93 id
94 }
95
96 pub(crate) fn cancel(&mut self, id: usize, graveyard: &mut Vec<Box<dyn Any>>) {
98 if let Some(slot) = self.tasks.get_mut(id) {
99 if let Some(task) = slot.take() {
100 if let Some(future) = task.future {
101 graveyard.push(Box::new(future));
102 }
103 self.wakes[id] = None;
104 self.free.push(id);
105 }
106 }
107 }
108
109 fn take_for_poll(&mut self, id: usize) -> Option<(LocalFuture, Waker)> {
110 let task = self.tasks.get_mut(id)?.as_mut()?;
111 let future = task.future.take()?;
112 if let Some(Some(wake)) = self.wakes.get(id) {
113 wake.queued.store(false, Ordering::Release);
114 }
115 Some((future, task.waker.clone()))
116 }
117
118 fn put_back(&mut self, id: usize, future: LocalFuture, graveyard: &mut Vec<Box<dyn Any>>) {
119 match self.tasks.get_mut(id).and_then(Option::as_mut) {
120 Some(task) => task.future = Some(future),
121 None => graveyard.push(Box::new(future)),
123 }
124 }
125
126 fn finish(&mut self, id: usize) {
127 if let Some(slot) = self.tasks.get_mut(id) {
128 if slot.take().is_some() {
129 self.wakes[id] = None;
130 self.free.push(id);
131 }
132 }
133 }
134
135 pub(crate) fn deliver_frame(&mut self, frame_time_nanos: u64) {
137 self.frame_time = Some(frame_time_nanos);
138 for (waker, cell) in self.frame_waiters.drain(..) {
139 cell.set(Some(frame_time_nanos));
140 waker.wake();
141 }
142 }
143}
144
145impl Composer {
146 pub(crate) fn spawn(&mut self, future: LocalFuture) -> usize {
147 let signal = Arc::clone(&self.signal);
148 self.executor.spawn(future, &signal)
149 }
150}
151
152pub(crate) fn poll_woken() {
155 for _ in 0..64 {
156 let woken = with_composer(|composer| {
157 std::mem::take(
158 &mut *composer
159 .signal
160 .woken
161 .lock()
162 .unwrap_or_else(|poison| poison.into_inner()),
163 )
164 })
165 .unwrap_or_default();
166 if woken.is_empty() {
167 return;
168 }
169 for id in woken {
170 let Some((mut future, waker)) =
171 with_composer(|composer| composer.executor.take_for_poll(id)).flatten()
172 else {
173 continue;
174 };
175 let mut context = Context::from_waker(&waker);
176 match future.as_mut().poll(&mut context) {
177 Poll::Ready(()) => {
178 with_composer(|composer| {
179 composer.executor.finish(id);
180 composer.graveyard.push(Box::new(future));
181 });
182 }
183 Poll::Pending => {
184 with_composer(|composer| {
185 let Composer {
186 executor,
187 graveyard,
188 ..
189 } = composer;
190 executor.put_back(id, future, graveyard);
191 });
192 }
193 }
194 }
195 super::drain_graveyard();
196 }
197}
198
199#[derive(Default)]
203pub(crate) struct LaunchHandle {
204 pub(crate) task: Cell<Option<usize>>,
205 pub(crate) cancelled: Cell<bool>,
206}
207
208#[derive(Default)]
210pub(crate) struct DisposeHandle {
211 on_dispose: RefCell<Option<Box<dyn FnOnce()>>>,
212 cancelled: Cell<bool>,
213}
214
215#[derive(Default)]
217pub(crate) struct CoroutineState {
218 tasks: RefCell<Vec<usize>>,
219 cancelled: Cell<bool>,
220}
221
222pub(crate) enum EffectSlot {
224 Launched {
225 key: Box<dyn Any>,
226 handle: Rc<LaunchHandle>,
227 },
228 Disposable {
229 key: Box<dyn Any>,
230 handle: Rc<DisposeHandle>,
231 },
232 Coroutines(Rc<CoroutineState>),
233}
234
235impl EffectSlot {
236 pub(crate) fn dispose(&mut self, composer: &mut Composer) {
239 match self {
240 Self::Launched { handle, .. } => {
241 handle.cancelled.set(true);
242 if let Some(task) = handle.task.take() {
243 let Composer {
244 executor,
245 graveyard,
246 ..
247 } = composer;
248 executor.cancel(task, graveyard);
249 }
250 }
251 Self::Disposable { handle, .. } => {
252 handle.cancelled.set(true);
253 if let Some(cleanup) = handle.on_dispose.borrow_mut().take() {
254 composer.disposals.push(cleanup);
255 }
256 }
257 Self::Coroutines(state) => {
258 state.cancelled.set(true);
259 let tasks = std::mem::take(&mut *state.tasks.borrow_mut());
260 let Composer {
261 executor,
262 graveyard,
263 ..
264 } = composer;
265 for task in tasks {
266 executor.cancel(task, graveyard);
267 }
268 }
269 }
270 }
271}
272
273const LAUNCHED_EFFECT: u64 = super::composer::call_site("compose_rust::LaunchedEffect", 0);
274const DISPOSABLE_EFFECT: u64 = super::composer::call_site("compose_rust::DisposableEffect", 0);
275const COROUTINE_SCOPE: u64 =
276 super::composer::call_site("compose_rust::remember_coroutine_scope", 0);
277
278fn same_key<K: PartialEq + 'static>(composer: &mut Composer, launched: bool, key: &K) -> bool {
281 let same = match composer.peek_slot_mut::<EffectSlot>() {
282 Some(EffectSlot::Launched { key: old, .. }) if launched => {
283 old.downcast_ref::<K>() == Some(key)
284 }
285 Some(EffectSlot::Disposable { key: old, .. }) if !launched => {
286 old.downcast_ref::<K>() == Some(key)
287 }
288 _ => false,
289 };
290 if same {
291 composer.slot_mut::<EffectSlot>();
292 }
293 same
294}
295
296pub fn launched_effect<K, F, Fut>(key: K, block: F)
314where
315 K: PartialEq + 'static,
316 F: FnOnce() -> Fut + 'static,
317 Fut: Future<Output = ()> + 'static,
318{
319 let _group = group_guard(LAUNCHED_EFFECT);
320 with_composer_if_idle(|composer| {
321 if !composer.composing() || same_key(composer, true, &key) {
322 return;
323 }
324 let handle = Rc::new(LaunchHandle::default());
325 composer.remember_new(EffectSlot::Launched {
326 key: Box::new(key),
327 handle: Rc::clone(&handle),
328 });
329 composer.push_effect(Effect::Launch {
330 handle,
331 start: Box::new(move || Box::pin(block()) as LocalFuture),
332 });
333 });
334}
335
336pub struct DisposableEffectResult {
338 cleanup: Box<dyn FnOnce()>,
339}
340
341pub fn on_dispose(cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
343 DisposableEffectResult {
344 cleanup: Box::new(cleanup),
345 }
346}
347
348pub fn disposable_effect<K, F>(key: K, effect: F)
359where
360 K: PartialEq + 'static,
361 F: FnOnce() -> DisposableEffectResult + 'static,
362{
363 let _group = group_guard(DISPOSABLE_EFFECT);
364 with_composer_if_idle(|composer| {
365 if !composer.composing() || same_key(composer, false, &key) {
366 return;
367 }
368 let handle = Rc::new(DisposeHandle::default());
369 composer.remember_new(EffectSlot::Disposable {
370 key: Box::new(key),
371 handle: Rc::clone(&handle),
372 });
373 composer.push_effect(Effect::Run(Box::new(move || {
374 if handle.cancelled.get() {
375 return;
376 }
377 let result = effect();
378 if handle.cancelled.get() {
379 (result.cleanup)();
380 } else {
381 *handle.on_dispose.borrow_mut() = Some(result.cleanup);
382 }
383 })));
384 });
385}
386
387pub fn side_effect(effect: impl FnOnce() + 'static) {
391 with_composer_if_idle(|composer| {
392 if composer.composing() {
393 composer.push_effect(Effect::Run(Box::new(effect)));
394 }
395 });
396}
397
398#[derive(Clone)]
401pub struct CoroutineScope {
402 state: Rc<CoroutineState>,
403}
404
405impl CoroutineScope {
406 pub fn launch(&self, future: impl Future<Output = ()> + 'static) {
409 if self.state.cancelled.get() {
410 return;
411 }
412 let id = with_composer_if_idle(|composer| composer.spawn(Box::pin(future)));
413 if let Some(id) = id {
414 let mut tasks = self.state.tasks.borrow_mut();
415 tasks.push(id);
416 }
417 }
418}
419
420pub fn remember_coroutine_scope() -> CoroutineScope {
430 let _group = group_guard(COROUTINE_SCOPE);
431 let state = with_composer_if_idle(|composer| {
432 if !composer.composing() {
433 return None;
434 }
435 if let Some(EffectSlot::Coroutines(state)) = composer.slot_mut::<EffectSlot>() {
436 return Some(Rc::clone(state));
437 }
438 let state = Rc::new(CoroutineState::default());
439 composer.remember_new(EffectSlot::Coroutines(Rc::clone(&state)));
440 Some(state)
441 })
442 .flatten();
443 CoroutineScope {
444 state: state.unwrap_or_default(),
445 }
446}
447
448pub(crate) fn apply_effects() {
451 let (disposals, effects) = with_composer(|composer| {
452 (
453 std::mem::take(&mut composer.disposals),
454 std::mem::take(&mut composer.effects),
455 )
456 })
457 .unwrap_or_default();
458 for cleanup in disposals {
459 cleanup();
460 }
461 for effect in effects {
462 match effect {
463 Effect::Run(run) => run(),
464 Effect::Launch { handle, start } => {
465 if handle.cancelled.get() {
466 continue;
467 }
468 let future = start();
469 let id = with_composer(|composer| composer.spawn(future));
470 if let Some(id) = id {
471 handle.task.set(Some(id));
472 }
473 }
474 }
475 }
476}
477
478pub fn with_frame_nanos<R>(on_frame: impl FnOnce(u64) -> R) -> impl Future<Output = R> {
483 FrameFuture {
484 on_frame: Some(on_frame),
485 cell: None,
486 }
487}
488
489struct FrameFuture<F> {
490 on_frame: Option<F>,
491 cell: Option<Rc<Cell<Option<u64>>>>,
492}
493
494impl<F> Unpin for FrameFuture<F> {}
495
496impl<R, F: FnOnce(u64) -> R> Future for FrameFuture<F> {
497 type Output = R;
498
499 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<R> {
500 if let Some(time) = self.cell.as_ref().and_then(|cell| cell.get()) {
501 let on_frame = self.on_frame.take().expect("a frame future resolves once");
502 return Poll::Ready(on_frame(time));
503 }
504 let cell = Rc::clone(self.cell.get_or_insert_with(Rc::default));
505 let waker = context.waker().clone();
506 let registered = with_composer(|composer| {
507 composer.executor.frame_waiters.push((waker, cell));
508 });
509 if registered.is_some() {
510 crate::boundary::request_frame_from_worker();
511 }
512 Poll::Pending
513 }
514}
515
516pub fn delay(duration: Duration) -> impl Future<Output = ()> {
522 Delay {
523 duration,
524 state: DelayState::Unstarted,
525 }
526}
527
528enum DelayState {
529 Unstarted,
530 #[cfg(not(target_family = "wasm"))]
531 Waiting(std::time::Instant),
532 #[cfg(target_family = "wasm")]
533 Counting(Option<u64>),
534}
535
536struct Delay {
537 duration: Duration,
538 state: DelayState,
539}
540
541impl Future for Delay {
542 type Output = ();
543
544 #[cfg(not(target_family = "wasm"))]
545 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
546 let deadline = match self.state {
547 DelayState::Waiting(deadline) => deadline,
548 DelayState::Unstarted => {
549 let deadline = std::time::Instant::now() + self.duration;
550 self.state = DelayState::Waiting(deadline);
551 deadline
552 }
553 };
554 if std::time::Instant::now() >= deadline {
555 return Poll::Ready(());
556 }
557 timer::register(deadline, context.waker().clone());
558 Poll::Pending
559 }
560
561 #[cfg(target_family = "wasm")]
562 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
563 let now = with_composer(|composer| composer.executor.frame_time).flatten();
564 let start = match self.state {
565 DelayState::Counting(Some(start)) => Some(start),
566 _ => now,
567 };
568 self.state = DelayState::Counting(start);
569 if let (Some(start), Some(now)) = (start, now) {
570 if now.saturating_sub(start) >= self.duration.as_nanos() as u64 {
571 return Poll::Ready(());
572 }
573 }
574 let cell = Rc::new(Cell::new(None));
575 let waker = context.waker().clone();
576 with_composer(|composer| composer.executor.frame_waiters.push((waker, cell)));
577 crate::boundary::request_frame_from_worker();
578 Poll::Pending
579 }
580}
581
582#[cfg(not(target_family = "wasm"))]
583mod timer {
584 use std::sync::{Condvar, Mutex, OnceLock};
585 use std::task::Waker;
586 use std::time::Instant;
587
588 struct Timer {
589 waiting: Mutex<Vec<(Instant, Waker)>>,
590 changed: Condvar,
591 }
592
593 static TIMER: OnceLock<&'static Timer> = OnceLock::new();
594
595 fn timer() -> &'static Timer {
596 TIMER.get_or_init(|| {
597 let timer: &'static Timer = Box::leak(Box::new(Timer {
598 waiting: Mutex::new(Vec::new()),
599 changed: Condvar::new(),
600 }));
601 std::thread::Builder::new()
602 .name("compose-rust-timer".to_owned())
603 .spawn(move || run(timer))
604 .expect("the timer thread could not be started");
605 timer
606 })
607 }
608
609 pub(super) fn register(deadline: Instant, waker: Waker) {
611 let timer = timer();
612 timer
613 .waiting
614 .lock()
615 .unwrap_or_else(|poison| poison.into_inner())
616 .push((deadline, waker));
617 timer.changed.notify_one();
618 }
619
620 fn run(timer: &'static Timer) {
621 let mut waiting = timer
622 .waiting
623 .lock()
624 .unwrap_or_else(|poison| poison.into_inner());
625 loop {
626 let now = Instant::now();
627 let mut index = 0;
628 while index < waiting.len() {
629 if waiting[index].0 <= now {
630 let (_, waker) = waiting.swap_remove(index);
631 waker.wake();
632 } else {
633 index += 1;
634 }
635 }
636 let next = waiting.iter().map(|(deadline, _)| *deadline).min();
637 waiting = match next {
638 Some(deadline) => {
639 timer
640 .changed
641 .wait_timeout(waiting, deadline.saturating_duration_since(now))
642 .unwrap_or_else(|poison| poison.into_inner())
643 .0
644 }
645 None => timer
646 .changed
647 .wait(waiting)
648 .unwrap_or_else(|poison| poison.into_inner()),
649 };
650 }
651 }
652}
653
654pub fn with_worker<R, W>(work: W) -> impl Future<Output = R>
659where
660 R: Send + 'static,
661 W: FnOnce() -> R + Send + 'static,
662{
663 WorkerFuture {
664 work: Some(work),
665 shared: Arc::new(Mutex::new((None, None))),
666 }
667}
668
669type WorkerShared<R> = Arc<Mutex<(Option<R>, Option<Waker>)>>;
670
671struct WorkerFuture<R, W> {
672 work: Option<W>,
673 shared: WorkerShared<R>,
674}
675
676impl<R, W> Unpin for WorkerFuture<R, W> {}
677
678impl<R, W> Future for WorkerFuture<R, W>
679where
680 R: Send + 'static,
681 W: FnOnce() -> R + Send + 'static,
682{
683 type Output = R;
684
685 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<R> {
686 if let Some(work) = self.work.take() {
687 #[cfg(target_family = "wasm")]
688 {
689 return Poll::Ready(work());
690 }
691 #[cfg(not(target_family = "wasm"))]
692 {
693 let shared = Arc::clone(&self.shared);
694 shared.lock().unwrap_or_else(|poison| poison.into_inner()).1 =
695 Some(context.waker().clone());
696 std::thread::Builder::new()
697 .name("compose-rust-worker".to_owned())
698 .spawn(move || {
699 let result = work();
700 let waker = {
701 let mut guard =
702 shared.lock().unwrap_or_else(|poison| poison.into_inner());
703 guard.0 = Some(result);
704 guard.1.take()
705 };
706 if let Some(waker) = waker {
707 waker.wake();
708 }
709 })
710 .expect("a worker thread could not be started");
711 return Poll::Pending;
712 }
713 }
714 let mut guard = self
715 .shared
716 .lock()
717 .unwrap_or_else(|poison| poison.into_inner());
718 match guard.0.take() {
719 Some(result) => Poll::Ready(result),
720 None => {
721 guard.1 = Some(context.waker().clone());
722 Poll::Pending
723 }
724 }
725 }
726}
727
728#[allow(dead_code)]
730pub(crate) fn current_group() -> Option<GroupId> {
731 with_composer_if_idle(|composer| composer.current_group()).flatten()
732}