Make the Rust client the sole app
This commit is contained in:
1 parent
5ca244528f
commit
6dbc800739
27 files changed
+59
-42
No files matched your search
+104
@@ -0,0 +1,104 @@
|
||||
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<Rsc> = SyncSender<Box<dyn TaskUpdate<Rsc>>>;
|
||||
pub type TaskMsgReceiver<Rsc> = SyncReceiver<Box<dyn TaskUpdate<Rsc>>>;
|
||||
|
||||
pub trait TaskUpdate<Rsc: HasState>: FnOnce(&mut Rsc::State, &mut Rsc) + Send {}
|
||||
impl<F: FnOnce(&mut Rsc::State, &mut Rsc) + Send, Rsc: HasState> TaskUpdate<Rsc> for F {}
|
||||
|
||||
pub struct Tasks<Rsc: HasState> {
|
||||
start: AsyncSender<BoxTask>,
|
||||
redraw: Arc<dyn RequestRedraw>,
|
||||
msg_send: SyncSender<Box<dyn TaskUpdate<Rsc>>>,
|
||||
}
|
||||
|
||||
pub struct TaskCtx<Rsc: HasState> {
|
||||
send: TaskMsgSender<Rsc>,
|
||||
}
|
||||
|
||||
impl<Rsc: HasState> TaskCtx<Rsc> {
|
||||
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
|
||||
let _ = self.send.send(Box::new(f));
|
||||
}
|
||||
}
|
||||
impl<Rsc: HasState + 'static> TaskCtx<Rsc> {
|
||||
fn new(send: TaskMsgSender<Rsc>) -> Self {
|
||||
Self { send }
|
||||
}
|
||||
}
|
||||
|
||||
type BoxTask = Pin<Box<dyn Future<Output = ()> + Send>>;
|
||||
|
||||
impl<Rsc: HasState> Tasks<Rsc> {
|
||||
pub fn init(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Rsc>) {
|
||||
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<dyn RequestRedraw> {
|
||||
self.redraw.clone()
|
||||
}
|
||||
|
||||
pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + '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<BoxTask>) {
|
||||
while let Some(task) = recv.recv().await {
|
||||
tokio::spawn(task);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user