teksilo_async/ext.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! `ctx.spawn_local(...)` — the extension trait that adds the spawn methods to
5//! [`EventContext`], pulling the [`AsyncRuntimeHandle`] out of app-state.
6
7use std::future::Future;
8
9use teksilo_core::{EventContext, TeksiloWindowId};
10
11use crate::executor::{AsyncRuntimeHandle, TaskHandle};
12
13const NOT_INSTALLED: &str = "teksilo-async: AsyncRuntimeHandle not installed — call \
14 TeksiloAppBuilder::install_async() at startup";
15
16/// Spawn async work on the main-thread executor from inside an event handler.
17///
18/// Brought into scope with `use teksilo_async::EventContextAsyncExt;` (or via
19/// the `teksilo` prelude when the `async` feature is on).
20pub trait EventContextAsyncExt {
21 /// Spawn a `!Send` future. It runs on the UI thread and may capture and
22 /// mutate `Signal`s and other `Rc` handles directly on resume. Drop the
23 /// returned [`TaskHandle`] to cancel, or call `.detach()` to fire-and-forget.
24 fn spawn_local(&mut self, future: impl Future<Output = ()> + 'static) -> TaskHandle;
25
26 /// Spawn a future and deliver its result to `on_complete` with a *fresh*
27 /// [`EventContext`] bound to this window's tree — the supported way to run
28 /// a one-shot ambient op (`open_window`, `send_intent`, …) once the work
29 /// finishes. The future body itself runs handle-only.
30 ///
31 /// # Panics
32 /// Panics if called outside a window context (there is no window to bind
33 /// the completion to). Use [`spawn_local`](Self::spawn_local) from such
34 /// sites and drive UI updates through `Signal`s.
35 fn spawn_local_with<R: 'static>(
36 &mut self,
37 future: impl Future<Output = R> + 'static,
38 on_complete: impl FnOnce(R, &mut EventContext) + 'static,
39 ) -> TaskHandle;
40}
41
42impl EventContextAsyncExt for EventContext<'_> {
43 fn spawn_local(&mut self, future: impl Future<Output = ()> + 'static) -> TaskHandle {
44 let handle = self
45 .app_state::<AsyncRuntimeHandle>()
46 .expect(NOT_INSTALLED)
47 .clone();
48 if let Some(poster) = self.poster() {
49 handle.set_poster(poster.clone());
50 }
51 handle.spawn_local(future)
52 }
53
54 fn spawn_local_with<R: 'static>(
55 &mut self,
56 future: impl Future<Output = R> + 'static,
57 on_complete: impl FnOnce(R, &mut EventContext) + 'static,
58 ) -> TaskHandle {
59 let handle = self
60 .app_state::<AsyncRuntimeHandle>()
61 .expect(NOT_INSTALLED)
62 .clone();
63 if let Some(poster) = self.poster() {
64 handle.set_poster(poster.clone());
65 }
66 let window_id: TeksiloWindowId = self
67 .window()
68 .map(|w| w.id())
69 .expect("teksilo-async: spawn_local_with requires a window context");
70 handle.spawn_local_with(window_id, future, on_complete)
71 }
72}