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
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)
}

View file

@ -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<DomEdit<'bump>>,
shared: &'bump SharedResources,
) -> Self {
pub fn new_headless(shared: &'bump SharedResources) -> Self {
Self {
edits: Mutations { edits: Vec::new() },
scope_stack: smallvec![ScopeId(0)],

View file

@ -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 {

View file

@ -55,8 +55,6 @@ pub struct VirtualDom {
active_fibers: Vec<Fiber<'static>>,
pending_events: BTreeMap<EventKey, EventTrigger>,
// 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::<P>(),
}
}
@ -222,42 +219,42 @@ impl VirtualDom {
Ok(edits)
}
async fn select_next_event(&mut self) -> Option<EventTrigger> {
let mut receiver = self.shared.task_receiver.borrow_mut();
// async fn select_next_event(&mut self) -> Option<EventTrigger> {
// 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,20 +309,8 @@ impl VirtualDom {
) -> Result<Mutations<'s>> {
let cur_component = self.base_scope;
let mut mutations = Mutations { edits: Vec::new() };
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;
}
let mut diff_machine =
DiffMachine::new(Mutations { edits: Vec::new() }, cur_component, &self.shared);
/*
Strategy:
@ -336,60 +321,16 @@ impl VirtualDom {
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| {
});
```
*/
// 1. Consume any pending events and create new fibers
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,22 +346,20 @@ 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) => {
if let Some(scope) = self.shared.get_scope_mut(trigger.originator) {
scope.call_listener(trigger)?;
}
None => {
log::warn!("No scope found for event: {:#?}", scope_id);
}
}
}
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 } => {
@ -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<EventTrigger> {
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<ScopeId>,
@ -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(),

View file

@ -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;