From 97745c6a7f2094deca32c7a1c30890853067848e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 10 Aug 2021 01:21:13 -0400 Subject: [PATCH] wip: got the structure figured out! --- packages/core/src/scheduler.rs | 142 ++++++++++++++++++++++++- packages/core/src/virtual_dom.rs | 173 +++++++++++++++++-------------- 2 files changed, 235 insertions(+), 80 deletions(-) diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index af327a56c..99b5fd61b 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -5,6 +5,8 @@ use std::cell::{Ref, RefCell, RefMut}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque}; use std::pin::Pin; +use futures_util::Future; + use crate::innerlude::*; /// The "Mutations" object holds the changes that need to be made to the DOM. @@ -45,16 +47,33 @@ impl<'a> NodeRefMutation<'a> { pub struct Scheduler { current_priority: EventPriority, + pending_events: VecDeque, + pending_immediates: VecDeque, + pending_tasks: VecDeque, + + garbage_scopes: HashSet, + + shared: SharedResources, + dirty_scopes: [HashSet; 3], fibers: Vec>, } impl Scheduler { - pub fn new() -> Self { + pub fn new(shared: SharedResources) -> Self { Self { + shared, + + // a storage for our receiver to dump into + pending_events: VecDeque::new(), + pending_immediates: VecDeque::new(), + pending_tasks: VecDeque::new(), + fibers: Vec::new(), + garbage_scopes: HashSet::new(), + current_priority: EventPriority::Low, // low, medium, high @@ -62,6 +81,64 @@ impl Scheduler { } } + // channels don't have these methods, so we just implement our own wrapper + pub fn next_event(&mut self) -> Option { + // pop the last event off the internal queue + self.pending_events.pop_back().or_else(|| { + self.shared + .ui_event_receiver + .borrow_mut() + .try_next() + .ok() + .and_then(|f| f) + }) + } + + // waits for a trigger, canceling early if the deadline is reached + // returns true if the deadline was reached + pub async fn wait_for_any_trigger(&mut self, deadline: &mut impl Future) -> bool { + todo!() + // match raw_trigger { + // Ok(Some(trigger)) => trigger, + // _ => { + // // nothing to do - let's clean up any garbage we might have + // self.scheduler.clean_up_garbage(); + + // // 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 the new event generates work more important than our current fiber, we should consider switching + // // only switch if it impacts different scopes. + // let mut ui_receiver = self.shared.ui_event_receiver.borrow_mut(); + // let ui_reciv_task = ui_receiver.next(); + + // // right now, this polling method will only catch batched set_states that don't get awaited. + // // However, in the future, we might be interested in batching set_states across await points + // let immediate_tasks = (); + + // futures_util::pin_mut!(tasks_tasks); + // futures_util::pin_mut!(ui_reciv_task); + + // // Poll the event receiver and the future pool for work + // // Abort early if our deadline has ran out + // let mut deadline = (&mut deadline_future).fuse(); + + // let trig = futures_util::select! { + // trigger = tasks_tasks => trigger, + // trigger = ui_reciv_task => trigger, + + // // abort if we're out of time + // _ = deadline => { return Ok(Mutations::new()); } + // // _ = deadline => { return Ok(diff_machine.mutations); } + // }; + + // trig.unwrap() + // } + // }; + // + } + pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) { // @@ -75,9 +152,68 @@ impl Scheduler { // .or_insert_with(|| new_priority); } - pub fn has_work() {} + pub fn has_work(&self) -> bool { + true + } - pub fn progress_work(&mut self, machine: &mut DiffMachine) {} + // returns true if the deadline is reached + // returns false if the deadline is not reached + pub fn progress_work( + &mut self, + machine: &mut DiffMachine, + is_deadline_reached: &mut impl FnMut() -> bool, + ) -> bool { + todo!() + } + + pub fn clean_up_garbage(&mut self) { + let mut scopes_to_kill = Vec::new(); + + let mut garbage_list = Vec::new(); + for scope in self.garbage_scopes.drain() { + let scope = self.shared.get_scope_mut(scope).unwrap(); + for node in scope.consume_garbage() { + garbage_list.push(node); + } + + while let Some(node) = garbage_list.pop() { + match &node.kind { + VNodeKind::Text(_) => { + self.shared.collect_garbage(node.direct_id()); + } + VNodeKind::Anchor(_) => { + self.shared.collect_garbage(node.direct_id()); + } + VNodeKind::Suspended(_) => { + self.shared.collect_garbage(node.direct_id()); + } + + VNodeKind::Element(el) => { + self.shared.collect_garbage(node.direct_id()); + for child in el.children { + garbage_list.push(child); + } + } + + VNodeKind::Fragment(frag) => { + for child in frag.children { + garbage_list.push(child); + } + } + + VNodeKind::Component(comp) => { + // TODO: run the hook destructors and then even delete the scope + + let scope_id = comp.ass_scope.get().unwrap(); + let scope = self.shared.get_scope(scope_id).unwrap(); + let root = scope.root(); + garbage_list.push(root); + scopes_to_kill.push(scope_id); + } + } + } + } + } } #[derive(PartialEq, Eq, Copy, Clone, Debug)] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1745d4740..8b72a6a7d 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -142,8 +142,8 @@ impl VirtualDom { Self { base_scope, _root_props: root_props, + scheduler: Scheduler::new(components.clone()), shared: components, - scheduler: Scheduler::new(), _root_prop_type: TypeId::of::

(), } } @@ -324,106 +324,125 @@ impl VirtualDom { and listeners hold references to hook data, it is wrong to run a scope that is already being diffed. */ - let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared); + // let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared); - // 1. Drain the existing immediates. - // - // These are generated by async tasks that we never got a chance to finish. - // All of these get scheduled with the lowest priority. - while let Ok(Some(dirty_scope)) = self.shared.immediate_receiver.borrow_mut().try_next() { - self.scheduler - .add_dirty_scope(dirty_scope, EventPriority::Low); - } + // Configure our deadline + use futures_util::FutureExt; + let mut deadline_future = deadline.shared(); - // 2. Drain the event queue, calling whatever listeners need to be called - // - while let Ok(Some(trigger)) = self.shared.ui_event_receiver.borrow_mut().try_next() { - match &trigger.event { - VirtualEvent::AsyncEvent { .. } => {} + let mut is_ready_deadline = deadline_future.clone(); + let mut is_ready = || -> bool { (&mut is_ready_deadline).now_or_never().is_some() }; - // This suspense system works, but it's not the most elegant solution. - // TODO: Replace this system - VirtualEvent::SuspenseEvent { hook_idx, domnode } => { - // Safety: this handler is the only thing that can mutate shared items at this moment in tim - let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); + loop { + // 1. Drain the existing immediates. + // + // These are generated by async tasks that we never got a chance to finish. + // All of these get scheduled with the lowest priority. + while let Ok(Some(dirty_scope)) = self.shared.immediate_receiver.borrow_mut().try_next() + { + self.scheduler + .add_dirty_scope(dirty_scope, EventPriority::Low); + } - // safety: we are sure that there are no other references to the inner content of suspense hooks - let hook = unsafe { scope.hooks.get_mut::(*hook_idx) }.unwrap(); + // 2. Drain the event queue, calling whatever listeners need to be called + // + // First, check if there's any set_states in the queue that we can mark as low priority + // Next, check the UI event receiver so we can mark those scopes as medium/high priority + // Next, work on any fibers. + // Once the fiber work is finished + // + // + // + // - let cx = Context { scope, props: &() }; - let scx = SuspendedContext { inner: cx }; + while let Some(trigger) = self.scheduler.next_event() { + match &trigger.event { + VirtualEvent::AsyncEvent { .. } => {} - // generate the new node! - let nodes: Option = (&hook.callback)(scx); + // This suspense system works, but it's not the most elegant solution. + // TODO: Replace this system + VirtualEvent::SuspenseEvent { hook_idx, domnode } => { + todo!(); + // // Safety: this handler is the only thing that can mutate shared items at this moment in tim + // let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); - if let Some(nodes) = nodes { - // allocate inside the finished frame - not the WIP frame - let nodes = scope.frames.finished_frame().bump.alloc(nodes); + // // safety: we are sure that there are no other references to the inner content of suspense hooks + // let hook = unsafe { scope.hooks.get_mut::(*hook_idx) }.unwrap(); - // push the old node's root onto the stack - let real_id = domnode.get().ok_or(Error::NotMounted)?; - diff_machine.edit_push_root(real_id); + // let cx = Context { scope, props: &() }; + // let scx = SuspendedContext { inner: cx }; - // push these new nodes onto the diff machines stack - let meta = diff_machine.create_vnode(&*nodes); + // // generate the new node! + // let nodes: Option = (&hook.callback)(scx); - // replace the placeholder with the new nodes we just pushed on the stack - diff_machine.edit_replace_with(1, meta.added_to_stack); - } else { - log::warn!( - "Suspense event came through, but there were no generated nodes >:(." - ); + // if let Some(nodes) = nodes { + // // allocate inside the finished frame - not the WIP frame + // let nodes = scope.frames.finished_frame().bump.alloc(nodes); + + // // push the old node's root onto the stack + // let real_id = domnode.get().ok_or(Error::NotMounted)?; + // diff_machine.edit_push_root(real_id); + + // // push these new nodes onto the diff machines stack + // let meta = diff_machine.create_vnode(&*nodes); + + // // replace the placeholder with the new nodes we just pushed on the stack + // diff_machine.edit_replace_with(1, meta.added_to_stack); + // } else { + // log::warn!( + // "Suspense event came through, but there were no generated nodes >:(." + // ); + // } } - } - 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(_) => { - if let Some(scope) = self.shared.get_scope_mut(trigger.originator) { - if let Some(element) = trigger.real_node_id { - scope.call_listener(trigger.event, element)?; + 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(_) => { + if let Some(scope) = self.shared.get_scope_mut(trigger.originator) { + if let Some(element) = trigger.real_node_id { + scope.call_listener(trigger.event, element)?; - // Drain the immediates into the dirty scopes, setting the appropiate priorities - while let Ok(Some(dirty_scope)) = - self.shared.immediate_receiver.borrow_mut().try_next() - { - self.scheduler - .add_dirty_scope(dirty_scope, trigger.priority) + // Drain the immediates into the dirty scopes, setting the appropiate priorities + while let Ok(Some(dirty_scope)) = + self.shared.immediate_receiver.borrow_mut().try_next() + { + self.scheduler + .add_dirty_scope(dirty_scope, trigger.priority) + } } } } } } - } - // 3. Work through the fibers, and wait for any future work to be ready + // 3. Work through the fibers, and wait for any future work to be ready + let mut machine = DiffMachine::new_headless(&self.shared); + self.scheduler.progress_work(&mut machine, &mut is_ready); - // Configure our deadline - use futures_util::FutureExt; - let mut deadline_future = deadline.boxed_local(); - let mut is_ready = || -> bool { (&mut deadline_future).now_or_never().is_some() }; - - loop { - if is_ready() { + // 4. Wait for any new triggers before looping again + if self + .scheduler + .wait_for_any_trigger(&mut deadline_future) + .await + { break; } - - self.scheduler } - Ok(diff_machine.mutations) + todo!() + // Ok(diff_machine.mutations) } pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender {