From 491bf4332d4b3a223ea87aede0f17d4ffd896160 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Nov 2022 17:00:34 -0800 Subject: [PATCH] wip: stop transmuting reference --- examples/dog_app.rs | 7 +- packages/core/src/bump_frame.rs | 2 +- packages/core/src/create.rs | 320 ++++++++++++++++++----------- packages/core/src/factory.rs | 4 +- packages/core/src/lib.rs | 3 + packages/core/src/scheduler.old.rs | 73 ------- packages/core/src/scopes.rs | 3 +- packages/ssr/src/template.rs | 95 +++++---- 8 files changed, 263 insertions(+), 244 deletions(-) delete mode 100644 packages/core/src/scheduler.old.rs diff --git a/examples/dog_app.rs b/examples/dog_app.rs index d5440d9ad..7f868c235 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -2,12 +2,7 @@ use dioxus::prelude::*; use std::collections::HashMap; fn main() { - dioxus_desktop::launch(|cx| { - cx.render(rsx! { - h1 {"Loading...."} - app_root {} - }) - }); + dioxus_desktop::launch(|cx| render!(app_root {})); } #[derive(Debug, Clone, PartialEq, serde::Deserialize)] diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 8b2650c22..ca7ff66f6 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -1,6 +1,6 @@ -use std::cell::Cell; use crate::factory::RenderReturn; use bumpalo::Bump; +use std::cell::Cell; pub struct BumpFrame { pub bump: Bump, diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index cea2388bd..c5b6c97c1 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,22 +1,36 @@ +use std::cell::Cell; + use crate::factory::RenderReturn; -use crate::innerlude::{Mutations, SuspenseContext, VText}; +use crate::innerlude::{Mutations, VComponent, VFragment, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; use crate::virtual_dom::VirtualDom; -use crate::{AttributeValue, ScopeId, TemplateAttribute}; +use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext, TemplateAttribute}; impl VirtualDom { + /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. + /// + /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. pub(crate) fn create_scope<'a>( &mut self, scope: ScopeId, mutations: &mut Mutations<'a>, template: &'a VNode<'a>, ) -> usize { + let mutations_to_this_point = mutations.len(); + self.scope_stack.push(scope); let out = self.create(mutations, template); self.scope_stack.pop(); + + if !self.collected_leaves.is_empty() { + if let Some(boundary) = self.scopes[scope.0].has_context::() { + println!("Boundary detected and pending leaves!"); + } + } + out } @@ -29,18 +43,7 @@ impl VirtualDom { // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways if !self.templates.contains_key(&template.template.id) { - for node in template.template.roots { - let mutations = &mut mutations.template_mutations; - self.create_static_node(mutations, template, node); - } - - mutations.template_mutations.push(SaveTemplate { - name: template.template.id, - m: template.template.roots.len(), - }); - - self.templates - .insert(template.template.id, template.template.clone()); + self.register_template(template, mutations); } // Walk the roots, creating nodes and assigning IDs @@ -167,6 +170,21 @@ impl VirtualDom { on_stack } + /// Insert a new template into the VirtualDom's template registry + fn register_template<'a>(&mut self, template: &'a VNode<'a>, mutations: &mut Mutations<'a>) { + for node in template.template.roots { + self.create_static_node(&mut mutations.template_mutations, template, node); + } + + mutations.template_mutations.push(SaveTemplate { + name: template.template.id, + m: template.template.roots.len(), + }); + + self.templates + .insert(template.template.id, template.template); + } + pub(crate) fn create_static_node<'a>( &mut self, mutations: &mut Vec>, @@ -234,125 +252,193 @@ impl VirtualDom { node: &'a DynamicNode<'a>, idx: usize, ) -> usize { - match &node { - DynamicNode::Text(VText { id, value }) => { - let new_id = self.next_element(template, template.template.node_paths[idx]); - id.set(new_id); - mutations.push(HydrateText { - id: new_id, - path: &template.template.node_paths[idx][1..], - value, - }); - 0 - } + use DynamicNode::*; + match node { + Text(text) => self.create_dynamic_text(mutations, template, text, idx), + Placeholder(slot) => self.create_placeholder(template, idx, slot, mutations), + Fragment(frag) => self.create_fragment(frag, mutations), + Component(component) => self.create_component_node(mutations, template, component, idx), + } + } - DynamicNode::Component(component) => { - let props = component.props.replace(None).unwrap(); - let prop_ptr = unsafe { std::mem::transmute(props.as_ref()) }; - let scope = self.new_scope(prop_ptr).id; - component.props.replace(Some(props)); + fn create_dynamic_text<'a>( + &mut self, + mutations: &mut Mutations<'a>, + template: &VNode<'a>, + text: &VText<'a>, + idx: usize, + ) -> usize { + // Allocate a dynamic element reference for this text node + let new_id = self.next_element(template, template.template.node_paths[idx]); - component.scope.set(Some(scope)); + // Make sure the text node is assigned to the correct element + text.id.set(new_id); - let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; + // Add the mutation to the list + mutations.push(HydrateText { + id: new_id, + path: &template.template.node_paths[idx][1..], + value: text.value, + }); - match return_nodes { - RenderReturn::Sync(None) => { - todo!() - } + // Since we're hydrating an existing node, we don't create any new nodes + 0 + } - RenderReturn::Async(_) => { - let new_id = self.next_element(template, template.template.node_paths[idx]); - component.placeholder.set(Some(new_id)); - self.scopes[scope.0].placeholder.set(Some(new_id)); + fn create_placeholder( + &mut self, + template: &VNode, + idx: usize, + slot: &Cell, + mutations: &mut Mutations, + ) -> usize { + // Allocate a dynamic element reference for this text node + let id = self.next_element(template, template.template.node_paths[idx]); - mutations.push(AssignId { - id: new_id, - path: &template.template.node_paths[idx][1..], - }); + // Make sure the text node is assigned to the correct element + slot.set(id); - let boudary = self.scopes[scope.0] - .consume_context::() - .unwrap(); + // Assign the ID to the existing node in the template + mutations.push(AssignId { + path: &template.template.node_paths[idx][1..], + id, + }); - if boudary.placeholder.get().is_none() { - boudary.placeholder.set(Some(new_id)); - } - boudary - .waiting_on - .borrow_mut() - .extend(self.collected_leaves.drain(..)); + // Since the placeholder is already in the DOM, we don't create any new nodes + 0 + } - 0 - } + fn create_fragment<'a>( + &mut self, + frag: &'a VFragment<'a>, + mutations: &mut Mutations<'a>, + ) -> usize { + frag.nodes + .iter() + .fold(0, |acc, child| acc + self.create(mutations, child)) + } - RenderReturn::Sync(Some(template)) => { - let mutations_to_this_point = mutations.len(); + fn create_component_node<'a>( + &mut self, + mutations: &mut Mutations<'a>, + template: &'a VNode<'a>, + component: &'a VComponent<'a>, + idx: usize, + ) -> usize { + let props = component.props.replace(None).unwrap(); - self.scope_stack.push(scope); - let mut created = self.create(mutations, template); - self.scope_stack.pop(); + let prop_ptr = unsafe { std::mem::transmute(props.as_ref()) }; + let scope = self.new_scope(prop_ptr).id; - if !self.collected_leaves.is_empty() { - if let Some(boundary) = - self.scopes[scope.0].has_context::() - { - // Since this is a boundary, use it as a placeholder - let new_id = - self.next_element(template, template.template.node_paths[idx]); - component.placeholder.set(Some(new_id)); - self.scopes[scope.0].placeholder.set(Some(new_id)); - mutations.push(AssignId { - id: new_id, - path: &template.template.node_paths[idx][1..], - }); + component.props.replace(Some(props)); + component.scope.set(Some(scope)); - // Now connect everything to the boundary - let boundary_mut = boundary; - let split_off = mutations.split_off(mutations_to_this_point); - let split_off: Vec = - unsafe { std::mem::transmute(split_off) }; + let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; - if boundary_mut.placeholder.get().is_none() { - boundary_mut.placeholder.set(Some(new_id)); - } + use RenderReturn::*; - // In the generated edits, we want to pick off from where we left off. - boundary_mut.mutations.borrow_mut().edits.extend(split_off); - - boundary_mut - .waiting_on - .borrow_mut() - .extend(self.collected_leaves.drain(..)); - - created = 0; - } - } - - // handle any waiting on futures accumulated by async calls down the tree - // if this is a boundary, we split off the tree - created - } - } - } - - DynamicNode::Fragment(frag) => { - // Todo: if there's no children create a placeholder instead ? - frag.nodes - .iter() - .fold(0, |acc, child| acc + self.create(mutations, child)) - } - - DynamicNode::Placeholder(slot) => { - let id = self.next_element(template, template.template.node_paths[idx]); - slot.set(id); - mutations.push(AssignId { - path: &template.template.node_paths[idx][1..], - id, - }); - - 0 + match return_nodes { + Sync(Some(t)) => self.create_component(mutations, scope, t, idx, component), + Sync(None) | Async(_) => { + self.create_component_placeholder(template, idx, component, scope, mutations) } } } + + fn create_component<'a>( + &mut self, + mutations: &mut Mutations<'a>, + scope: ScopeId, + template: &'a VNode<'a>, + idx: usize, + component: &'a VComponent<'a>, + ) -> usize { + // // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary + // // is encountered + // let mutations_to_this_point = mutations.len(); + + // Create the component's root element + let created = self.create_scope(scope, mutations, template); + + if !self.collected_leaves.is_empty() { + println!("collected leaves: {:?}", self.collected_leaves); + } + + created + + // // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense + // let boundary = match self.scopes[scope.0].has_context::() { + // Some(boundary) if !self.collected_leaves.is_empty() => boundary, + // _ => return created, + // }; + + // // Since this is a boundary, use it as a placeholder + // let new_id = self.next_element(template, template.template.node_paths[idx]); + // component.placeholder.set(Some(new_id)); + // self.scopes[scope.0].placeholder.set(Some(new_id)); + // mutations.push(AssignId { + // id: new_id, + // path: &template.template.node_paths[idx][1..], + // }); + + // // Now connect everything to the boundary + // let boundary_mut = boundary; + // let split_off = mutations.split_off(mutations_to_this_point); + // let split_off: Vec = unsafe { std::mem::transmute(split_off) }; + + // if boundary_mut.placeholder.get().is_none() { + // boundary_mut.placeholder.set(Some(new_id)); + // } + + // // In the generated edits, we want to pick off from where we left off. + // boundary_mut.mutations.borrow_mut().edits.extend(split_off); + + // boundary_mut + // .waiting_on + // .borrow_mut() + // .extend(self.collected_leaves.drain(..)); + + // 0 + + // let boudary = self.scopes[scope.0] + // .consume_context::() + // .unwrap(); + + // boudary + // .waiting_on + // .borrow_mut() + // .extend(self.collected_leaves.drain(..)); + + // if boudary.placeholder.get().is_none() { + // boudary.placeholder.set(Some(new_id)); + // } + } + + /// Take the rendered nodes from a component and handle them if they were async + /// + /// IE simply assign an ID to the placeholder + fn create_component_placeholder( + &mut self, + template: &VNode, + idx: usize, + component: &VComponent, + scope: ScopeId, + mutations: &mut Mutations, + ) -> usize { + let new_id = self.next_element(template, template.template.node_paths[idx]); + + // Set the placeholder of the component + component.placeholder.set(Some(new_id)); + + // Set the placeholder of the scope + self.scopes[scope.0].placeholder.set(Some(new_id)); + + // Since the placeholder is already in the DOM, we don't create any new nodes + mutations.push(AssignId { + id: new_id, + path: &template.template.node_paths[idx][1..], + }); + + 0 + } } diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 374d63aa4..773fc5c29 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -1,5 +1,4 @@ use std::{ - any::Any, cell::{Cell, RefCell}, fmt::Arguments, }; @@ -130,7 +129,10 @@ where } pub enum RenderReturn<'a> { + /// A currently-available element Sync(Element<'a>), + + /// An ongoing future that will resolve to a [`Element`] Async(BumpBox<'a, dyn Future> + 'a>), } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 1a78ed22b..26f6d69a7 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -95,7 +95,10 @@ pub use crate::innerlude::{ TemplateAttribute, TemplateNode, UiEvent, + VComponent, + VFragment, VNode, + VText, VirtualDom, }; diff --git a/packages/core/src/scheduler.old.rs b/packages/core/src/scheduler.old.rs deleted file mode 100644 index fdc07a233..000000000 --- a/packages/core/src/scheduler.old.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::{cell::RefCell, rc::Rc, sync::Arc}; - -use futures_task::ArcWake; -use slab::Slab; -use std::future::Future; - -use crate::{innerlude::Mutation, ScopeId}; - -type Shared = Rc>; -struct LocalTask {} - -pub struct Fiber { - // The work-in progress of this suspended tree - pub mutations: Vec>, -} - -#[derive(Clone)] -pub struct SchedulerHandle { - tasks: Shared>, - suspended: Shared>, - fibers: Shared>, - tx: futures_channel::mpsc::UnboundedSender, -} - -struct TaskEntry {} - -struct LocalTaskWaker { - future: T, - id: TaskId, - tx: futures_channel::mpsc::UnboundedSender, -} - -unsafe impl Send for LocalTaskWaker {} -unsafe impl Sync for LocalTaskWaker {} - -impl ArcWake for LocalTaskWaker { - fn wake(self: Arc) { - Self::wake_by_ref(&self) - } - fn wake_by_ref(arc_self: &Arc) { - arc_self - .tx - .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)) - .unwrap(); - } -} - -impl SchedulerHandle { - fn spawn(&self, fut: impl Future + 'static) -> TaskId { - use futures_task::waker; - - let tasks = self.tasks.borrow_mut(); - let entry = tasks.vacant_entry(); - let id = TaskId(entry.key()); - - let task = Arc::new(LocalTaskWaker { - future: fut, - id, - tx: self.tx.clone(), - }); - - let local_task = waker(task.clone()); - - entry.insert(val); - - // - todo!() - } - - fn remove(&self, id: TaskId) { - // - } -} diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 981780c3a..9d90acf4a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -2,6 +2,7 @@ use crate::{ any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, + factory::RenderReturn, innerlude::{Scheduler, SchedulerMsg}, lazynodes::LazyNodes, nodes::VNode, @@ -116,7 +117,7 @@ impl ScopeState { /// Get a handle to the currently active head node arena for this Scope /// /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR. - pub fn root_node<'a>(&'a self) -> &'a VNode<'a> { + pub fn root_node<'a>(&'a self) -> &'a RenderReturn<'a> { let r = unsafe { &*self.current_frame().node.get() }; unsafe { std::mem::transmute(r) } } diff --git a/packages/ssr/src/template.rs b/packages/ssr/src/template.rs index c37808b16..1df8c1581 100644 --- a/packages/ssr/src/template.rs +++ b/packages/ssr/src/template.rs @@ -1,5 +1,5 @@ use super::cache::Segment; -use dioxus_core::{prelude::*, AttributeValue, DynamicNode}; +use dioxus_core::{prelude::*, AttributeValue, DynamicNode, VText}; use std::collections::HashMap; use std::fmt::Write; use std::rc::Rc; @@ -18,7 +18,7 @@ impl SsrRender { let root = scope.root_node(); let mut out = String::new(); - self.render_template(&mut out, dom, root).unwrap(); + // self.render_template(&mut out, dom, root).unwrap(); out } @@ -46,32 +46,37 @@ impl SsrRender { }; } Segment::Node(idx) => match &template.dynamic_nodes[*idx] { - DynamicNode::Text { value, inner, .. } => { - // in SSR, we are concerned that we can't hunt down the right text node since they might get merged - if !*inner { - write!(buf, "")?; - } + DynamicNode::Component(_) => todo!(), + DynamicNode::Text(_) => todo!(), + DynamicNode::Fragment(_) => todo!(), + DynamicNode::Placeholder(_) => todo!(), + // todo!() + // DynamicNode::Text(VText { id, value }) => { + // // in SSR, we are concerned that we can't hunt down the right text node since they might get merged + // // if !*inner { + // write!(buf, "")?; + // // } - // todo: escape the text - write!(buf, "{}", value)?; + // // todo: escape the text + // write!(buf, "{}", value)?; - if !*inner { - write!(buf, "")?; - } - } - DynamicNode::Fragment { nodes, .. } => { - for child in *nodes { - self.render_template(buf, dom, child)?; - } - } - DynamicNode::Component { scope, .. } => { - let id = scope.get().unwrap(); - let scope = dom.get_scope(id).unwrap(); - self.render_template(buf, dom, scope.root_node())?; - } - DynamicNode::Placeholder(_el) => { - write!(buf, "")?; - } + // // if !*inner { + // write!(buf, "")?; + // // } + // } + // DynamicNode::Fragment { nodes, .. } => { + // for child in *nodes { + // self.render_template(buf, dom, child)?; + // } + // } + // DynamicNode::Component { scope, .. } => { + // let id = scope.get().unwrap(); + // let scope = dom.get_scope(id).unwrap(); + // self.render_template(buf, dom, scope.root_node())?; + // } + // DynamicNode::Placeholder(_el) => { + // write!(buf, "")?; + // } }, Segment::PreRendered(contents) => buf.push_str(contents), @@ -107,24 +112,24 @@ fn to_string_works() { use Segment::*; - assert_eq!( - StringCache::from_template(&dom.base_scope().root_node()) - .unwrap() - .segments, - vec![ - PreRendered("
Hello world 1 -->".into(),), - Node(0,), - PreRendered("<-- Hello world 2
nest 1
nest 2
".into(),), - Node(1,), - Node(2,), - PreRendered("
".into(),), - ] - ); + // assert_eq!( + // StringCache::from_template(&dom.base_scope().root_node()) + // .unwrap() + // .segments, + // vec![ + // PreRendered("
Hello world 1 -->".into(),), + // Node(0,), + // PreRendered("<-- Hello world 2
nest 1
nest 2
".into(),), + // Node(1,), + // Node(2,), + // PreRendered("
".into(),), + // ] + // ); - assert_eq!( - SsrRender::default().render_vdom(&dom), - "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
" - ); + // assert_eq!( + // SsrRender::default().render_vdom(&dom), + // "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
" + // ); }