diff --git a/.vscode/settings.json b/.vscode/settings.json index a3b8a0b79..c7ce540f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "rust-analyzer.inlayHints.enable": false, + "rust-analyzer.inlayHints.enable": true, "rust-analyzer.cargo.allFeatures": true } diff --git a/packages/core-macro/examples/fc.rs b/packages/core-macro/examples/fc.rs deleted file mode 100644 index f328e4d9d..000000000 --- a/packages/core-macro/examples/fc.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/packages/core-macro/examples/prop_test.rs b/packages/core-macro/examples/prop_test.rs index d80c0cc35..525b3c8b0 100644 --- a/packages/core-macro/examples/prop_test.rs +++ b/packages/core-macro/examples/prop_test.rs @@ -20,5 +20,5 @@ struct SomeProps { /// This implementation does not require a "PartialEq" because it does not memoize #[derive(dioxus_core_macro::Props)] struct SomePropsTwo<'a> { - a: &'a str, + _a: &'a str, } diff --git a/packages/core-macro/examples/rsxt.rs b/packages/core-macro/examples/rsxt.rs deleted file mode 100644 index 2a6434b5d..000000000 --- a/packages/core-macro/examples/rsxt.rs +++ /dev/null @@ -1,125 +0,0 @@ -pub mod dioxus { - pub mod builder { - pub struct Builder; - - struct AttrVal; - - impl Into for &'static str { - fn into(self) -> AttrVal { - todo!() - } - } - - impl Into for String { - fn into(self) -> AttrVal { - todo!() - } - } - // impl From for AttrVal { - // fn from(_: T) -> Self { - // todo!() - // } - // } - - impl Builder { - // fn attr(mut self, key: &str, value: impl Into) -> Self { - pub fn attr(self, _key: &str, _value: T) -> Self { - Self - } - - pub fn on(self, _key: &str, _value: T) -> Self { - Self - } - - pub fn finish(self) { - // Self - } - } - - pub struct Bump; - pub fn div(_bump: &Bump) -> Builder { - todo!() - } - pub fn h1(_bump: &Bump) -> Builder { - todo!() - } - pub fn h2(_bump: &Bump) -> Builder { - todo!() - } - } -} - -pub fn main() { - // render(rsx! { - // div { // we can actually support just a list of nodes too - // h1 {"Hello Dioxus"} - // p {"This is a beautful app you're building"} - // section { - // "custom section to the rescue", - // class: "abc123" - // } - // span { - // class: "abc123" - // "Try backwards too." - // "Anything goes!" - // "As long as it's within the rules" - // {0..10.map(|f| rsx!{ - // div { - // h3 {"totally okay to drop in iterators and expressions"} - // p {"however, debug information is lost"} - // } - // })} - // } - // span { - // "Feel free" - // class: "abc123" - // "To mix to your heart's content" - // } - // span { class: "some-very-long-and-tedious-class-name-is-now-separated" - // "Very ergonomic" - // } - // span { "Innovative design 🛠" - // class: "some-very-long-and-tedious-class-name-is-now-separated" - // } - // } - // }); - - let _g = String::from("asd"); - - // let lazy = rsx! { - // div { - // a: "asd", - // a: "asd", - // a: "asd", - // a: "asd", - // a: "asd", - // // a: {rsx!{ h1 {"hello world"} }}, // include - // a: {&g}, - // b: {1 + 2}, - // onclick: {move |e: ()| { - // println!("hello world!") - // }}, - // div { - // a: "asd" - // div { - // div { - // div { - - // } - // } - // } - // } - // h1 { - - // } - // h2 { - // "child" - // } - // "Childnode" - // } - // }; - - // render(lazy); -} - -fn render(_f: impl Fn(&dioxus::builder::Bump)) {} diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index f744e6070..e69fd9511 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -22,7 +22,7 @@ criterion_group!(mbenches, create_rows); criterion_main!(mbenches); fn create_rows(c: &mut Criterion) { - static App: FC<()> = |cx, props| { + static App: FC<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000).map(|f| { let label = Label::new(&mut rng); diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 02bebb43d..bb170873e 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -1,8 +1,12 @@ use dioxus_core::prelude::*; -fn main() {} +fn main() { + let mut dom = VirtualDom::new(EXAMPLE); + dom.rebuild(); + println!("{}", dom); +} -pub static Example: FC<()> = |cx, props| { +pub static EXAMPLE: FC<()> = |cx, _| { let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); cx.render(LazyNodes::new(move |cx| { diff --git a/packages/core/examples/async.rs b/packages/core/examples/async.rs index a60dfb0f4..6575afe5f 100644 --- a/packages/core/examples/async.rs +++ b/packages/core/examples/async.rs @@ -1,39 +1,15 @@ use dioxus_core::prelude::*; -fn main() {} +fn main() { + let mut dom = VirtualDom::new(App); + dom.rebuild(); +} const App: FC<()> = |cx, props| { - // create a new future - let _fut = cx.use_hook( - |_| { - // - async { loop {} } - // Box::pin(async { loop {} }) as Pin>> - }, - |f| f, - |_| {}, - ); - // let g = unsafe { Pin::new_unchecked(fut) }; + let id = cx.scope_id(); + cx.submit_task(Box::pin(async move { id })); - // cx.submit_task(fut); + let (handle, contents) = use_task(cx, || async { "hello world".to_string() }); todo!() }; - -const Task: FC<()> = |cx, props| { - let (_task, _res) = use_task(cx, || async { true }); - // task.pause(); - // task.restart(); - // task.stop(); - // task.drop(); - - // - - let _s = use_task(cx, || async { "hello world".to_string() }); - - todo!() -}; - -fn use_mut(_cx: Context, _f: impl FnOnce() -> T) -> &mut T { - todo!() -} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 9dea0c71a..34ec66644 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -137,7 +137,7 @@ impl<'a> SavedDiffWork<'a> { std::mem::transmute(self) } - pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> { + pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> { let extended: SavedDiffWork<'b> = std::mem::transmute(self); DiffMachine { vdom, diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index a0d12609e..f90f42d59 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -15,9 +15,21 @@ use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc}; /// Awaits the given task, forcing the component to re-render when the value is ready. /// +/// Returns the handle to the task and the value (if it is ready, else None). /// +/// ``` +/// static Example: FC<()> = |cx, props| { +/// let (task, value) = use_task(|| async { +/// timer::sleep(Duration::from_secs(1)).await; +/// "Hello World" +/// }); /// -/// +/// match contents { +/// Some(contents) => rsx!(cx, div { "{title}" }), +/// None => rsx!(cx, div { "Loading..." }), +/// } +/// }; +/// ``` pub fn use_task<'src, Out, Fut, Init>( cx: Context<'src>, task_initializer: Init, diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 542b28e01..922cfa2fa 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -101,6 +101,10 @@ pub enum SchedulerMsg { Immediate(ScopeId), // tasks + Task(TaskMsg), +} + +pub enum TaskMsg { SubmitTask(FiberTask, u64), ToggleTask(u64), PauseTask(u64), @@ -152,7 +156,9 @@ pub(crate) struct Scheduler { pub garbage_scopes: HashSet, - pub lane: PriorityLane, + pub dirty_scopes: IndexSet, + pub saved_state: Option>, + pub in_progress: bool, } impl Scheduler { @@ -181,7 +187,9 @@ impl Scheduler { let task_id = task_counter.get(); task_counter.set(task_id + 1); sender - .unbounded_send(SchedulerMsg::SubmitTask(fiber_task, task_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask( + fiber_task, task_id, + ))) .unwrap(); TaskHandle { our_id: task_id, @@ -214,6 +222,12 @@ impl Scheduler { let async_tasks = FuturesUnordered::new(); + // push a task that would never resolve - prevents us from immediately aborting the scheduler + async_tasks.push(Box::pin(async { + std::future::pending::<()>().await; + ScopeId(0) + }) as FiberTask); + Self { pool, @@ -235,24 +249,9 @@ impl Scheduler { garbage_scopes: HashSet::new(), - // current_priority: EventPriority::Low, - lane: PriorityLane::new(), - } - } - - pub fn manually_poll_events(&mut self) { - while let Ok(Some(msg)) = self.receiver.try_next() { - match msg { - SchedulerMsg::UiEvent(event) => self.ui_events.push_front(event), - SchedulerMsg::Immediate(im) => self.pending_immediates.push_front(im), - - // task stuff - SchedulerMsg::SubmitTask(_, _) => todo!(), - SchedulerMsg::ToggleTask(_) => todo!(), - SchedulerMsg::PauseTask(_) => todo!(), - SchedulerMsg::ResumeTask(_) => todo!(), - SchedulerMsg::DropTask(_) => todo!(), - } + dirty_scopes: Default::default(), + saved_state: None, + in_progress: false, } } @@ -272,31 +271,27 @@ impl Scheduler { } } - use EventPriority::*; + // use EventPriority::*; - match priority { - Immediate => todo!(), - High => todo!(), - Medium => todo!(), - Low => todo!(), - } + // match priority { + // Immediate => todo!(), + // High => todo!(), + // Medium => todo!(), + // Low => todo!(), + // } discrete } fn prepare_work(&mut self) { - while let Some(trigger) = self.ui_events.pop_back() { - if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {} - } + // while let Some(trigger) = self.ui_events.pop_back() { + // if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {} + // } } // nothing to do, no events on channels, no work pub fn has_any_work(&self) -> bool { - self.lane.has_work() || self.has_pending_events() - } - - pub fn has_pending_events(&self) -> bool { - !self.ui_events.is_empty() + !(self.dirty_scopes.is_empty() && self.ui_events.is_empty()) } /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work @@ -304,35 +299,39 @@ impl Scheduler { fn save_work(&mut self, lane: SavedDiffWork) { let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) }; - self.lane.saved_state = Some(saved); + self.saved_state = Some(saved); } - fn load_work(&mut self) -> SavedDiffWork<'static> { - // match self.current_priority { - // EventPriority::Immediate => todo!(), - // EventPriority::High => todo!(), - // EventPriority::Medium => todo!(), - // EventPriority::Low => todo!(), - // } - unsafe { self.lane.saved_state.take().unwrap().extend() } + unsafe fn load_work(&mut self) -> SavedDiffWork<'static> { + self.saved_state.take().unwrap().extend() } pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) { match msg { + // + SchedulerMsg::Task(msg) => todo!(), + SchedulerMsg::Immediate(_) => todo!(), SchedulerMsg::UiEvent(event) => { // - self.handle_ui_event(event); - } + let (discrete, priority) = event_meta(&event); - // - SchedulerMsg::SubmitTask(_, _) => todo!(), - SchedulerMsg::ToggleTask(_) => todo!(), - SchedulerMsg::PauseTask(_) => todo!(), - SchedulerMsg::ResumeTask(_) => todo!(), - SchedulerMsg::DropTask(_) => todo!(), + if let Some(scope) = self.pool.get_scope_mut(event.scope) { + if let Some(element) = event.mounted_dom_id { + // TODO: bubble properly here + scope.call_listener(event.event, element); + + while let Ok(Some(dirty_scope)) = self.receiver.try_next() { + // + // self.add_dirty_scope(dirty_scope, trigger.priority) + } + } + } + + discrete; + } } } @@ -346,22 +345,22 @@ impl Scheduler { ) -> bool { // Work through the current subtree, and commit the results when it finishes // When the deadline expires, give back the work - let saved_state = self.load_work(); + let saved_state = unsafe { self.load_work() }; // We have to split away some parts of ourself - current lane is borrowed mutably - let mut shared = self.pool.clone(); - let mut machine = unsafe { saved_state.promote(&mut shared) }; + let shared = self.pool.clone(); + let mut machine = unsafe { saved_state.promote(&shared) }; if machine.stack.is_empty() { let shared = self.pool.clone(); - self.lane.dirty_scopes.sort_by(|a, b| { + self.dirty_scopes.sort_by(|a, b| { let h1 = shared.get_scope(*a).unwrap().height; let h2 = shared.get_scope(*b).unwrap().height; h1.cmp(&h2) }); - if let Some(scope) = self.lane.dirty_scopes.pop() { + if let Some(scope) = self.dirty_scopes.pop() { let component = self.pool.get_scope(scope).unwrap(); let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); machine.stack.push(DiffInstruction::Diff { new, old }); @@ -378,7 +377,7 @@ impl Scheduler { false } else { for node in saved.seen_scopes.drain() { - self.lane.dirty_scopes.remove(&node); + self.dirty_scopes.remove(&node); } let mut new_mutations = Mutations::new(); @@ -395,9 +394,9 @@ impl Scheduler { /// Uses some fairly complex logic to schedule what work should be produced. /// /// Returns a list of successful mutations. - pub async fn work_with_deadline<'a>( + pub fn work_with_deadline<'a>( &'a mut self, - deadline: impl Future, + mut deadline: impl FnMut() -> bool, ) -> Vec> { /* Strategy: @@ -429,33 +428,29 @@ impl Scheduler { - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff? */ let mut committed_mutations = Vec::>::new(); - pin_mut!(deadline); loop { - // Move work out of the scheduler message receiver and into dedicated message lanes - self.manually_poll_events(); + // switch our priority, pop off any work + for event in self.ui_events.drain(..) { + if let Some(scope) = self.pool.get_scope_mut(event.scope) { + if let Some(element) = event.mounted_dom_id { + // TODO: bubble properly here + scope.call_listener(event.event, element); - // Wait for any new events if we have nothing to do - // todo: poll the events once even if there is work to do to prevent starvation - if !self.has_any_work() { - futures_util::select! { - // todo: find a solution for the exhausted queue problem - // msg = self.async_tasks.next() => {} - msg = self.receiver.next() => { - log::debug!("received work in scheduler"); - self.handle_channel_msg(msg.unwrap()) - }, - _ = (&mut deadline).fuse() => return committed_mutations, + while let Ok(Some(dirty_scope)) = self.receiver.try_next() { + match dirty_scope { + SchedulerMsg::Immediate(im) => { + self.dirty_scopes.insert(im); + } + _ => todo!(), + } + } + } } } - // switch our priority, pop off any work - self.prepare_work(); - - let mut deadline_reached = || (&mut deadline).now_or_never().is_some(); - let finished_before_deadline = - self.work_on_current_lane(&mut deadline_reached, &mut committed_mutations); + self.work_on_current_lane(&mut deadline, &mut committed_mutations); if !finished_before_deadline { break; @@ -471,7 +466,9 @@ impl Scheduler { pub fn work_sync<'a>(&'a mut self) -> Vec> { let mut committed_mutations = Vec::new(); - self.manually_poll_events(); + while let Ok(Some(msg)) = self.receiver.try_next() { + self.handle_channel_msg(msg); + } if !self.has_any_work() { return committed_mutations; @@ -534,23 +531,3 @@ impl Scheduler { } } } - -pub(crate) struct PriorityLane { - pub dirty_scopes: IndexSet, - pub saved_state: Option>, - pub in_progress: bool, -} - -impl PriorityLane { - pub fn new() -> Self { - Self { - saved_state: None, - dirty_scopes: Default::default(), - in_progress: false, - } - } - - fn has_work(&self) -> bool { - !self.dirty_scopes.is_empty() || self.in_progress - } -} diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs index 8306eb529..95540d153 100644 --- a/packages/core/src/tasks.rs +++ b/packages/core/src/tasks.rs @@ -12,28 +12,28 @@ impl TaskHandle { /// This method is not synchronous - your task will not stop immediately. pub fn toggle(&self) { self.sender - .unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id))) .unwrap() } /// This method is not synchronous - your task will not stop immediately. pub fn resume(&self) { self.sender - .unbounded_send(SchedulerMsg::ResumeTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id))) .unwrap() } /// This method is not synchronous - your task will not stop immediately. pub fn stop(&self) { self.sender - .unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id))) .unwrap() } /// This method is not synchronous - your task will not stop immediately. pub fn restart(&self) { self.sender - .unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id))) .unwrap() } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 6c2233f13..1c732d862 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -334,11 +334,8 @@ impl VirtualDom { /// applied the edits. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. - pub async fn run_with_deadline( - &mut self, - deadline: impl Future, - ) -> Vec> { - self.scheduler.work_with_deadline(deadline).await + pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec> { + self.scheduler.work_with_deadline(deadline) } pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender { @@ -355,12 +352,38 @@ impl VirtualDom { log::debug!("No active work.... waiting for some..."); use futures_util::StreamExt; + + // right now this won't poll events if there is ongoing work + // in the future we want to prioritize some events over ongoing work + // this is coming in the "priorities" PR + + // Wait for any new events if we have nothing to do + // todo: poll the events once even if there is work to do to prevent starvation futures_util::select! { - // // hmm - will this resolve to none if there are no async tasks? - // _ = self.scheduler.async_tasks.next() => { - // log::debug!("async task completed!"); - // } - msg = self.scheduler.receiver.next() => self.scheduler.handle_channel_msg(msg.unwrap()), + _ = self.scheduler.async_tasks.next() => {} + msg = self.scheduler.receiver.next() => { + match msg.unwrap() { + SchedulerMsg::Task(t) => todo!(), + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + } + }, + } + + while let Ok(Some(msg)) = self.scheduler.receiver.try_next() { + match msg { + SchedulerMsg::Task(t) => todo!(), + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + } } } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index a6588f07a..dc4b2373c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -61,6 +61,7 @@ use dioxus::prelude::Properties; use dioxus::virtual_dom::VirtualDom; pub use dioxus_core as dioxus; use dioxus_core::prelude::FC; +use futures_util::FutureExt; mod cache; mod cfg; @@ -145,10 +146,10 @@ pub async fn run_with_props(root: FC, root_props: T, dom.wait_for_work().await; // wait for the mainthread to schedule us in - let deadline = work_loop.wait_for_idle_time().await; + let mut deadline = work_loop.wait_for_idle_time().await; // run the virtualdom work phase until the frame deadline is reached - let mutations = dom.run_with_deadline(deadline).await; + let mutations = dom.run_with_deadline(|| (&mut deadline).now_or_never().is_some()); // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; diff --git a/packages/web/src/ric_raf.rs b/packages/web/src/ric_raf.rs index 0a914540c..08a3b89f5 100644 --- a/packages/web/src/ric_raf.rs +++ b/packages/web/src/ric_raf.rs @@ -33,8 +33,10 @@ impl RafLoop { .call0(&JsValue::NULL) .unwrap(); + let window = web_sys::window().unwrap(); + Self { - window: web_sys::window().unwrap(), + window, raf_receiver, raf_closure, ric_receiver,