wip: on collaborative scheduling

This commit is contained in:
Jonathan Kelley 2021-08-09 02:37:11 -04:00
parent fac42339c2
commit 1a323835c8
5 changed files with 122 additions and 155 deletions

View file

@ -110,8 +110,8 @@ impl SharedResources {
} }
/// this is unsafe because the caller needs to track which other scopes it's already using /// 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> { pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
let inner = &mut *self.components.get(); let inner = unsafe { &mut *self.components.get() };
inner.get_mut(idx.0) inner.get_mut(idx.0)
} }

View file

@ -108,10 +108,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
/// this is mostly useful for testing /// this is mostly useful for testing
/// ///
/// This will PANIC if any component elements are passed in. /// This will PANIC if any component elements are passed in.
pub fn new_headless( pub fn new_headless(shared: &'bump SharedResources) -> Self {
// edits: &'r mut Vec<DomEdit<'bump>>,
shared: &'bump SharedResources,
) -> Self {
Self { Self {
edits: Mutations { edits: Vec::new() }, edits: Mutations { edits: Vec::new() },
scope_stack: smallvec![ScopeId(0)], scope_stack: smallvec![ScopeId(0)],

View file

@ -112,6 +112,8 @@ pub enum VirtualEvent {
GarbageCollection, GarbageCollection,
/// A type of "immediate" event scheduled by components /// A type of "immediate" event scheduled by components
///
/// Usually called through "set_state"
ScheduledUpdate { ScheduledUpdate {
height: u32, height: u32,
}, },
@ -158,6 +160,32 @@ pub enum VirtualEvent {
MouseEvent(on::MouseEvent), MouseEvent(on::MouseEvent),
PointerEvent(on::PointerEvent), 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 { impl std::fmt::Debug for VirtualEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

@ -55,8 +55,6 @@ pub struct VirtualDom {
active_fibers: Vec<Fiber<'static>>, active_fibers: Vec<Fiber<'static>>,
pending_events: BTreeMap<EventKey, EventTrigger>,
// for managing the props that were used to create the dom // for managing the props that were used to create the dom
#[doc(hidden)] #[doc(hidden)]
_root_prop_type: std::any::TypeId, _root_prop_type: std::any::TypeId,
@ -147,7 +145,6 @@ impl VirtualDom {
_root_props: root_props, _root_props: root_props,
shared: components, shared: components,
active_fibers: Vec::new(), active_fibers: Vec::new(),
pending_events: BTreeMap::new(),
_root_prop_type: TypeId::of::<P>(), _root_prop_type: TypeId::of::<P>(),
} }
} }
@ -222,42 +219,42 @@ impl VirtualDom {
Ok(edits) Ok(edits)
} }
async fn select_next_event(&mut self) -> Option<EventTrigger> { // async fn select_next_event(&mut self) -> Option<EventTrigger> {
let mut receiver = self.shared.task_receiver.borrow_mut(); // 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 // // drain the in-flight events so that we can sort them out with the current events
while let Ok(Some(trigger)) = receiver.try_next() { // while let Ok(Some(trigger)) = receiver.try_next() {
log::info!("retrieving event from receiver"); // log::info!("retrieving event from receiver");
let key = self.shared.make_trigger_key(&trigger); // let key = self.shared.make_trigger_key(&trigger);
self.pending_events.insert(key, trigger); // self.pending_events.insert(key, trigger);
} // }
if self.pending_events.is_empty() { // if self.pending_events.is_empty() {
// Continuously poll the future pool and the event receiver for work // // Continuously poll the future pool and the event receiver for work
let mut tasks = self.shared.async_tasks.borrow_mut(); // let mut tasks = self.shared.async_tasks.borrow_mut();
let tasks_tasks = tasks.next(); // let tasks_tasks = tasks.next();
let mut receiver = self.shared.task_receiver.borrow_mut(); // let mut receiver = self.shared.task_receiver.borrow_mut();
let reciv_task = receiver.next(); // let reciv_task = receiver.next();
futures_util::pin_mut!(tasks_tasks); // futures_util::pin_mut!(tasks_tasks);
futures_util::pin_mut!(reciv_task); // futures_util::pin_mut!(reciv_task);
let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await { // let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await {
futures_util::future::Either::Left((trigger, _)) => trigger, // futures_util::future::Either::Left((trigger, _)) => trigger,
futures_util::future::Either::Right((trigger, _)) => trigger, // futures_util::future::Either::Right((trigger, _)) => trigger,
} // }
.unwrap(); // .unwrap();
let key = self.shared.make_trigger_key(&trigger); // let key = self.shared.make_trigger_key(&trigger);
self.pending_events.insert(key, trigger); // self.pending_events.insert(key, trigger);
} // }
// pop the most important event off // // pop the most important event off
let key = self.pending_events.keys().next().unwrap().clone(); // let key = self.pending_events.keys().next().unwrap().clone();
let trigger = self.pending_events.remove(&key).unwrap(); // let trigger = self.pending_events.remove(&key).unwrap();
Some(trigger) // Some(trigger)
} // }
/// Runs the virtualdom immediately, not waiting for any suspended nodes to complete. /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
/// ///
@ -312,20 +309,8 @@ impl VirtualDom {
) -> Result<Mutations<'s>> { ) -> Result<Mutations<'s>> {
let cur_component = self.base_scope; 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::<ScopeId>::new();
let mut receiver = self.shared.task_receiver.borrow_mut();
//
loop {
if deadline_exceeded() {
break;
}
/* /*
Strategy: Strategy:
@ -336,60 +321,16 @@ impl VirtualDom {
5. While processing a fiber, periodically check if we're out of time 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 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) 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| {
});
```
*/ */
// 1. Consume any pending events and create new fibers
let mut receiver = self.shared.task_receiver.borrow_mut(); let mut receiver = self.shared.task_receiver.borrow_mut();
while let Ok(Some(trigger)) = receiver.try_next() {
// match receiver.try_next() {} // todo: cache the fibers
let mut fiber = Fiber::new();
let trigger = receiver.next().await.unwrap();
match &trigger.event { 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::ClipboardEvent(_)
| VirtualEvent::CompositionEvent(_) | VirtualEvent::CompositionEvent(_)
| VirtualEvent::KeyboardEvent(_) | VirtualEvent::KeyboardEvent(_)
@ -405,22 +346,20 @@ impl VirtualDom {
| VirtualEvent::ToggleEvent(_) | VirtualEvent::ToggleEvent(_)
| VirtualEvent::MouseEvent(_) | VirtualEvent::MouseEvent(_)
| VirtualEvent::PointerEvent(_) => { | VirtualEvent::PointerEvent(_) => {
let scope_id = &trigger.originator; if let Some(scope) = self.shared.get_scope_mut(trigger.originator) {
let scope = unsafe { self.shared.get_scope_mut(*scope_id) };
match scope {
Some(scope) => {
scope.call_listener(trigger)?; scope.call_listener(trigger)?;
} }
None => {
log::warn!("No scope found for event: {:#?}", scope_id);
}
}
} }
VirtualEvent::AsyncEvent { .. } => { VirtualEvent::AsyncEvent { .. } => {
// we want to progress these events while let Ok(Some(event)) = receiver.try_next() {
// However, there's nothing we can do for these events, they must generate their own events. 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 // Suspense Events! A component's suspended node is updated
VirtualEvent::SuspenseEvent { hook_idx, domnode } => { VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
@ -509,35 +448,13 @@ impl VirtualDom {
log::debug!("should be removing scope {:#?}", scope); 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!");
}
}
} }
} }
// // while !deadline_exceeded() {
// if realdom.must_commit() { let mut receiver = self.shared.task_receiver.borrow_mut();
// // commit these edits and then wait for the next idle period
// realdom.commit_edits(&mut diff_machine.edits).await; // no messages to receive, just work on the fiber
// }
} }
Ok(diff_machine.edits) Ok(diff_machine.edits)
@ -546,6 +463,10 @@ impl VirtualDom {
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> { pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
self.shared.task_sender.clone() self.shared.task_sender.clone()
} }
fn get_scope_mut(&mut self, id: ScopeId) -> Option<&mut Scope> {
unsafe { self.shared.get_scope_mut(id) }
}
} }
// TODO! // TODO!
@ -554,8 +475,6 @@ unsafe impl Sync for VirtualDom {}
unsafe impl Send for VirtualDom {} unsafe impl Send for VirtualDom {}
struct Fiber<'a> { struct Fiber<'a> {
trigger: EventTrigger,
// scopes that haven't been updated yet // scopes that haven't been updated yet
pending_scopes: Vec<ScopeId>, pending_scopes: Vec<ScopeId>,
@ -570,9 +489,8 @@ struct Fiber<'a> {
} }
impl Fiber<'_> { impl Fiber<'_> {
fn new(trigger: EventTrigger) -> Self { fn new() -> Self {
Self { Self {
trigger,
pending_scopes: Vec::new(), pending_scopes: Vec::new(),
pending_nodes: Vec::new(), pending_nodes: Vec::new(),
edits: Vec::new(), edits: Vec::new(),

View file

@ -2,6 +2,30 @@
//! -------------- //! --------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. //! 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; use std::rc::Rc;
pub use crate::cfg::WebConfig; pub use crate::cfg::WebConfig;