浏览代码

wip: got the structure figured out!

Jonathan Kelley 3 年之前
父节点
当前提交
97745c6a7f
共有 2 个文件被更改,包括 244 次插入89 次删除
  1. 139 3
      packages/core/src/scheduler.rs
  2. 105 86
      packages/core/src/virtual_dom.rs

+ 139 - 3
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<EventTrigger>,
+    pending_immediates: VecDeque<EventTrigger>,
+    pending_tasks: VecDeque<EventTrigger>,
+
+    garbage_scopes: HashSet<ScopeId>,
+
+    shared: SharedResources,
+
     dirty_scopes: [HashSet<DirtyScope>; 3],
 
     fibers: Vec<Fiber<'static>>,
 }
 
 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<EventTrigger> {
+        // 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<Output = ()>) -> 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)]

+ 105 - 86
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::<P>(),
         }
     }
@@ -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 { .. } => {}
-
-                // 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();
-
-                    // safety: we are sure that there are no other references to the inner content of suspense hooks
-                    let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
-
-                    let cx = Context { scope, props: &() };
-                    let scx = SuspendedContext { inner: cx };
-
-                    // generate the new node!
-                    let nodes: Option<VNode> = (&hook.callback)(scx);
-
-                    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 >:(."
-                        );
+        let mut is_ready_deadline = deadline_future.clone();
+        let mut is_ready = || -> bool { (&mut is_ready_deadline).now_or_never().is_some() };
+
+        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);
+            }
+
+            // 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
+            //
+            //
+            //
+            //
+
+            while let Some(trigger) = self.scheduler.next_event() {
+                match &trigger.event {
+                    VirtualEvent::AsyncEvent { .. } => {}
+
+                    // 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();
+
+                        // // safety: we are sure that there are no other references to the inner content of suspense hooks
+                        // let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
+
+                        // let cx = Context { scope, props: &() };
+                        // let scx = SuspendedContext { inner: cx };
+
+                        // // generate the new node!
+                        // let nodes: Option<VNode> = (&hook.callback)(scx);
+
+                        // 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)?;
-
-                            // 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)
+                    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)
+                                }
                             }
                         }
                     }
                 }
             }
-        }
 
-        // 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<EventTrigger> {