1
0
Jonathan Kelley 1 жил өмнө
parent
commit
374c7d0cd8

+ 18 - 19
packages/core/src/any_props.rs

@@ -11,27 +11,26 @@ pub(crate) trait AnyProps {
     fn duplicate(&self) -> BoxedAnyProps;
 }
 
-pub(crate) struct VProps<P: 'static> {
-    pub render_fn: Component<P>,
-    pub memo: fn(&P, &P) -> bool,
-    pub props: P,
-    pub name: &'static str,
+/// Create a new boxed props object.
+pub fn new_any_props<P: 'static + Clone>(
+    render_fn: Component<P>,
+    memo: fn(&P, &P) -> bool,
+    props: P,
+    name: &'static str,
+) -> Box<dyn AnyProps> {
+    Box::new(VProps {
+        render_fn,
+        memo,
+        props,
+        name,
+    })
 }
 
-impl<P: 'static> VProps<P> {
-    pub(crate) fn new(
-        render_fn: Component<P>,
-        memo: fn(&P, &P) -> bool,
-        props: P,
-        name: &'static str,
-    ) -> Self {
-        Self {
-            render_fn,
-            memo,
-            props,
-            name,
-        }
-    }
+struct VProps<P> {
+    render_fn: Component<P>,
+    memo: fn(&P, &P) -> bool,
+    props: P,
+    name: &'static str,
 }
 
 impl<P: Clone + 'static> AnyProps for VProps<P> {

+ 9 - 0
packages/core/src/events.rs

@@ -27,6 +27,15 @@ pub struct Event<T: 'static + ?Sized> {
     pub(crate) propagates: Rc<Cell<bool>>,
 }
 
+impl<T: ?Sized + 'static> Event<T> {
+    pub(crate) fn new(data: Rc<T>, bubbles: bool) -> Self {
+        Self {
+            data,
+            propagates: Rc::new(Cell::new(bubbles)),
+        }
+    }
+}
+
 impl<T> Event<T> {
     /// Map the event data to a new type
     ///

+ 2 - 2
packages/core/src/lib.rs

@@ -16,10 +16,10 @@ mod nodes;
 mod platform;
 mod properties;
 mod runtime;
-mod scheduler;
 mod scope_arena;
 mod scope_context;
 mod scopes;
+mod tasks;
 mod virtual_dom;
 
 pub(crate) mod innerlude {
@@ -34,8 +34,8 @@ pub(crate) mod innerlude {
     pub use crate::platform::*;
     pub use crate::properties::*;
     pub use crate::runtime::{Runtime, RuntimeGuard};
-    pub use crate::scheduler::*;
     pub use crate::scopes::*;
+    pub use crate::tasks::*;
     pub use crate::virtual_dom::*;
 
     /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].

+ 3 - 17
packages/core/src/nodes.rs

@@ -1,5 +1,5 @@
 use crate::{
-    any_props::{BoxedAnyProps, VProps},
+    any_props::{new_any_props, BoxedAnyProps},
     innerlude::ScopeState,
 };
 use crate::{arena::ElementId, Element, Event};
@@ -520,36 +520,22 @@ pub struct VComponent {
 
 impl VComponent {
     /// Create a new [`VComponent`] variant
-    ///
-    ///
-    /// The given component can be any of four signatures. Remember that an [`Element`] is really a [`Result<VNode>`].
-    ///
-    /// ```rust, ignore
-    /// // Without explicit props
-    /// fn() -> Element;
-    /// async fn(Scope<'_>) -> Element;
-    ///
-    /// // With explicit props
-    /// fn(Props) -> Element;
-    /// async fn(Scope<Props<'_>>) -> Element;
-    /// ```
     pub fn new<P, M>(
         component: impl ComponentFunction<P, M>,
         props: P,
         fn_name: &'static str,
     ) -> Self
     where
-        // The properties must be valid until the next bump frame
         P: Properties + 'static,
     {
         let component = Rc::new(component);
         let render_fn = component.id();
         let component = component.as_component();
-        let vcomp = VProps::new(component, <P as Properties>::memoize, props, fn_name);
+        let props = new_any_props(component, <P as Properties>::memoize, props, fn_name);
 
         VComponent {
             name: fn_name,
-            props: Box::new(vcomp),
+            props,
             render_fn,
         }
     }

+ 2 - 2
packages/core/src/runtime.rs

@@ -69,9 +69,9 @@ pub struct Runtime {
     pub(crate) rendering: Cell<bool>,
 
     /// Tasks created with cx.spawn
-    pub tasks: RefCell<Slab<LocalTask>>,
+    pub(crate) tasks: RefCell<Slab<LocalTask>>,
 
-    pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+    pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
 }
 
 impl Runtime {

+ 0 - 18
packages/core/src/scheduler/mod.rs

@@ -1,18 +0,0 @@
-use crate::ScopeId;
-
-mod task;
-mod wait;
-
-pub use task::*;
-
-/// The type of message that can be sent to the scheduler.
-///
-/// These messages control how the scheduler will process updates to the UI.
-#[derive(Debug)]
-pub(crate) enum SchedulerMsg {
-    /// Immediate updates from Components that mark them as dirty
-    Immediate(ScopeId),
-
-    /// A task has woken and needs to be progressed
-    TaskNotified(Task),
-}

+ 0 - 32
packages/core/src/scheduler/suspense.rs

@@ -1,32 +0,0 @@
-use futures_util::task::ArcWake;
-
-use super::SchedulerMsg;
-use crate::ElementId;
-use crate::{innerlude::Mutations, Element, ScopeId};
-use std::future::Future;
-use std::sync::Arc;
-use std::task::Waker;
-use std::{
-    cell::{Cell, RefCell},
-    collections::HashSet,
-};
-
-/// A boundary in the VirtualDom that captures all suspended components below it
-pub struct SuspenseContext {
-    pub(crate) id: ScopeId,
-    pub(crate) waiting_on: RefCell<HashSet<ScopeId>>,
-}
-
-impl SuspenseContext {
-    /// Create a new boundary for suspense
-    pub fn new(id: ScopeId) -> Self {
-        Self {
-            id,
-            waiting_on: Default::default(),
-        }
-    }
-
-    pub fn mark_suspend(&self, id: ScopeId) {
-        self.waiting_on.borrow_mut().insert(id);
-    }
-}

+ 0 - 41
packages/core/src/scheduler/wait.rs

@@ -1,41 +0,0 @@
-use crate::{runtime::RuntimeGuard, Task, VirtualDom};
-use std::task::Context;
-
-impl VirtualDom {
-    /// Handle notifications by tasks inside the scheduler
-    ///
-    /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
-    /// queue
-    pub(crate) fn handle_task_wakeup(&mut self, id: Task) {
-        let _runtime = RuntimeGuard::new(self.runtime.clone());
-        let mut tasks = self.runtime.tasks.borrow_mut();
-
-        let task = match tasks.get(id.0) {
-            Some(task) => task,
-            // The task was removed from the scheduler, so we can just ignore it
-            None => return,
-        };
-
-        let mut cx = Context::from_waker(&task.waker);
-
-        // update the scope stack
-        self.runtime.scope_stack.borrow_mut().push(task.scope);
-        self.runtime.rendering.set(false);
-        self.runtime.current_task.set(Some(id));
-
-        // If the task completes...
-        if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
-            // Remove it from the scope so we dont try to double drop it when the scope dropes
-            let scope = &self.get_scope(task.scope).unwrap();
-            scope.context().spawned_tasks.borrow_mut().remove(&id);
-
-            // Remove it from the scheduler
-            tasks.try_remove(id.0);
-        }
-
-        // Remove the scope from the stack
-        self.runtime.scope_stack.borrow_mut().pop();
-        self.runtime.rendering.set(true);
-        self.runtime.current_task.set(None);
-    }
-}

+ 65 - 14
packages/core/src/scheduler/task.rs → packages/core/src/tasks.rs

@@ -1,8 +1,6 @@
-use futures_util::task::ArcWake;
-
-use super::SchedulerMsg;
 use crate::innerlude::{remove_future, spawn, Runtime};
 use crate::ScopeId;
+use futures_util::task::ArcWake;
 use std::cell::RefCell;
 use std::future::Future;
 use std::pin::Pin;
@@ -11,8 +9,7 @@ use std::task::Waker;
 
 /// A task's unique identifier.
 ///
-/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused
-/// once a Task has been completed.
+/// `Task` is a unique identifier for a task that has been spawned onto the runtime. It can be used to cancel the task
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct Task(pub(crate) usize);
@@ -39,14 +36,6 @@ impl Task {
     }
 }
 
-/// the task itself is the waker
-pub(crate) struct LocalTask {
-    pub scope: ScopeId,
-    pub parent: Option<Task>,
-    pub task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
-    pub waker: Waker,
-}
-
 impl Runtime {
     /// Start a new future on the same thread as the rest of the VirtualDom.
     ///
@@ -86,6 +75,43 @@ impl Runtime {
         task_id
     }
 
+    pub(crate) fn handle_task_wakeup(&self, id: Task) {
+        let mut tasks = self.tasks.borrow_mut();
+
+        let task = match tasks.get(id.0) {
+            Some(task) => task,
+            // The task was removed from the scheduler, so we can just ignore it
+            None => return,
+        };
+
+        use std::task::Context;
+
+        let mut cx = Context::from_waker(&task.waker);
+
+        // update the scope stack
+        self.scope_stack.borrow_mut().push(task.scope);
+        self.rendering.set(false);
+        self.current_task.set(Some(id));
+
+        // If the task completes...
+        if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
+            // Remove it from the scope so we dont try to double drop it when the scope dropes
+            self.get_context(task.scope)
+                .unwrap()
+                .spawned_tasks
+                .borrow_mut()
+                .remove(&id);
+
+            // Remove it from the scheduler
+            tasks.try_remove(id.0);
+        }
+
+        // Remove the scope from the stack
+        self.scope_stack.borrow_mut().pop();
+        self.rendering.set(true);
+        self.current_task.set(None);
+    }
+
     /// Drop the future with the given TaskId
     ///
     /// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
@@ -97,9 +123,34 @@ impl Runtime {
     pub fn current_task(&self) -> Option<Task> {
         self.current_task.get()
     }
+
+    /// Get the parent task of the given task, if it exists
+    pub fn parent_task(&self, task: Task) -> Option<Task> {
+        self.tasks.borrow().get(task.0)?.parent
+    }
+}
+
+/// the task itself is the waker
+pub(crate) struct LocalTask {
+    scope: ScopeId,
+    parent: Option<Task>,
+    task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
+    waker: Waker,
+}
+
+/// The type of message that can be sent to the scheduler.
+///
+/// These messages control how the scheduler will process updates to the UI.
+#[derive(Debug)]
+pub(crate) enum SchedulerMsg {
+    /// Immediate updates from Components that mark them as dirty
+    Immediate(ScopeId),
+
+    /// A task has woken and needs to be progressed
+    TaskNotified(Task),
 }
 
-pub struct LocalTaskHandle {
+struct LocalTaskHandle {
     id: Task,
     tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
 }

+ 97 - 96
packages/core/src/virtual_dom.rs

@@ -3,7 +3,7 @@
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 
 use crate::{
-    any_props::VProps,
+    any_props::new_any_props,
     arena::ElementId,
     innerlude::{
         DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount,
@@ -14,12 +14,12 @@ use crate::{
     properties::ComponentFunction,
     runtime::{Runtime, RuntimeGuard},
     scopes::ScopeId,
-    AttributeValue, BoxedContext, Element, Event, Mutations,
+    AttributeValue, BoxedContext, Element, Event, Mutations, Task,
 };
 use futures_util::{pin_mut, StreamExt};
 use rustc_hash::{FxHashMap, FxHashSet};
 use slab::Slab;
-use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
+use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
@@ -287,12 +287,7 @@ impl VirtualDom {
         };
 
         let root = dom.new_scope(
-            Box::new(VProps::new(
-                Rc::new(root).as_component(),
-                |_, _| true,
-                root_props,
-                "root",
-            )),
+            new_any_props(Rc::new(root).as_component(), |_, _| true, root_props, "app"),
             "app",
         );
 
@@ -363,7 +358,6 @@ impl VirtualDom {
     /// It is up to the listeners themselves to mark nodes as dirty.
     ///
     /// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
-
     pub fn handle_event(
         &mut self,
         name: &str,
@@ -394,87 +388,86 @@ impl VirtualDom {
         | | |       <-- no, broke early
         |           <-- no, broke early
         */
-        let parent_path = match self.elements.get(element.0) {
-            Some(Some(el)) => *el,
-            _ => return,
+        let Some(Some(parent_path)) = self.elements.get(element.0).copied() else {
+            return;
         };
-        let mut parent_node = Some(parent_path);
 
         // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
-        let uievent = Event {
-            propagates: Rc::new(Cell::new(bubbles)),
-            data,
-        };
+        let uievent = Event::new(data, bubbles);
+
+        // Use the simple non-bubbling algorithm if the event doesn't bubble
+        if !bubbles {
+            return self.handle_non_bubbling_event(parent_path, name, uievent);
+        }
+
+        let mut parent_node = Some(parent_path);
 
         // If the event bubbles, we traverse through the tree until we find the target element.
-        if bubbles {
-            // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
-            while let Some(path) = parent_node {
-                let mut listeners = vec![];
-
-                let el_ref = &self.mounts[path.mount.0].node;
-                let node_template = el_ref.template.get();
-                let target_path = path.path;
-
-                for (idx, attrs) in el_ref.dynamic_attrs.iter().enumerate() {
-                    let this_path = node_template.attr_paths[idx];
-
-                    for attr in attrs.iter() {
-                        // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                        if attr.name.trim_start_matches("on") == name
-                            && target_path.is_decendant(&this_path)
-                        {
-                            listeners.push(&attr.value);
-
-                            // Break if this is the exact target element.
-                            // This means we won't call two listeners with the same name on the same element. This should be
-                            // documented, or be rejected from the rsx! macro outright
-                            if target_path == this_path {
-                                break;
-                            }
+        // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
+        while let Some(path) = parent_node {
+            let mut listeners = vec![];
+
+            let el_ref = &self.mounts[path.mount.0].node;
+            let node_template = el_ref.template.get();
+            let target_path = path.path;
+
+            for (idx, attrs) in el_ref.dynamic_attrs.iter().enumerate() {
+                let this_path = node_template.attr_paths[idx];
+
+                for attr in attrs.iter() {
+                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                    if attr.name.trim_start_matches("on") == name
+                        && target_path.is_decendant(&this_path)
+                    {
+                        listeners.push(&attr.value);
+
+                        // Break if this is the exact target element.
+                        // This means we won't call two listeners with the same name on the same element. This should be
+                        // documented, or be rejected from the rsx! macro outright
+                        if target_path == this_path {
+                            break;
                         }
                     }
                 }
+            }
 
-                // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
-                // We check the bubble state between each call to see if the event has been stopped from bubbling
-                for listener in listeners.into_iter().rev() {
-                    if let AttributeValue::Listener(listener) = listener {
-                        self.runtime.rendering.set(false);
-                        listener.call(uievent.clone());
-                        self.runtime.rendering.set(true);
+            // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
+            // We check the bubble state between each call to see if the event has been stopped from bubbling
+            for listener in listeners.into_iter().rev() {
+                if let AttributeValue::Listener(listener) = listener {
+                    self.runtime.rendering.set(false);
+                    listener.call(uievent.clone());
+                    self.runtime.rendering.set(true);
 
-                        if !uievent.propagates.get() {
-                            return;
-                        }
+                    if !uievent.propagates.get() {
+                        return;
                     }
                 }
-
-                let mount = el_ref.mount.get().as_usize();
-                parent_node = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
             }
-        } else {
-            // Otherwise, we just call the listener on the target element
-            if let Some(path) = parent_node {
-                let el_ref = &self.mounts[path.mount.0].node;
-                let node_template = el_ref.template.get();
-                let target_path = path.path;
-
-                for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
-                    let this_path = node_template.attr_paths[idx];
-
-                    for attr in attr.iter() {
-                        // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                        // Only call the listener if this is the exact target element.
-                        if attr.name.trim_start_matches("on") == name && target_path == this_path {
-                            if let AttributeValue::Listener(listener) = &attr.value {
-                                self.runtime.rendering.set(false);
-                                listener.call(uievent.clone());
-                                self.runtime.rendering.set(true);
-
-                                break;
-                            }
-                        }
+
+            let mount = el_ref.mount.get().as_usize();
+            parent_node = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
+        }
+    }
+
+    /// Call an event listener in the simplest way possible without bubbling upwards
+    fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
+        let el_ref = &self.mounts[node.mount.0].node;
+        let node_template = el_ref.template.get();
+        let target_path = node.path;
+
+        for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
+            let this_path = node_template.attr_paths[idx];
+
+            for attr in attr.iter() {
+                // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                // Only call the listener if this is the exact target element.
+                if attr.name.trim_start_matches("on") == name && target_path == this_path {
+                    if let AttributeValue::Listener(listener) = &attr.value {
+                        self.runtime.rendering.set(false);
+                        listener.call(uievent.clone());
+                        self.runtime.rendering.set(true);
+                        break;
                     }
                 }
             }
@@ -501,27 +494,26 @@ impl VirtualDom {
         let mut some_msg = None;
 
         loop {
-            match some_msg.take() {
-                // If a bunch of messages are ready in a sequence, try to pop them off synchronously
-                Some(msg) => match msg {
+            // If a bunch of messages are ready in a sequence, try to pop them off synchronously
+            if let Some(msg) = some_msg.take() {
+                match msg {
                     SchedulerMsg::Immediate(id) => self.mark_dirty(id),
                     SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
-                },
-
-                // If they're not ready, then we should wait for them to be ready
-                None => {
-                    match self.rx.try_next() {
-                        Ok(Some(val)) => some_msg = Some(val),
-                        Ok(None) => return,
-                        Err(_) => {
-                            // If we have any dirty scopes, or finished fiber trees then we should exit
-                            if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
-                                return;
-                            }
-
-                            some_msg = self.rx.next().await
-                        }
+                }
+                continue;
+            }
+
+            // If they're not ready, then we should wait for them to be ready
+            match self.rx.try_next() {
+                Ok(Some(val)) => some_msg = Some(val),
+                Ok(None) => return,
+                Err(_) => {
+                    // If we have any dirty scopes, or finished fiber trees then we should exit
+                    if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
+                        return;
                     }
+
+                    some_msg = self.rx.next().await
                 }
             }
         }
@@ -537,6 +529,15 @@ impl VirtualDom {
         }
     }
 
+    /// Handle notifications by tasks inside the scheduler
+    ///
+    /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
+    /// queue
+    fn handle_task_wakeup(&mut self, id: Task) {
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+        self.runtime.handle_task_wakeup(id);
+    }
+
     /// Replace a template at runtime. This will re-render all components that use this template.
     /// This is the primitive that enables hot-reloading.
     ///