use iris_core::HasState; use std::{ pin::Pin, sync::{ Arc, mpsc::{Receiver as SyncReceiver, Sender as SyncSender, channel as sync_channel}, }, }; use tokio::{ runtime::Runtime, sync::mpsc::{ UnboundedReceiver as AsyncReceiver, UnboundedSender as AsyncSender, unbounded_channel as async_channel, }, }; /// What a completed task nudges when it wants its result drawn. Shared /// between backends rather than typed as `winit::window::Window` directly: /// android-view has no `Window` at all, and the redraw request there is a /// JNI call (`View::post_frame_callback`) rather than a method call on a /// value this crate owns. Each backend supplies its own implementation -- /// `desktop/render.rs` for winit, `android/render.rs` for android-view -- /// and this module never needs to know which one it is holding. pub trait RequestRedraw: Send + Sync + 'static { fn request_redraw(&self); } pub type TaskMsgSender = SyncSender>>; pub type TaskMsgReceiver = SyncReceiver>>; pub trait TaskUpdate: FnOnce(&mut Rsc::State, &mut Rsc) + Send {} impl TaskUpdate for F {} pub struct Tasks { start: AsyncSender, redraw: Arc, msg_send: SyncSender>>, } pub struct TaskCtx { send: TaskMsgSender, } impl TaskCtx { pub fn update(&mut self, f: impl TaskUpdate + 'static) { let _ = self.send.send(Box::new(f)); } } impl TaskCtx { fn new(send: TaskMsgSender) -> Self { Self { send } } } type BoxTask = Pin + Send>>; impl Tasks { pub fn init(redraw: Arc) -> (Self, TaskMsgReceiver) { let (start, start_recv) = async_channel(); let (msgs, msgs_recv) = sync_channel(); std::thread::spawn(|| { let rt = Runtime::new().unwrap(); rt.block_on(listen(start_recv)) }); ( Self { start, msg_send: msgs, redraw, }, msgs_recv, ) } /// The same redraw handle `spawn`'s wrapper calls once, after a whole /// task's future completes -- exposed so a caller running its own /// longer-lived loop *inside* a spawned task (a live SSE follow, here) /// can ask for a frame after each `TaskCtx::update`, not just at the /// end. Without this a caller has no way to get a redraw mid-stream, /// which is exactly the gap `iris/desktop-app`'s `app.rs` module doc /// names for why it uses winit's `Proxy` instead of `Tasks` -- Android /// has no `Proxy`, so this is what closes the same gap there. pub fn redraw_handle(&self) -> Arc { self.redraw.clone() } pub fn spawn) + 'static + std::marker::Send>(&mut self, task: F) where F::CallOnceFuture: Send, { let send = self.msg_send.clone(); let redraw = self.redraw.clone(); let _ = self.start.send(Box::pin(async move { task(TaskCtx::new(send)).await; redraw.request_redraw(); })); } } async fn listen(mut recv: AsyncReceiver) { while let Some(task) = recv.recv().await { tokio::spawn(task); } }