diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 375fef087..23af30627 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -12,4 +12,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version ="^0.1.7"} +dioxus-core = { path = "../../packages/core", version = "^0.1.7" } + + +[dev-dependencies] +futures-util = { version = "0.3", default-features = false } diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 593371b23..5636b4031 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -1,6 +1,7 @@ +#![warn(clippy::pedantic)] + use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; -use dioxus_core::exports::futures_channel; use dioxus_core::{LazyNodes, ScopeState, TaskId}; use std::future::Future; use std::{cell::Cell, rc::Rc}; @@ -25,34 +26,35 @@ use std::{cell::Cell, rc::Rc}; pub fn use_coroutine<'a, F>( cx: &'a ScopeState, create_future: impl FnOnce() -> F + 'a, -) -> UseCoroutine +) -> UseCoroutine<'a, F> where F: Future + 'static, { let state = cx.use_hook(move |_| CoroutineInner { - _id: None, - running: Default::default(), + task_id: Cell::new(None), + running: std::rc::Rc::default(), + run_count: Cell::new(0), }); - let mut bump = None; - // as an optimization, we use the bump arena to allocate the callback instead of boxes // that way we don't always call the constructor, but it's still efficient - cx.render(LazyNodes::new(move |f| { - bump.replace(f.bump()); - f.static_text("") - })); - - let mut slot = Some(create_future); - // safety: bumpalo is limited in constructing unsized box types, so we have to do it through dynamic dispatch let boxed: BumpBox<'a, dyn FnMut() -> F + 'a> = unsafe { - BumpBox::from_raw(bump.unwrap().alloc(move || { - let inner = slot.take().unwrap(); + let mut bump = None; + cx.render(LazyNodes::new(move |f| { + bump.replace(f.bump()); + f.static_text("") + })); + let mut slot = Some(create_future); + let bump = bump.expect("bump is assigned during render"); + BumpBox::from_raw(bump.alloc(move || { + let inner = slot.take().expect("closure to not be called twice"); inner() })) }; + state.run_count.set(state.run_count.get() + 1); + UseCoroutine { inner: state, create_fut: Cell::new(Some(boxed)), @@ -62,7 +64,8 @@ where struct CoroutineInner { running: Rc>, - _id: Option, + task_id: Cell>, + run_count: Cell, } pub struct UseCoroutine<'a, F: Future + 'static> { @@ -73,35 +76,50 @@ pub struct UseCoroutine<'a, F: Future + 'static> { impl<'a, F: Future + 'static> UseCoroutine<'a, F> { pub fn auto_start(&self, start: bool) -> &Self { - todo!() + if start && self.inner.run_count.get() == 1 { + self.start(); + } + self + } + + pub fn is_running(&self) -> bool { + self.inner.running.get() } pub fn start(&self) { if !self.is_running() { if let Some(mut fut) = self.create_fut.take() { let fut = fut(); - self.cx.push_future(fut); + let ready_handle = self.inner.running.clone(); + + let task = self.cx.push_future(async move { + ready_handle.set(true); + fut.await; + ready_handle.set(false); + }); + + self.inner.task_id.set(Some(task)); } } } - pub fn is_running(&self) -> bool { - // self.inner.running.get() - false - } + // todo: wire these up, either into the task system or into the coroutine system itself + // we would have change how we poll the coroutine and how its awaken - pub fn resume(&self) { - // self.cx.push_task(fut) - } - - pub fn stop(&self) {} - - pub fn restart(&self) {} + // pub fn resume(&self) {} + // pub fn stop(&self) {} + // pub fn restart(&self) {} } -#[test] -fn it_works() { +#[cfg(test)] +mod tests { + #![allow(unused)] + + use super::*; + use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::prelude::*; + use futures_util::StreamExt; + fn app(cx: Scope) -> Element { let poll_tasks = use_coroutine(&cx, || async { loop { @@ -113,4 +131,22 @@ fn it_works() { todo!() } + + fn app_with_channel(cx: Scope) -> Element { + let (tx, mut rx) = unbounded(); + + let tx = cx.use_hook(|_| tx); + + let poll_tasks = use_coroutine(&cx, move || async move { + while let Some(msg) = rx.next().await { + println!("polling tasks: {}", msg); + } + }); + + poll_tasks.auto_start(true); + + tx.unbounded_send("asd").unwrap(); + + todo!() + } }