From 59901b6580e80dd096ff1e070e75a62a37499d9f Mon Sep 17 00:00:00 2001 From: shadow cat Date: Sat, 3 Jan 2026 16:26:23 -0500 Subject: [PATCH] tasks initial impl (still working on task_on trait method) --- Cargo.lock | 10 +++ Cargo.toml | 6 +- core/src/event/rsc.rs | 8 +- core/src/widget/handle.rs | 2 + .../test => examples/tabs}/assets/sungals.png | Bin {src/bin/test => examples/tabs}/main.rs | 0 examples/task.rs | 33 +++++++ src/default/mod.rs | 47 ++++++---- src/default/task.rs | 85 ++++++++++++++++++ src/event.rs | 61 +++++++++++-- src/lib.rs | 1 + src/typed.rs | 2 +- src/widget/trait_fns.rs | 2 +- 13 files changed, 231 insertions(+), 26 deletions(-) rename {src/bin/test => examples/tabs}/assets/sungals.png (100%) rename {src/bin/test => examples/tabs}/main.rs (100%) create mode 100644 examples/task.rs create mode 100644 src/default/task.rs diff --git a/Cargo.lock b/Cargo.lock index c77f763..2376472 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1047,6 +1047,7 @@ dependencies = [ "iris-core", "iris-macro", "pollster", + "tokio", "unicode-segmentation", "wgpu", "winit", @@ -2575,6 +2576,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "toml" version = "0.8.23" diff --git a/Cargo.toml b/Cargo.toml index 14186b3..0fec7e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [package] name = "iris" -default-run = "test" version.workspace = true edition.workspace = true @@ -16,6 +15,10 @@ arboard = { workspace = true, features = ["wayland-data-control"] } pollster = { workspace = true } wgpu = { workspace = true } image = { workspace = true } +tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } + +[dev-dependencies] +tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] } [workspace] members = ["core", "macro"] @@ -36,3 +39,4 @@ fxhash = "0.2.1" arboard = "3.6.1" iris-core = { path = "core" } iris-macro = { path = "macro" } +tokio = "1.49.0" diff --git a/core/src/event/rsc.rs b/core/src/event/rsc.rs index b814bc9..706cb8f 100644 --- a/core/src/event/rsc.rs +++ b/core/src/event/rsc.rs @@ -2,9 +2,11 @@ use crate::{ Event, EventCtx, EventLike, EventManager, HasUi, IdLike, Widget, WidgetEventFn, WidgetRef, }; -pub trait HasEvents: Sized + HasUi + 'static { +pub trait HasState: 'static { type State; +} +pub trait HasEvents: Sized + HasUi + HasState { fn events(&self) -> &EventManager; fn events_mut(&mut self) -> &mut EventManager; @@ -18,7 +20,7 @@ pub trait HasEvents: Sized + HasUi + 'static { } } -pub trait RunEvents: HasEvents + 'static { +pub trait RunEvents: HasEvents { fn run_event( &mut self, id: impl IdLike, @@ -29,4 +31,4 @@ pub trait RunEvents: HasEvents + 'static { f(EventCtx { state, data }, self) } } -impl RunEvents for T {} +impl RunEvents for T {} diff --git a/core/src/widget/handle.rs b/core/src/widget/handle.rs index f2ac213..9ea1302 100644 --- a/core/src/widget/handle.rs +++ b/core/src/widget/handle.rs @@ -169,3 +169,5 @@ fn null_ptr() -> *const W { unsafe { std::mem::transmute_copy(&[0usize; 1]) } } } + +unsafe impl Send for WidgetRef {} diff --git a/src/bin/test/assets/sungals.png b/examples/tabs/assets/sungals.png similarity index 100% rename from src/bin/test/assets/sungals.png rename to examples/tabs/assets/sungals.png diff --git a/src/bin/test/main.rs b/examples/tabs/main.rs similarity index 100% rename from src/bin/test/main.rs rename to examples/tabs/main.rs diff --git a/examples/task.rs b/examples/task.rs new file mode 100644 index 0000000..7860e50 --- /dev/null +++ b/examples/task.rs @@ -0,0 +1,33 @@ +use std::time::Duration; + +iris::state_prelude!(DefaultRsc); + +fn main() { + DefaultApp::::run(); +} + +#[derive(DefaultUiState)] +struct State { + ui_state: DefaultUiState, +} + +impl DefaultAppState for State { + fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc, _: Proxy) -> Self { + let rect = rect(Color::RED).add(rsc); + rect.on(CursorSense::click(), move |_, rsc| { + rsc.tasks.spawn(async move |ctx| { + tokio::time::sleep(Duration::from_secs(1)).await; + ctx.update(move |_, rsc| { + let rect = rect(rsc); + if rect.color == Color::RED { + rect.color = Color::GREEN; + } else { + rect.color = Color::RED; + } + }); + }); + }) + .set_root(rsc); + Self { ui_state } + } +} diff --git a/src/default/mod.rs b/src/default/mod.rs index ee526d2..1e9819a 100644 --- a/src/default/mod.rs +++ b/src/default/mod.rs @@ -17,6 +17,7 @@ mod event; mod input; mod render; mod sense; +mod task; pub use app::*; pub use attr::*; @@ -24,6 +25,7 @@ pub use event::*; pub use input::*; pub use render::*; pub use sense::*; +pub use task::*; pub type Proxy = EventLoopProxy; @@ -79,19 +81,25 @@ pub trait DefaultAppState: HasDefaultUiState { } } -pub struct DefaultRsc { +pub struct DefaultRsc { pub ui: Ui, pub events: EventManager, + pub tasks: Tasks, _state: PhantomData, } -impl Default for DefaultRsc { - fn default() -> Self { - Self { - ui: Default::default(), - events: Default::default(), - _state: Default::default(), - } +impl DefaultRsc { + fn init(window: Arc) -> (Self, TaskMsgReceiver) { + let (tasks, recv) = Tasks::init(window); + ( + Self { + ui: Default::default(), + events: Default::default(), + tasks, + _state: Default::default(), + }, + recv, + ) } } @@ -105,9 +113,11 @@ impl HasUi for DefaultRsc { } } -impl HasEvents for DefaultRsc { +impl HasState for DefaultRsc { type State = State; +} +impl HasEvents for DefaultRsc { fn events(&self) -> &EventManager { &self.events } @@ -120,11 +130,9 @@ impl HasEvents for DefaultRsc { pub struct DefaultApp { rsc: DefaultRsc, state: State, + task_recv: TaskMsgReceiver>, } -// impl StateLike for DefaultRsc { -// } -// impl AppState for DefaultApp { type Event = State::Event; @@ -132,9 +140,14 @@ impl AppState for DefaultApp { let window = event_loop .create_window(State::window_attributes()) .unwrap(); - let mut rsc = DefaultRsc::default(); - let state = State::new(DefaultUiState::new(window), &mut rsc, proxy); - Self { rsc, state } + let default_state = DefaultUiState::new(window); + let (mut rsc, task_recv) = DefaultRsc::init(default_state.window.clone()); + let state = State::new(default_state, &mut rsc, proxy); + Self { + rsc, + state, + task_recv, + } } fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { @@ -142,6 +155,10 @@ impl AppState for DefaultApp { } fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { + for update in self.task_recv.try_iter() { + update(&mut self.state, &mut self.rsc); + } + let ui_state = self.state.default_state_mut(); let input_changed = ui_state.input.event(&event); let cursor_state = ui_state.cursor_state().clone(); diff --git a/src/default/task.rs b/src/default/task.rs new file mode 100644 index 0000000..51f6a66 --- /dev/null +++ b/src/default/task.rs @@ -0,0 +1,85 @@ +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, + }, +}; +use winit::window::Window; + +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, + window: 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(window: 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, + window, + }, + msgs_recv, + ) + } + + pub fn spawn AsyncFnOnce(&'a mut TaskCtx) + 'static + std::marker::Send>( + &mut self, + task: F, + ) where + for<'a> ,)>>::CallOnceFuture: Send, + { + let send = self.msg_send.clone(); + let window = self.window.clone(); + let _ = self.start.send(Box::pin(async move { + let mut ctx = TaskCtx::new(send); + task(&mut ctx).await; + window.request_redraw(); + })); + } +} + +async fn listen(mut recv: AsyncReceiver) { + while let Some(task) = recv.recv().await { + tokio::spawn(task); + } +} diff --git a/src/event.rs b/src/event.rs index 9d4cfe7..f8960ea 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,17 +1,20 @@ -use crate::prelude::*; +use iris_core::*; +use iris_macro::*; + +use crate::default::{TaskCtx, Tasks}; pub mod eventable { use super::*; widget_trait! { - pub trait Eventable; + pub trait Eventable; fn on( self, event: E, f: impl for<'a> WidgetEventFn::Data<'a>, WL::Widget>, ) -> impl WidgetIdFn { - move |state| { - let id = self.add(state); - state.register_event(id, event.into_event(), move |ctx, rsc| { + move |rsc| { + let id = self.add(rsc); + rsc.register_event(id, event.into_event(), move |ctx, rsc| { f(&mut EventIdCtx { widget: id, state: ctx.state, @@ -22,4 +25,52 @@ pub mod eventable { } } } + + // widget_trait! { + // pub trait TaskEventable; + // fn task_on( + // self, + // event: E, + // f: impl for<'a> AsyncWidgetEventFn::Data<'a>, WL::Widget>, + // ) -> impl WidgetIdFn { + // move |rsc| { + // let id = self.add(rsc); + // rsc.register_event(id, event.into_event(), move |ctx, rsc| { + // rsc.tasks_mut().spawn(async move |task| { + // f(&mut AsyncEventIdCtx { + // widget: id, + // state: ctx.state, + // data: ctx.data, + // task, + // }, rsc).await; + // }); + // }); + // id + // } + // } + // } +} + +pub trait HasTasks: Sized + HasState + HasEvents { + fn tasks_mut(&mut self) -> &mut Tasks; +} + +pub trait AsyncWidgetEventFn: + AsyncFn(&mut AsyncEventIdCtx, &mut Rsc) + 'static +{ +} +impl< + Rsc: HasEvents, + F: AsyncFn(&mut AsyncEventIdCtx, &mut Rsc) + 'static, + Data, + W: ?Sized, +> AsyncWidgetEventFn for F +{ +} + +pub struct AsyncEventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> { + pub widget: WidgetRef, + pub state: &'a mut Rsc::State, + pub data: &'a mut Data, + pub task: &'a mut TaskCtx, } diff --git a/src/lib.rs b/src/lib.rs index 7d776fa..81f1a3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ #![feature(associated_type_defaults)] #![feature(unsize)] #![feature(option_into_flat_iter)] +#![feature(async_fn_traits)] pub mod default; pub mod event; diff --git a/src/typed.rs b/src/typed.rs index 4f4b1cd..d22d8fb 100644 --- a/src/typed.rs +++ b/src/typed.rs @@ -30,7 +30,7 @@ macro_rules! event_state { } } } - $vis type EventManager = $crate::prelude::EventManager<<$rsc as HasEvents>::State>; + $vis type EventManager = $crate::prelude::EventManager<<$rsc as HasState>::State>; $vis use local_event_trait::*; }; } diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index a6e4500..594cc98 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -84,7 +84,7 @@ widget_trait! { } fn scrollable(self) -> impl WidgetIdFn where Rsc: HasEvents { - use eventable::*; + use eventable::Eventable; move |state| { Scroll::new(self.add_strong(state), Axis::Y) .on(CursorSense::Scroll, |ctx, rsc| {