Jonathan Kelley 2 лет назад
Родитель
Сommit
aec1b326ba

+ 51 - 36
packages/core/src/create.rs

@@ -1,15 +1,15 @@
-use std::task::Context;
+use std::pin::Pin;
 
-use futures_util::task::noop_waker_ref;
-use futures_util::{pin_mut, Future};
-
-use crate::factory::RenderReturn;
+use crate::factory::{FiberLeaf, RenderReturn};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
+use crate::suspense::LeafLocation;
 use crate::virtualdom::VirtualDom;
 use crate::{AttributeValue, Element, ElementId, TemplateAttribute};
+use bumpalo::boxed::Box as BumpBox;
+use futures_util::Future;
 
 impl VirtualDom {
     /// Create this template and write its mutations
@@ -177,7 +177,9 @@ impl VirtualDom {
                 1
             }
 
-            DynamicNode::Component { props, .. } => {
+            DynamicNode::Component {
+                props, placeholder, ..
+            } => {
                 let id = self.new_scope(unsafe { std::mem::transmute(props.get()) });
 
                 let render_ret = self.run_scope(id);
@@ -192,39 +194,52 @@ impl VirtualDom {
                         self.scope_stack.pop();
                         created
                     }
-                    RenderReturn::Sync(None) => todo!("nodes that return nothing"),
+
+                    // whenever the future is polled later, we'll revisit it
+                    // For now, just set the placeholder
+                    RenderReturn::Sync(None) => {
+                        let new_id = self.next_element(template);
+                        placeholder.set(Some(new_id));
+                        mutations.push(AssignId {
+                            id: new_id,
+                            path: &template.template.node_paths[idx][1..],
+                        });
+                        0
+                    }
                     RenderReturn::Async(fut) => {
-                        use futures_util::FutureExt;
-
-                        // Poll the suspense node once to see if we can get any nodes from it
-                        let mut cx = Context::from_waker(&noop_waker_ref());
-                        let res = fut.poll_unpin(&mut cx);
-
-                        match res {
-                            std::task::Poll::Ready(Some(val)) => {
-                                let scope = self.get_scope(id).unwrap();
-                                let ready = &*scope.bump().alloc(val);
-                                let ready = unsafe { std::mem::transmute(ready) };
-
-                                self.scope_stack.push(id);
-                                let created = self.create(mutations, ready);
-                                self.scope_stack.pop();
-                                created
-                            }
-                            std::task::Poll::Ready(None) => {
-                                todo!("Pending suspense")
-                            }
-                            std::task::Poll::Pending => {
-                                let new_id = self.next_element(template);
-                                // id.set(new_id);
-                                mutations.push(AssignId {
-                                    id: new_id,
-                                    path: &template.template.node_paths[idx][1..],
-                                });
-
-                                0
+                        let new_id = self.next_element(template);
+
+                        // move up the tree looking for the first suspense boundary
+                        // our current component can not be a suspense boundary, so we skip it
+                        for scope_id in self.scope_stack.iter().rev().skip(1) {
+                            let scope = &mut self.scopes[scope_id.0];
+                            if let Some(fiber) = &mut scope.suspense_boundary {
+                                // save the fiber leaf onto the fiber itself
+                                let detached: &mut FiberLeaf<'static> =
+                                    unsafe { std::mem::transmute(fut) };
+
+                                // And save the fiber leaf using the placeholder node
+                                // this way, when we resume the fiber, we just need to "pick up placeholder"
+                                fiber.futures.insert(
+                                    LeafLocation {
+                                        element: new_id,
+                                        scope: *scope_id,
+                                    },
+                                    detached,
+                                );
+
+                                self.suspended_scopes.insert(*scope_id);
+                                break;
                             }
                         }
+
+                        placeholder.set(Some(new_id));
+                        mutations.push(AssignId {
+                            id: new_id,
+                            path: &template.template.node_paths[idx][1..],
+                        });
+
+                        0
                     }
                 }
             }

+ 3 - 0
packages/core/src/factory.rs

@@ -97,6 +97,7 @@ impl ScopeState {
             name: fn_name,
             is_static: P::IS_STATIC,
             props: Cell::new(detached_dyn),
+            placeholder: Cell::new(None),
         }
     }
 }
@@ -135,6 +136,8 @@ pub enum RenderReturn<'a> {
     Async(Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>),
 }
 
+pub type FiberLeaf<'a> = Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>;
+
 pub trait IntoVnode<'a, A = ()> {
     fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
 }

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

@@ -14,6 +14,8 @@ mod nodes;
 mod properties;
 mod scope_arena;
 mod scopes;
+// mod subtree;
+mod suspense;
 mod virtualdom;
 
 pub(crate) mod innerlude {

+ 1 - 0
packages/core/src/nodes.rs

@@ -79,6 +79,7 @@ pub enum DynamicNode<'a> {
         name: &'static str,
         is_static: bool,
         props: Cell<*mut dyn AnyProps<'a>>,
+        placeholder: Cell<Option<ElementId>>,
     },
 
     // Comes in with string interpolation or from format_args, include_str, etc

+ 20 - 1
packages/core/src/scope_arena.rs

@@ -1,3 +1,7 @@
+use std::task::Context;
+
+use futures_util::task::noop_waker_ref;
+
 use crate::{
     any_props::AnyProps,
     arena::ElementId,
@@ -23,6 +27,7 @@ impl VirtualDom {
             height,
             props,
             tasks: self.pending_futures.clone(),
+            suspense_boundary: None,
             node_arena_1: BumpFrame::new(50),
             node_arena_2: BumpFrame::new(50),
             render_cnt: Default::default(),
@@ -53,7 +58,7 @@ impl VirtualDom {
         let scope = &mut self.scopes[id.0];
         scope.hook_idx.set(0);
 
-        let res = {
+        let mut new_nodes = {
             let props = unsafe { &mut *scope.props };
             let props: &mut dyn AnyProps = unsafe { std::mem::transmute(props) };
             let res: RenderReturn = props.render(scope);
@@ -61,6 +66,20 @@ impl VirtualDom {
             res
         };
 
+        // immediately resolve futures that can be resolved immediatelys
+        let res = match &mut new_nodes {
+            RenderReturn::Sync(_) => new_nodes,
+            RenderReturn::Async(fut) => {
+                use futures_util::FutureExt;
+                let mut cx = Context::from_waker(&noop_waker_ref());
+
+                match fut.poll_unpin(&mut cx) {
+                    std::task::Poll::Ready(nodes) => RenderReturn::Sync(nodes),
+                    std::task::Poll::Pending => new_nodes,
+                }
+            }
+        };
+
         let frame = match scope.render_cnt % 2 {
             0 => &mut scope.node_arena_1,
             1 => &mut scope.node_arena_2,

+ 3 - 1
packages/core/src/scopes.rs

@@ -11,7 +11,7 @@ use futures_util::Future;
 
 use crate::{
     any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, future_container::FutureQueue,
-    innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, TaskId,
+    innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, suspense::Fiber, TaskId,
 };
 
 pub struct Scope<'a, T = ()> {
@@ -66,6 +66,8 @@ pub struct ScopeState {
 
     pub tasks: FutureQueue,
 
+    pub suspense_boundary: Option<Fiber<'static>>,
+
     pub props: *mut dyn AnyProps<'static>,
 }
 

+ 58 - 0
packages/core/src/subtree.rs

@@ -0,0 +1,58 @@
+/*
+This is a WIP module
+
+Subtrees allow the virtualdom to split up the mutation stream into smaller chunks which can be directed to different parts of the dom.
+It's core to implementing multiwindow desktop support, portals, and alternative inline renderers like react-three-fiber.
+
+The primary idea is to give each renderer a linear element tree managed by Dioxus to maximize performance and minimize memory usage.
+This can't be done if two renderers need to share the same native tree.
+With subtrees, we have an entirely different slab of elements
+
+*/
+
+use std::borrow::Cow;
+
+use slab::Slab;
+
+use crate::{ElementPath, ScopeId};
+
+/// A collection of elements confined to a single scope under a chunk of the tree
+///
+/// All elements in this collection are guaranteed to be in the same scope and share the same numbering
+///
+/// This unit can be multithreaded
+/// Whenever multiple subtrees are present, we can perform **parallel diffing**
+pub struct Subtree {
+    id: usize,
+    namespace: Cow<'static, str>,
+    root: ScopeId,
+    elements: Slab<ElementPath>,
+}
+
+// fn app(cx: Scope) -> Element {
+//     // whenever a user connects, they get a new connection
+//     // this requires the virtualdom to be Send + Sync
+//     rsx! {
+//         ClientForEach(|req| rsx!{
+//             Route {}
+//             Route {}
+//             Route {}
+//             Route {}
+//             Route {}
+//             Route {}
+//         })
+
+//         // windows.map(|w| {
+//         //     WebviewWindow {}
+//         //     WebviewWindow {}
+//         //     WebviewWindow {}
+//         //     WebviewWindow {}
+//         // })
+
+//         // if show_settings {
+//         //     WebviewWindow {
+//         //         Settings {}
+//         //     }
+//         // }
+//     }
+// }

+ 149 - 0
packages/core/src/suspense.rs

@@ -0,0 +1,149 @@
+//! Container for polling suspended nodes
+//!
+//! Whenever a future is returns a value, we walk the tree upwards and check if any of the parents are suspended.
+
+use bumpalo::boxed::Box as BumpBox;
+use futures_util::Future;
+use std::{
+    collections::{HashMap, HashSet},
+    future::poll_fn,
+    pin::Pin,
+    task::Poll,
+};
+
+use crate::{
+    factory::FiberLeaf, innerlude::Mutation, Element, ElementId, ScopeId, VNode, VirtualDom,
+};
+
+impl VirtualDom {
+    // todo: lots of hammering lifetimes here...
+    async fn wait_for_suspense(&mut self) {
+        let res = poll_fn(|cx| {
+            let all_suspended_complete = true;
+
+            let suspended_scopes: Vec<_> = self.suspended_scopes.iter().copied().collect();
+
+            for scope in suspended_scopes {
+                let mut fiber = self.scopes[scope.0]
+                    .suspense_boundary
+                    .as_mut()
+                    .expect(" A fiber to be present if the scope is suspended");
+
+                let mut fiber: &mut Fiber = unsafe { std::mem::transmute(fiber) };
+
+                let mutations = &mut fiber.mutations;
+                let mutations: &mut Vec<Mutation> = unsafe { std::mem::transmute(mutations) };
+
+                let keys = fiber.futures.keys().copied().collect::<Vec<_>>();
+                for loc in keys {
+                    let fut = *fiber.futures.get_mut(&loc).unwrap();
+                    let fut = unsafe { &mut *fut };
+                    let fut: &mut FiberLeaf<'_> = unsafe { std::mem::transmute(fut) };
+
+                    use futures_util::FutureExt;
+
+                    match fut.poll_unpin(cx) {
+                        Poll::Ready(nodes) => {
+                            // remove the future from the fiber
+                            fiber.futures.remove(&loc).unwrap();
+
+                            // set the original location to the new nodes
+                            // todo!("set the original location to the new nodes");
+                            let template = nodes.unwrap();
+
+                            let scope = &self.scopes[scope.0];
+                            let template = scope.bump().alloc(template);
+                            let template: &VNode = unsafe { std::mem::transmute(template) };
+
+                            // now create the template
+                            self.create(mutations, template);
+                        }
+                        Poll::Pending => todo!("still working huh"),
+                    }
+                }
+
+                // let mut fiber = Pin::new(&mut fiber);
+
+                // let mut scope = scope;
+                // let mut vnode = self.scopes[scope.0].vnode.take().unwrap();
+
+                // let mut vnode = Pin::new(&mut vnode);
+
+                // let mut vnode = poll_fn(|cx| {
+                //     let mut vnode = Pin::new(&mut vnode);
+                //     let mut fiber = Pin::new(&mut fiber);
+
+                //     let res = vnode.as_mut().poll(cx);
+
+                //     if let Poll::Ready(res) = res {
+                //         Poll::Ready(res)
+                //     } else {
+                //         Poll::Pending
+                //     }
+                // })
+                // .await;
+
+                // self.scopes[scope.0].vnode = Some(vnode);
+                // self.scopes[scope.0].suspense_boundary = Some(fiber);
+            }
+
+            match all_suspended_complete {
+                true => Poll::Ready(()),
+                false => Poll::Pending,
+            }
+        });
+
+        todo!()
+    }
+}
+
+// impl SuspenseGenerator {
+//     async fn wait_for_work(&mut self) {
+//         use futures_util::future::{select, Either};
+
+//         // let scopes = &mut self.scopes;
+//         let suspense_status = poll_fn(|cx| {
+//             // let mut tasks = scopes.tasks.tasks.borrow_mut();
+//             // tasks.retain(|_, task| task.as_mut().poll(cx).is_pending());
+
+//             match true {
+//                 // match tasks.is_empty() {
+//                 true => Poll::Ready(()),
+//                 false => Poll::Pending,
+//             }
+//         });
+
+//         // Suspense {
+//         // maybe generate futures
+//         // only render when all the futures are ready
+//         // }
+
+//         /*
+//             div {
+//                 as1 {}
+//                 as2 {}
+//                 as3 {}
+//             }
+//         */
+//         // match select(task_poll, self.channel.1.next()).await {
+//         //     Either::Left((_, _)) => {}
+//         //     Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
+//         // }
+//     }
+// }
+
+#[derive(Default)]
+pub struct Fiber<'a> {
+    // The work-in progress of this suspended tree
+    pub mutations: Vec<Mutation<'a>>,
+
+    // All the pending futures (DFS)
+    pub futures:
+        HashMap<LeafLocation, *mut Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>>,
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
+pub struct LeafLocation {
+    pub scope: ScopeId,
+    pub element: ElementId,
+}

+ 21 - 2
packages/core/src/virtualdom.rs

@@ -7,6 +7,7 @@ use crate::future_container::FutureQueue;
 use crate::innerlude::SchedulerMsg;
 use crate::mutations::Mutation;
 use crate::nodes::{Template, TemplateId};
+use crate::suspense::Fiber;
 use crate::{
     arena::ElementId,
     scopes::{ScopeId, ScopeState},
@@ -26,6 +27,7 @@ pub struct VirtualDom {
     pub(crate) pending_futures: FutureQueue,
     pub(crate) sender: UnboundedSender<SchedulerMsg>,
     pub(crate) receiver: UnboundedReceiver<SchedulerMsg>,
+    pub(crate) suspended_scopes: BTreeSet<ScopeId>,
 }
 
 impl VirtualDom {
@@ -40,6 +42,7 @@ impl VirtualDom {
             element_stack: vec![ElementId(0)],
             dirty_scopes: BTreeSet::new(),
             pending_futures: FutureQueue::new(sender.clone()),
+            suspended_scopes: BTreeSet::new(),
             receiver,
             sender,
         };
@@ -49,6 +52,9 @@ impl VirtualDom {
 
         let root = res.new_scope(props);
 
+        // the root component is always a suspense boundary for any async children
+        res.scopes[root.0].suspense_boundary = Some(Fiber::default());
+
         assert_eq!(root, ScopeId(0));
 
         res
@@ -56,8 +62,6 @@ impl VirtualDom {
 
     /// Render the virtualdom, without processing any suspense.
     pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
-        // let root = self.scopes.get(0).unwrap();
-
         let root_node: &RenderReturn = self.run_scope(ScopeId(0));
         let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) };
         match root_node {
@@ -104,3 +108,18 @@ impl Drop for VirtualDom {
         // self.drop_scope(ScopeId(0));
     }
 }
+
+/*
+div {
+    Window {}
+    Window {}
+}
+
+edits -> Vec<Mutation>
+
+Subtree {
+    id: 0,
+    namespace: "react-three-fiber",
+    edits: []
+}
+*/