use std::sync::OnceLock; use tokio::runtime::Runtime; static RT: OnceLock = OnceLock::new(); pub fn init() { RT.get_or_init(|| { tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .expect("failed to build tokio runtime") }); } /// Spawn `future` on the tokio runtime. When it completes, `callback` /// is invoked on the GLib main context (GTK main thread). /// /// Works by routing the result through a tokio oneshot channel; the /// receiving end is awaited by `glib::spawn_future_local`, which runs /// on the GLib event loop but does no I/O, so it never needs tokio's /// reactor itself. pub fn spawn(future: F, callback: C) where F: std::future::Future + Send + 'static, T: Send + 'static, C: FnOnce(T) + 'static, { let (tx, rx) = tokio::sync::oneshot::channel::(); RT.get().expect("runtime not initialised").spawn(async move { let result = future.await; let _ = tx.send(result); }); // The receive future only polls a mutex-protected flag — no I/O, // so running it on the GLib event loop is fine. glib::spawn_future_local(async move { if let Ok(value) = rx.await { callback(value); } }); } /// Fire-and-forget: spawn on the tokio runtime, no callback. pub fn spawn_bg(future: F) where F: std::future::Future + Send + 'static, { RT.get().expect("runtime not initialised").spawn(future); }