teksilo_async/install.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! `install_async()` — the builder hook that wires the executor into the app.
5
6use teksilo_app::TeksiloAppBuilder;
7
8use crate::executor::AsyncRuntimeHandle;
9
10/// Adds [`install_async`](TeksiloAppBuilderAsyncExt::install_async) to the app
11/// builder. Brought into scope with `use teksilo_async::TeksiloAppBuilderAsyncExt;`
12/// (or via the `teksilo` prelude when the `async` feature is on).
13pub trait TeksiloAppBuilderAsyncExt {
14 /// Install the main-thread async runtime: register the
15 /// [`AsyncRuntimeHandle`] and its completion registry in app-state, and
16 /// wire the executor poll into the event loop via
17 /// [`on_loop_tick`](TeksiloAppBuilder::on_loop_tick). After this, handlers
18 /// can call `ctx.spawn_local(...)` /
19 /// [`spawn_blocking`](crate::spawn_blocking).
20 fn install_async(self) -> Self;
21}
22
23impl TeksiloAppBuilderAsyncExt for TeksiloAppBuilder {
24 fn install_async(self) -> Self {
25 let handle = AsyncRuntimeHandle::new();
26 let poll_source = handle.poll_source();
27 // The completion registry is a teksilo-core type, so teksilo-app can
28 // fetch it from app-state and route `spawn_local_with` completions
29 // without depending on teksilo-async (which would be a cycle).
30 let completions = handle.completions();
31 let tick_handle = handle.clone();
32 let waker_handle = handle.clone();
33 self.app_state(handle)
34 .app_state(completions)
35 .on_loop_tick(poll_source, move || tick_handle.tick())
36 // Wire the cross-thread waker at startup (the AppEventProxy is itself
37 // an AppEventPoster), so a spawn wakes the loop even when the app
38 // uses the handle directly rather than via `ctx.spawn_local`. The
39 // ext trait also sets it lazily; `set_poster` is idempotent.
40 .on_ready(move |proxy| waker_handle.set_poster(std::sync::Arc::new(proxy)))
41 }
42}