Skip to main content

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}