diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 44c4b34a8..55c5c31e3 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -110,8 +110,8 @@ impl SharedResources { } /// this is unsafe because the caller needs to track which other scopes it's already using - pub unsafe fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> { - let inner = &mut *self.components.get(); + pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> { + let inner = unsafe { &mut *self.components.get() }; inner.get_mut(idx.0) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index e3a8003bf..09c4ea92e 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -108,10 +108,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> { /// this is mostly useful for testing /// /// This will PANIC if any component elements are passed in. - pub fn new_headless( - // edits: &'r mut Vec>, - shared: &'bump SharedResources, - ) -> Self { + pub fn new_headless(shared: &'bump SharedResources) -> Self { Self { edits: Mutations { edits: Vec::new() }, scope_stack: smallvec![ScopeId(0)], diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 6804a9f95..3fe04f28f 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -112,6 +112,8 @@ pub enum VirtualEvent { GarbageCollection, /// A type of "immediate" event scheduled by components + /// + /// Usually called through "set_state" ScheduledUpdate { height: u32, }, @@ -158,6 +160,32 @@ pub enum VirtualEvent { MouseEvent(on::MouseEvent), PointerEvent(on::PointerEvent), } +impl VirtualEvent { + pub fn is_input_event(&self) -> bool { + match self { + VirtualEvent::ClipboardEvent(_) + | VirtualEvent::CompositionEvent(_) + | VirtualEvent::KeyboardEvent(_) + | VirtualEvent::FocusEvent(_) + | VirtualEvent::FormEvent(_) + | VirtualEvent::SelectionEvent(_) + | VirtualEvent::TouchEvent(_) + | VirtualEvent::UIEvent(_) + | VirtualEvent::WheelEvent(_) + | VirtualEvent::MediaEvent(_) + | VirtualEvent::AnimationEvent(_) + | VirtualEvent::TransitionEvent(_) + | VirtualEvent::ToggleEvent(_) + | VirtualEvent::MouseEvent(_) + | VirtualEvent::PointerEvent(_) => true, + + VirtualEvent::GarbageCollection + | VirtualEvent::ScheduledUpdate { .. } + | VirtualEvent::AsyncEvent { .. } + | VirtualEvent::SuspenseEvent { .. } => false, + } + } +} impl std::fmt::Debug for VirtualEvent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 090282728..e6f27c6c5 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -55,8 +55,6 @@ pub struct VirtualDom { active_fibers: Vec>, - pending_events: BTreeMap, - // for managing the props that were used to create the dom #[doc(hidden)] _root_prop_type: std::any::TypeId, @@ -147,7 +145,6 @@ impl VirtualDom { _root_props: root_props, shared: components, active_fibers: Vec::new(), - pending_events: BTreeMap::new(), _root_prop_type: TypeId::of::

(), } } @@ -222,42 +219,42 @@ impl VirtualDom { Ok(edits) } - async fn select_next_event(&mut self) -> Option { - let mut receiver = self.shared.task_receiver.borrow_mut(); + // async fn select_next_event(&mut self) -> Option { + // let mut receiver = self.shared.task_receiver.borrow_mut(); - // drain the in-flight events so that we can sort them out with the current events - while let Ok(Some(trigger)) = receiver.try_next() { - log::info!("retrieving event from receiver"); - let key = self.shared.make_trigger_key(&trigger); - self.pending_events.insert(key, trigger); - } + // // drain the in-flight events so that we can sort them out with the current events + // while let Ok(Some(trigger)) = receiver.try_next() { + // log::info!("retrieving event from receiver"); + // let key = self.shared.make_trigger_key(&trigger); + // self.pending_events.insert(key, trigger); + // } - if self.pending_events.is_empty() { - // Continuously poll the future pool and the event receiver for work - let mut tasks = self.shared.async_tasks.borrow_mut(); - let tasks_tasks = tasks.next(); + // if self.pending_events.is_empty() { + // // Continuously poll the future pool and the event receiver for work + // let mut tasks = self.shared.async_tasks.borrow_mut(); + // let tasks_tasks = tasks.next(); - let mut receiver = self.shared.task_receiver.borrow_mut(); - let reciv_task = receiver.next(); + // let mut receiver = self.shared.task_receiver.borrow_mut(); + // let reciv_task = receiver.next(); - futures_util::pin_mut!(tasks_tasks); - futures_util::pin_mut!(reciv_task); + // futures_util::pin_mut!(tasks_tasks); + // futures_util::pin_mut!(reciv_task); - let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await { - futures_util::future::Either::Left((trigger, _)) => trigger, - futures_util::future::Either::Right((trigger, _)) => trigger, - } - .unwrap(); - let key = self.shared.make_trigger_key(&trigger); - self.pending_events.insert(key, trigger); - } + // let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await { + // futures_util::future::Either::Left((trigger, _)) => trigger, + // futures_util::future::Either::Right((trigger, _)) => trigger, + // } + // .unwrap(); + // let key = self.shared.make_trigger_key(&trigger); + // self.pending_events.insert(key, trigger); + // } - // pop the most important event off - let key = self.pending_events.keys().next().unwrap().clone(); - let trigger = self.pending_events.remove(&key).unwrap(); + // // pop the most important event off + // let key = self.pending_events.keys().next().unwrap().clone(); + // let trigger = self.pending_events.remove(&key).unwrap(); - Some(trigger) - } + // Some(trigger) + // } /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete. /// @@ -312,84 +309,28 @@ impl VirtualDom { ) -> Result> { let cur_component = self.base_scope; - let mut mutations = Mutations { edits: Vec::new() }; + let mut diff_machine = + DiffMachine::new(Mutations { edits: Vec::new() }, cur_component, &self.shared); - let mut diff_machine = DiffMachine::new(mutations, cur_component, &self.shared); - - let must_be_re_rendered = HashSet::::new(); + /* + Strategy: + 1. Check if there are any events in the receiver. + 2. If there are, process them and create a new fiber. + 3. If there are no events, then choose a fiber to work on. + 4. If there are no fibers, then wait for the next event from the receiver. + 5. While processing a fiber, periodically check if we're out of time + 6. If we are almost out of time, then commit our edits to the realdom + 7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported) + */ + // 1. Consume any pending events and create new fibers let mut receiver = self.shared.task_receiver.borrow_mut(); - - // - - loop { - if deadline_exceeded() { - break; - } - - /* - Strategy: - 1. Check if there are any events in the receiver. - 2. If there are, process them and create a new fiber. - 3. If there are no events, then choose a fiber to work on. - 4. If there are no fibers, then wait for the next event from the receiver. - 5. While processing a fiber, periodically check if we're out of time - 6. If we are almost out of time, then commit our edits to the realdom - 7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported) - - - - - The user of this method will loop over "run", waiting for edits and then committing them IE - - - task::spawn_local(async { - let vdom = VirtualDom::new(App); - loop { - let deadline = wait_for_idle().await; - let mutations = vdom.run_with_deadline(deadline); - realdom.apply_edits(mutations.edits); - realdom.apply_refs(mutations.refs); - } - }); - - - let vdom = VirtualDom::new(App); - let realdom = WebsysDom::new(App); - loop { - let deadline = wait_for_idle().await; - let mutations = vdom.run_with_deadline(deadline); - - realdom.apply_edits(mutations.edits); - realdom.apply_refs(mutations.refs); - } - - - - ``` - task::spawn_local(async move { - let vdom = VirtualDom::new(App); - loop { - let mutations = vdom.run_with_deadline(16); - realdom.apply_edits(mutations.edits)?; - realdom.apply_refs(mutations.refs)?; - } - }); - - event_loop.run(move |event, _, flow| { - - }); - ``` - */ - let mut receiver = self.shared.task_receiver.borrow_mut(); - - // match receiver.try_next() {} - - let trigger = receiver.next().await.unwrap(); + while let Ok(Some(trigger)) = receiver.try_next() { + // todo: cache the fibers + let mut fiber = Fiber::new(); match &trigger.event { - // If any user event is received, then we run the listener and let it dump "needs updates" into the queue - // + // If any input event is received, then we need to create a new fiber VirtualEvent::ClipboardEvent(_) | VirtualEvent::CompositionEvent(_) | VirtualEvent::KeyboardEvent(_) @@ -405,23 +346,21 @@ impl VirtualDom { | VirtualEvent::ToggleEvent(_) | VirtualEvent::MouseEvent(_) | VirtualEvent::PointerEvent(_) => { - let scope_id = &trigger.originator; - let scope = unsafe { self.shared.get_scope_mut(*scope_id) }; - match scope { - Some(scope) => { - scope.call_listener(trigger)?; - } - None => { - log::warn!("No scope found for event: {:#?}", scope_id); - } + if let Some(scope) = self.shared.get_scope_mut(trigger.originator) { + scope.call_listener(trigger)?; } } VirtualEvent::AsyncEvent { .. } => { - // we want to progress these events - // However, there's nothing we can do for these events, they must generate their own events. + while let Ok(Some(event)) = receiver.try_next() { + fiber.pending_scopes.push(event.originator); + } } + // These shouldn't normally be received, but if they are, it's done because some task set state manually + // Instead of batching the results, + VirtualEvent::ScheduledUpdate { height: u32 } => {} + // Suspense Events! A component's suspended node is updated VirtualEvent::SuspenseEvent { hook_idx, domnode } => { // Safety: this handler is the only thing that can mutate shared items at this moment in tim @@ -438,8 +377,8 @@ impl VirtualDom { match nodes { None => { log::warn!( - "Suspense event came through, but there were no generated nodes >:(." - ); + "Suspense event came through, but there were no generated nodes >:(." + ); } Some(nodes) => { // allocate inside the finished frame - not the WIP frame @@ -509,35 +448,13 @@ impl VirtualDom { log::debug!("should be removing scope {:#?}", scope); } } - - // Run the component - VirtualEvent::ScheduledUpdate { height: u32 } => { - let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); - - match scope.run_scope() { - Ok(_) => { - todo!(); - // let event = VirtualEvent::DiffComponent; - // let trigger = EventTrigger { - // event, - // originator: trigger.originator, - // priority: EventPriority::High, - // real_node_id: None, - // }; - // self.shared.task_sender.unbounded_send(trigger); - } - Err(_) => { - log::error!("failed to run this component!"); - } - } - } } + } - // // - // if realdom.must_commit() { - // // commit these edits and then wait for the next idle period - // realdom.commit_edits(&mut diff_machine.edits).await; - // } + while !deadline_exceeded() { + let mut receiver = self.shared.task_receiver.borrow_mut(); + + // no messages to receive, just work on the fiber } Ok(diff_machine.edits) @@ -546,6 +463,10 @@ impl VirtualDom { pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender { self.shared.task_sender.clone() } + + fn get_scope_mut(&mut self, id: ScopeId) -> Option<&mut Scope> { + unsafe { self.shared.get_scope_mut(id) } + } } // TODO! @@ -554,8 +475,6 @@ unsafe impl Sync for VirtualDom {} unsafe impl Send for VirtualDom {} struct Fiber<'a> { - trigger: EventTrigger, - // scopes that haven't been updated yet pending_scopes: Vec, @@ -570,9 +489,8 @@ struct Fiber<'a> { } impl Fiber<'_> { - fn new(trigger: EventTrigger) -> Self { + fn new() -> Self { Self { - trigger, pending_scopes: Vec::new(), pending_nodes: Vec::new(), edits: Vec::new(), diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index aa941cd75..abf9c7a83 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -2,6 +2,30 @@ //! -------------- //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. +/* +From Google's guide on rAF and rIC: +-------- + +If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed, +which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside + of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next + frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout, + which is a potential performance bottleneck. + +Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable, +and as such we could easily go past the deadline the browser provided. + +The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the +browser with that type of work in mind. That means that our code will need to use a document fragment, which can then +be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback +to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback. + +Essentially: +------------ +- Do the VDOM work during the idlecallback +- Do DOM work in the next requestAnimationFrame callback +*/ + use std::rc::Rc; pub use crate::cfg::WebConfig;