diff --git a/Cargo.toml b/Cargo.toml index 0841d0b62..44d161884 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "packages/dioxus", "packages/core-macro", "packages/core", - # "packages/web", + "packages/web", # "packages/router", # "packages/ssr", # "packages/webview", diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 7a2b6b574..9cd1429a0 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -16,7 +16,7 @@ once_cell = "1.7.2" proc-macro-hack = "0.5.19" proc-macro2 = "1.0.6" quote = "1.0" -syn = { version = "1.0.11", features = ["full"] } +syn = { version = "1.0.11", features = ["full", "extra-traits"] } # testing [dev-dependencies] diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 5f16aab82..95090105c 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -11,7 +11,6 @@ use proc_macro2::TokenStream; use syn::parse::Error; use syn::spanned::Spanned; - use quote::quote; pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result { diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 52b41edce..d0676bcd4 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -10,10 +10,8 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# dodrio-derive = { path = "../html-macro-2", version = "0.1.0" } -# dioxus-html-macro = { path = "../html-macro", version = "0.1.0" } -# dioxus-html-2 = { path = "../html-macro-2", version = "0.1.0" } dioxus-core-macro = { path = "../core-macro", version = "0.1.1" } + # Backs some static data once_cell = "1.5.2" @@ -22,18 +20,12 @@ generational-arena = { version = "0.2.8", features = ["serde"] } # Bumpalo backs the VNode creation bumpalo = { version = "3.6.0", features = ["collections"] } -owning_ref = "0.4.1" - # all the arenas 👿 typed-arena = "2.0.1" -toolshed = "0.8.1" id-arena = "2.2.1" thiserror = "1.0.23" fxhash = "0.2.1" longest-increasing-subsequence = "0.1.0" serde = { version = "1.0.123", features = ["derive"] } log = "0.4.14" -pretty_env_logger = "0.4.0" -fstrings = "0.2.3" -# ouroboros = "0.8.0" -# hashbrown = { version = "0.9.1", features = ["bumpalo"] } +uuid = {version = "0.8.2", features=["serde", "v4"]} diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 7b6138ad7..011c84ec7 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -7,8 +7,6 @@ fn main() {} - - use dioxus_core::prelude::*; struct Props { @@ -73,9 +71,14 @@ impl PartialEq for ChildProps<'_> { impl<'a> Properties for ChildProps<'a> { type Builder = ChildPropsBuilder<'a, ((), ())>; + type StaticOutput = ChildProps<'static>; fn builder() -> ::Builder { ChildProps::builder() } + + unsafe fn into_static(self) -> Self::StaticOutput { + unsafe { std::mem::transmute(self) } + } } fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree { diff --git a/packages/core/examples/fc.rs b/packages/core/examples/fc.rs index 5b9d6557a..eaa385c1a 100644 --- a/packages/core/examples/fc.rs +++ b/packages/core/examples/fc.rs @@ -1,9 +1,6 @@ use dioxus_core::component::fc_to_builder; use dioxus_core::prelude::*; - - - static BLAH: FC<()> = |ctx, _props| { let g = "asd".to_string(); ctx.render(rsx! { @@ -30,7 +27,12 @@ fn main() {} impl Properties for ExampleProps { type Builder = ExamplePropsBuilder<((),)>; + type StaticOutput = ExampleProps; fn builder() -> Self::Builder { ExampleProps::builder() } + + unsafe fn into_static(self) -> Self { + self + } } diff --git a/packages/core/examples/fmter.rs b/packages/core/examples/fmter.rs index 9d86f1237..0136757b9 100644 --- a/packages/core/examples/fmter.rs +++ b/packages/core/examples/fmter.rs @@ -1,12 +1,8 @@ // #[macro_use] -extern crate fstrings; - - // use dioxus_core::ifmt; // use fstrings::format_args_f; - fn main() { let bump = bumpalo::Bump::new(); let _b = ≎ diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index eefc250a4..09c14e188 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -27,7 +27,12 @@ static Example: FC = |ctx, _props| { // toodo: derive this impl Properties for SomeProps { type Builder = SomePropsBuilder<((),)>; + type StaticOutput = SomeProps; fn builder() -> Self::Builder { SomeProps::builder() } + + unsafe fn into_static(self) -> Self { + self + } } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 950ba45df..f1771719e 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -11,7 +11,9 @@ pub type ScopeIdx = generational_arena::Index; pub trait Properties: PartialEq { type Builder; + type StaticOutput: Properties + 'static; fn builder() -> Self::Builder; + unsafe fn into_static(self) -> Self::StaticOutput; } pub struct EmptyBuilder; @@ -23,10 +25,15 @@ impl EmptyBuilder { impl Properties for () { type Builder = EmptyBuilder; + type StaticOutput = (); fn builder() -> Self::Builder { EmptyBuilder {} } + + unsafe fn into_static(self) -> Self::StaticOutput { + std::mem::transmute(self) + } } pub fn fc_to_builder(_f: FC) -> T::Builder { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 5c1b6c885..74209774d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -32,13 +32,18 @@ //! //! More info on how to improve this diffing algorithm: //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ -use crate::{ - innerlude::*, -}; +use crate::{innerlude::*, scope::Scoped}; use bumpalo::Bump; use fxhash::{FxHashMap, FxHashSet}; +use generational_arena::Arena; +use uuid::Uuid; -use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; +use std::{ + cell::{RefCell, RefMut}, + cmp::Ordering, + collections::VecDeque, + rc::Rc, +}; /// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while /// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event. @@ -50,20 +55,29 @@ use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; /// different cursor position. /// /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components -/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via +/// that were modified by the eventtrigger. This prevents doubly evaluating components if they were both updated via /// subscriptions and props changes. pub struct DiffMachine<'a> { pub change_list: EditMachine<'a>, pub diffed: FxHashSet, - pub need_to_diff: FxHashSet, + pub lifecycle_events: VecDeque, +} + +#[derive(Debug)] +pub enum LifeCycleEvent { + Mount { caller: Caller, id: Uuid }, + PropsChanged, + SameProps, + Remove, } impl<'a> DiffMachine<'a> { - pub fn new(bump: &'a Bump) -> Self { + pub fn new() -> Self { + // pub fn new(bump: &'a Bump) -> Self { Self { - change_list: EditMachine::new(bump), + lifecycle_events: VecDeque::new(), + change_list: EditMachine::new(), diffed: FxHashSet::default(), - need_to_diff: FxHashSet::default(), } } @@ -111,18 +125,31 @@ impl<'a> DiffMachine<'a> { self.diff_children(eold.children, enew.children); } - (VNode::Component(_cold), VNode::Component(_cnew)) => { - // if cold.comp != cnew.comp { - // // queue an event to mount this new component + (VNode::Component(cold), VNode::Component(cnew)) => { + todo!("should not happen") + // if cold.caller_ref != cnew.caller_ref { + // // todo: create a stable addr + // self.lifecycle_events.push_back(LifeCycleEvent::Mount); // return; // } - // compare props.... somehow..... + // let comparator = &cnew.comparator.0; + // let old_props = cold.raw_props.as_ref(); + // let has_changed = comparator(old_props); - todo!("Usage of component VNode not currently supported"); + // if has_changed { + // self.lifecycle_events + // .push_back(LifeCycleEvent::PropsChanged); + // return; + // } + + // the component is the same and hasn't changed + // migrate props over (so the addr remains stable) but don't rerun the component } + // todo: knock out any listeners (_, VNode::Component(_new)) => { + // self.lifecycle_events.push_back(LifeCycleEvent::Mount); // we have no stable reference to work from // push the lifecycle event onto the queue // self.lifecycle_events @@ -151,6 +178,100 @@ impl<'a> DiffMachine<'a> { } } + // Emit instructions to create the given virtual node. + // + // The change list stack may have any shape upon entering this function: + // + // [...] + // + // When this function returns, the new node is on top of the change list stack: + // + // [... node] + fn create(&mut self, node: &VNode<'a>) { + debug_assert!(self.change_list.traversal_is_committed()); + match node { + VNode::Text(VText { text }) => { + self.change_list.create_text_node(text); + } + VNode::Element(&VElement { + key: _, + tag_name, + listeners, + attributes, + children, + namespace, + }) => { + // log::info!("Creating {:#?}", node); + if let Some(namespace) = namespace { + self.change_list.create_element_ns(tag_name, namespace); + } else { + self.change_list.create_element(tag_name); + } + + listeners.iter().enumerate().for_each(|(_id, listener)| { + self.change_list + .new_event_listener(listener.event, listener.scope, listener.id) + }); + + for attr in attributes { + self.change_list + .set_attribute(&attr.name, &attr.value, namespace.is_some()); + } + + // Fast path: if there is a single text child, it is faster to + // create-and-append the text node all at once via setting the + // parent's `textContent` in a single change list instruction than + // to emit three instructions to (1) create a text node, (2) set its + // text content, and finally (3) append the text node to this + // parent. + if children.len() == 1 { + if let VNode::Text(VText { text }) = children[0] { + self.change_list.set_text(text); + return; + } + } + + for child in children { + self.create(child); + self.change_list.append_child(); + } + } + + /* + todo: integrate re-entrace + */ + // NodeKind::Cached(ref c) => { + // cached_roots.insert(c.id); + // let (node, template) = cached_set.get(c.id); + // if let Some(template) = template { + // create_with_template( + // cached_set, + // self.change_list, + // registry, + // template, + // node, + // cached_roots, + // ); + // } else { + // create(cached_set, change_list, registry, node, cached_roots); + // } + // } + VNode::Component(component) => { + self.change_list + .create_text_node("placeholder for vcomponent"); + let id = uuid::Uuid::new_v4(); + self.change_list.save_known_root(id); + self.lifecycle_events.push_back(LifeCycleEvent::Mount { + caller: component.caller.clone(), + id, + }); + } + VNode::Suspended => { + todo!("Creation of VNode::Suspended not yet supported") + } + } + } + // Diff event listeners between `old` and `new`. // // The listeners' node must be on top of the change list stack: @@ -787,93 +908,6 @@ impl<'a> DiffMachine<'a> { // Support methods // ====================== - // Emit instructions to create the given virtual node. - // - // The change list stack may have any shape upon entering this function: - // - // [...] - // - // When this function returns, the new node is on top of the change list stack: - // - // [... node] - fn create(&mut self, node: &VNode<'a>) { - debug_assert!(self.change_list.traversal_is_committed()); - match node { - VNode::Text(VText { text }) => { - self.change_list.create_text_node(text); - } - VNode::Element(&VElement { - key: _, - tag_name, - listeners, - attributes, - children, - namespace, - }) => { - // log::info!("Creating {:#?}", node); - if let Some(namespace) = namespace { - self.change_list.create_element_ns(tag_name, namespace); - } else { - self.change_list.create_element(tag_name); - } - - listeners.iter().enumerate().for_each(|(_id, listener)| { - self.change_list - .new_event_listener(listener.event, listener.scope, listener.id) - }); - - for attr in attributes { - self.change_list - .set_attribute(&attr.name, &attr.value, namespace.is_some()); - } - - // Fast path: if there is a single text child, it is faster to - // create-and-append the text node all at once via setting the - // parent's `textContent` in a single change list instruction than - // to emit three instructions to (1) create a text node, (2) set its - // text content, and finally (3) append the text node to this - // parent. - if children.len() == 1 { - if let VNode::Text(VText { text }) = children[0] { - self.change_list.set_text(text); - return; - } - } - - for child in children { - self.create(child); - self.change_list.append_child(); - } - } - - /* - todo: integrate re-entrace - */ - // NodeKind::Cached(ref c) => { - // cached_roots.insert(c.id); - // let (node, template) = cached_set.get(c.id); - // if let Some(template) = template { - // create_with_template( - // cached_set, - // self.change_list, - // registry, - // template, - // node, - // cached_roots, - // ); - // } else { - // create(cached_set, change_list, registry, node, cached_roots); - // } - // } - VNode::Suspended => { - todo!("Creation of VNode::Suspended not yet supported") - } - VNode::Component(_) => { - todo!("Creation of VNode::Component not yet supported") - } - } - } - // Remove all of a node's children. // // The change list stack must have this shape upon entry to this function: diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index a8f0e4a47..7936c6a88 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -10,7 +10,11 @@ pub use use_state_def::use_state; mod use_state_def { use crate::innerlude::*; - use std::{cell::RefCell, ops::DerefMut, rc::Rc}; + use std::{ + cell::RefCell, + ops::{Deref, DerefMut}, + rc::Rc, + }; struct UseState { new_val: Rc>>, @@ -18,6 +22,22 @@ mod use_state_def { caller: Box, } + struct UseStateHandle<'a, T> { + inner: &'a T, + } + impl<'a, T> UseStateHandle<'a, T> { + fn set(&self, new_val: T) {} + fn modify(&self, f: impl FnOnce(&mut T)) {} + } + + impl<'a, T> Deref for UseStateHandle<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner + } + } + /// Store state between component renders! /// When called, this hook retrives a stored value and provides a setter to update that value. /// When the setter is called, the component is re-ran with the new value. @@ -175,9 +195,9 @@ mod use_reducer_def { // #[cfg(test)] mod tests { - + use crate::prelude::*; - + enum Actions { Incr, Decr, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 3a337e44a..c97cc7658 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -91,14 +91,17 @@ pub(crate) mod innerlude { // pub(crate) use crate::component::Properties; pub(crate) use crate::component::Properties; pub(crate) use crate::context::Context; - pub(crate) use crate::error::{Result}; - pub use crate::events::EventTrigger; + pub use crate::diff::LifeCycleEvent; + pub(crate) use crate::error::Result; + pub use crate::events::{EventTrigger, VirtualEvent}; use crate::nodes; - - - pub(crate) use nodes::*; pub use crate::component::ScopeIdx; + pub use crate::context::hooks::Hook; + pub use crate::nodes::VNode; + + pub(crate) use nodes::*; + pub use crate::diff::DiffMachine; pub use crate::patch::{EditList, EditMachine}; // pub use crate::patchdx; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 81e5a7248..cb5613d51 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1,18 +1,15 @@ //! Helpers for building virtual DOM VNodes. -use std::{borrow::BorrowMut}; +use std::{borrow::BorrowMut, intrinsics::transmute}; use crate::{ context::NodeCtx, events::VirtualEvent, - innerlude::{VComponent, FC}, + innerlude::{Properties, VComponent, FC}, nodes::{Attribute, Listener, NodeKey, VNode}, prelude::VElement, }; - - - /// A virtual DOM element builder. /// /// Typically constructed with element-specific constructors, eg the `div` @@ -526,12 +523,10 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { // _f: crate::innerlude::FC, // _props: T -pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>( - _ctx: &'b NodeCtx<'a>, - _f: FC, - _p: T, -) -> VNode<'a> { - // crate::nodes::VComponent - todo!() - // VNode::Component() +pub fn virtual_child<'a, T: Properties>(ctx: &NodeCtx<'a>, f: FC, p: T) -> VNode<'a> { + // alloc props into the bump; + + // erase the lifetime + let p_static = unsafe { p.into_static() }; + VNode::Component(crate::nodes::VComponent::new(f, p_static)) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 7de6d9062..b9f5c8b23 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -10,12 +10,7 @@ use crate::{ use bumpalo::Bump; use std::fmt::Debug; -use std::{ - any::{Any}, - cell::RefCell, - marker::PhantomData, - rc::Rc, -}; +use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc}; /// A domtree represents the result of "Viewing" the context /// It's a placeholder over vnodes, to make working with lifetimes easier @@ -271,21 +266,28 @@ impl<'a> VText<'a> { /// Only supports the functional syntax pub type StableScopeAddres = Rc>>; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct VComponent<'src> { pub stable_addr: StableScopeAddres, + pub raw_props: Rc, pub comparator: Comparator, pub caller: Caller, + pub caller_ref: *const (), _p: PhantomData<&'src ()>, } -pub struct Comparator(Box bool>); +pub trait PropsComparator {} + +#[derive(Clone)] +pub struct Comparator(pub Rc bool>); +// pub struct Comparator(pub Rc bool>); impl std::fmt::Debug for Comparator { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } } -pub struct Caller(Box DomTree>); +#[derive(Clone)] +pub struct Caller(pub Rc DomTree>); impl std::fmt::Debug for Caller { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) @@ -298,30 +300,41 @@ impl<'a> VComponent<'a> { // - perform comparisons when diffing (memoization) // TODO: lift the requirement that props need to be static // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait - pub fn new(caller: FC

, props: P) -> Self { + pub fn new(caller: FC, props: P) -> Self { + let caller_ref = caller as *const (); + let props = Rc::new(props); // used for memoization let p1 = props.clone(); - let props_comparator = move |new_props: &dyn Any| -> bool { - new_props - .downcast_ref::

() - .map(|new_p| p1.as_ref() == new_p) - .unwrap_or_else(|| { - log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); - false - }) + let props_comparator = move |new_props: ()| -> bool { + false + // let props_comparator = move |new_props: &dyn Any| -> bool { + // new_props + // .downcast_ref::

() + // .map(|new_p| p1.as_ref() == new_p) + // .unwrap_or_else(|| { + // log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); + // false + // }) }; // used for actually rendering the custom component let p2 = props.clone(); - let caller = move |ctx: Context| -> DomTree { caller(ctx, p2.as_ref()) }; + let caller = move |ctx: Context| -> DomTree { + // + // cast back into the right lifetime + caller(ctx, p2.as_ref()) + }; - Self { - _p: PhantomData, - comparator: Comparator(Box::new(props_comparator)), - caller: Caller(Box::new(caller)), - stable_addr: Rc::new(RefCell::new(None)), - } + todo!() + // Self { + // caller_ref, + // raw_props: props, + // _p: PhantomData, + // comparator: Comparator(Rc::new(props_comparator)), + // caller: Caller(Rc::new(caller)), + // stable_addr: Rc::new(RefCell::new(None)), + // } } } diff --git a/packages/core/src/patch.rs b/packages/core/src/patch.rs index 5999e55bf..2e7c726d2 100644 --- a/packages/core/src/patch.rs +++ b/packages/core/src/patch.rs @@ -83,11 +83,13 @@ pub enum Edit<'d> { // ======================================================== // push a known node on to the stack TraverseToKnown { - node: ScopeIdx, + node: uuid::Uuid, + // node: ScopeIdx, }, // Add the current top of the stack to the known nodes MakeKnown { - node: ScopeIdx, + node: uuid::Uuid, + // node: ScopeIdx, }, // Remove the current top of the stack from the known nodes RemoveKnown, @@ -126,18 +128,18 @@ pub struct EditMachine<'src> { pub traversal: Traversal, next_temporary: u32, forcing_new_listeners: bool, - pub emitter: EditList<'src>, } impl<'b> EditMachine<'b> { - pub fn new(_bump: &'b bumpalo::Bump) -> Self { + pub fn new() -> Self { + // pub fn new(_bump: &'b bumpalo::Bump) -> Self { // todo: see if bumpalo is needed for edit list Self { traversal: Traversal::new(), next_temporary: 0, forcing_new_listeners: false, - emitter: EditList::default(), + emitter: EditList::<'b>::default(), } } } @@ -364,11 +366,18 @@ impl<'a> EditMachine<'a> { // debug!("emit: remove_event_listener({:?})", event); } - pub fn save_known_root(&mut self, id: ScopeIdx) { + pub fn save_known_root(&mut self, id: uuid::Uuid) { + // pub fn save_known_root(&mut self, id: ScopeIdx) { + // pub fn save_known_root(&mut self, id: ScopeIdx) { log::debug!("emit: save_known_root({:?})", id); self.emitter.push(Edit::MakeKnown { node: id }) } + pub fn load_known_root(&mut self, id: uuid::Uuid) { + log::debug!("emit: TraverseToKnown({:?})", id); + self.emitter.push(Edit::TraverseToKnown { node: id }) + } + // pub fn save_template(&mut self, id: CacheId) { // debug_assert!(self.traversal_is_committed()); // debug_assert!(!self.has_template(id)); diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index be7487bf1..dbfb526e6 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,21 +1,12 @@ -use crate::component::ScopeIdx; -use crate::context::hooks::Hook; use crate::innerlude::*; -use crate::nodes::VNode; use bumpalo::Bump; -use std::{ - any::{Any}, - cell::RefCell, - marker::PhantomData, - ops::Deref, -}; +use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, ops::Deref}; pub trait Scoped { fn run(&mut self); - fn compare_props(&self, new: &dyn std::any::Any) -> bool; + // fn compare_props(&self, new: &dyn std::any::Any) -> bool; fn call_listener(&mut self, trigger: EventTrigger); - fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>; fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>; } @@ -33,11 +24,16 @@ pub struct Scope { // our own index pub myidx: ScopeIdx, - // the props - pub props: P, + // + pub children: HashSet, + + // // the props + // pub props: P, // and the actual render function - pub caller: FC

, + pub caller: Caller, + _p: std::marker::PhantomData

, + // pub raw_caller: FC

, // ========================== // slightly unsafe stuff @@ -56,43 +52,31 @@ pub struct Scope { // - is self-refenrential and therefore needs to point into the bump // Stores references into the listeners attached to the vnodes // NEEDS TO BE PRIVATE - listeners: RefCell>, + listeners: RefCell>, } // instead of having it as a trait method, we use a single function // todo: do the unsafety magic stuff to erase the type of p -pub fn create_scoped( - caller: FC

, - props: P, +pub fn create_scoped( + // raw_f: FC

, + caller: Caller, + + // props: P, myidx: ScopeIdx, parent: Option, ) -> Box { - let hook_arena = typed_arena::Arena::new(); - let hooks = RefCell::new(Vec::new()); - - let listeners = Default::default(); - - let old_frame = BumpFrame { - bump: Bump::new(), - head_node: VNode::text(""), - }; - - let new_frame = BumpFrame { - bump: Bump::new(), - head_node: VNode::text(""), - }; - - let frames = ActiveFrame::from_frames(old_frame, new_frame); - - Box::new(Scope { + Box::new(Scope::<()> { + // raw_caller: raw_f, + _p: Default::default(), caller, myidx, - hook_arena, - hooks, - frames, - listeners, + hook_arena: typed_arena::Arena::new(), + hooks: RefCell::new(Vec::new()), + frames: ActiveFrame::new(), + children: HashSet::new(), + listeners: Default::default(), parent, - props, + // props, }) } @@ -122,9 +106,8 @@ impl Scoped for Scope

{ }; // Note that the actual modification of the vnode head element occurs during this call - // let _: DomTree = caller(ctx, props); - // let _: DomTree = P::render (ctx, &self.props); - let _: DomTree = (self.caller)(ctx, &self.props); + let _: DomTree = (self.caller.0.as_ref())(ctx); + // let _: DomTree = (self.raw_caller)(ctx, &self.props); /* SAFETY ALERT @@ -145,11 +128,11 @@ impl Scoped for Scope

{ .expect("Viewing did not happen"); } - fn compare_props(&self, new: &dyn Any) -> bool { - new.downcast_ref::

() - .map(|f| &self.props == f) - .expect("Props should not be of a different type") - } + // fn compare_props(&self, new: &dyn Any) -> bool { + // new.downcast_ref::

() + // .map(|f| &self.props == f) + // .expect("Props should not be of a different type") + // } // A safe wrapper around calling listeners // calling listeners will invalidate the list of listeners @@ -194,7 +177,6 @@ impl Scoped for Scope

{ // ========================== // Active-frame related code // ========================== - // todo, do better with the active frame stuff // somehow build this vnode with a lifetime tied to self // This root node has "static" lifetime, but it's really not static. @@ -212,6 +194,19 @@ pub struct BumpFrame { } impl ActiveFrame { + pub fn new() -> Self { + Self::from_frames( + BumpFrame { + bump: Bump::new(), + head_node: VNode::text(""), + }, + BumpFrame { + bump: Bump::new(), + head_node: VNode::text(""), + }, + ) + } + fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { Self { idx: 0.into(), diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 908be0df7..f2fe3a6ca 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -7,7 +7,7 @@ use crate::{ }; use bumpalo::Bump; use generational_arena::Arena; - +use std::{any::TypeId, cell::RefCell, rc::Rc}; /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. @@ -17,8 +17,9 @@ pub struct VirtualDom { /// /// eventually, come up with a better datastructure that reuses boxes for known P types /// like a generational typemap bump arena - /// -> IE a cache line for each P type with soem heuristics on optimizing layout + /// -> IE a cache line for each P type with some heuristics on optimizing layout pub(crate) components: Arena>, + // pub(crate) components: RefCell>>, // pub(crate) components: Rc>>>, /// The index of the root component. /// Will not be ready if the dom is fresh @@ -59,53 +60,87 @@ impl VirtualDom { /// /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// to toss out the entire tree. - pub fn new_with_props(_root: FC

, _root_props: P) -> Self { - // let mut components = Arena::new(); - // let mut components = Arena::new(); + pub fn new_with_props(root: FC

, root_props: P) -> Self { + let mut components = Arena::new(); // Create a reference to the component in the arena // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist - // This puts the dom in a usable state on creation, rather than being potentially invalid + let caller = Caller(Rc::new(move |ctx| { + // + root(ctx, &root_props) + // DomTree {} + })); + let base_scope = components.insert_with(|id| create_scoped(caller, id, None)); // let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); - todo!() - // Self { - // // components: RefCell::new(components), - // components: components, - // // components: Rc::new(RefCell::new(components)), - // base_scope, - // // event_queue: RefCell::new(VecDeque::new()), - // diff_bump: Bump::new(), - // _root_prop_type: TypeId::of::

(), - // } + Self { + components, + base_scope, + diff_bump: Bump::new(), + _root_prop_type: TypeId::of::

(), + } } /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. - - // pub fn rebuild<'s>(&'s mut self) -> Result<> { - // pub fn rebuild<'s>(&'s mut self) -> Result>>> { pub fn rebuild<'s>(&'s mut self) -> Result> { + log::debug!("rebuilding..."); // Reset and then build a new diff machine // The previous edit list cannot be around while &mut is held // Make sure variance doesnt break this - self.diff_bump.reset(); + // bump.reset(); - self.components - .get_mut(self.base_scope) - .expect("Root should always exist") - .run(); + // Diff from the top + let mut diff_machine = DiffMachine::new(); // partial borrow + { + let component = self + .components + .get_mut(self.base_scope) + .expect("failed to acquire base scope"); - let _b = Bump::new(); + component.run(); + } - let mut diff_machine = DiffMachine::new(&self.diff_bump); - // let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope); + { + let component = self + .components + .get(self.base_scope) + .expect("failed to acquire base scope"); - // todo!() + diff_machine.diff_node(component.old_frame(), component.new_frame()); + } - let component = self.components.get(self.base_scope).unwrap(); - diff_machine.diff_node(component.old_frame(), component.new_frame()); - let edits = diff_machine.consume(); - // self.diff_bump = b; + // 'render: loop { + // for event in &mut diff_machine.lifecycle_events.drain(..) { + // log::debug!("event is {:#?}", event); + // match event { + // LifeCycleEvent::Mount { caller, id } => { + // diff_machine.change_list.load_known_root(id); + // let idx = self + // .components + // .insert_with(|f| create_scoped(caller, f, None)); + // // .insert_with(|f| create_scoped(caller, props, myidx, parent)); + // } + // LifeCycleEvent::PropsChanged => { + // // + // break 'render; + // } + // LifeCycleEvent::SameProps => { + // // + // break 'render; + // } + // LifeCycleEvent::Remove => { + // // + // break 'render; + // } + // } + // } + + // if diff_machine.lifecycle_events.is_empty() { + // break 'render; + // } + // } + + let edits: Vec> = diff_machine.consume(); Ok(edits) } @@ -133,50 +168,20 @@ impl VirtualDom { /// } /// /// ``` - pub fn progress_with_event(&mut self, _event: EventTrigger) -> Result> { - // self.components - // .borrow_mut() - // .get_mut(event.component_id) - // .map(|f| { - // f.call_listener(event); - // f - // }) - // .map(|f| f.run()) - // .expect("Borrowing should not fail"); + pub fn progress_with_event(&mut self, event: EventTrigger) -> Result> { + let component = self + .components + .get_mut(event.component_id) + .expect("Borrowing should not fail"); - // component.call_listener(event); + component.call_listener(event); + component.run(); - // .expect("Component should exist if an event was triggered"); - // Reset and then build a new diff machine - // The previous edit list cannot be around while &mut is held - // Make sure variance doesnt break this - // self.diff_bump.reset(); - // let mut diff_machine = DiffMachine::new(&mut self, event.component_id); - // let mut diff_machine = - // DiffMachine::new(&self.diff_bump, &mut self.components, event.component_id); + let mut diff_machine = DiffMachine::new(); + // let mut diff_machine = DiffMachine::new(&self.diff_bump); - // component.run(); - // diff_machine.diff_node(component.old_frame(), component.new_frame()); + diff_machine.diff_node(component.old_frame(), component.new_frame()); - todo!() - // Ok(diff_machine.consume()) - // Err(crate::error::Error::NoEvent) - // Mark dirty components. Descend from the highest node until all dirty nodes are updated. - // let mut affected_components = Vec::new(); - - // while let Some(event) = self.pop_event() { - // if let Some(component_idx) = event.index() { - // affected_components.push(component_idx); - // } - // self.process_lifecycle(event)?; - // } - - // todo!() + Ok(diff_machine.consume()) } } - -enum LifeCycleEvent { - Mount {}, -} - -// todo: add some "handle" to the vdom. Or a way of giving out closures that can mutate the vdoms internal data. diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 6ab8e5345..f2b88a7d0 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -13,26 +13,27 @@ fn main() { } static App: FC<()> = |ctx, _| { - log::info!("Ran component"); - use dioxus::builder::*; - todo!() - // ctx.render(|b| { - // div(b) - // .child(text("hello")) - // .listeners([on(b, "click", |_| { - // // - // log::info!("button1 clicked!"); - // })]) - // .finish() - // }) - // ctx.render(html! { - //

- // "Hello" - // //
- // //
"Count is ..."
- // // - // // - // //
- //
- // }) + ctx.render(rsx! { + div { + h1 {"hello"} + C1 {} + C2 {} + } + }) +}; + +static C1: FC<()> = |ctx, props| { + ctx.render(rsx! { + button { + "numba 1" + } + }) +}; + +static C2: FC<()> = |ctx, props| { + ctx.render(rsx! { + button { + "numba 2" + } + }) }; diff --git a/packages/web/examples/infer.rs b/packages/web/examples/infer.rs index fa9cd1529..3737cec3a 100644 --- a/packages/web/examples/infer.rs +++ b/packages/web/examples/infer.rs @@ -6,6 +6,7 @@ fn main() { wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); console_error_panic_hook::set_once(); + log::info!("hello world"); wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } @@ -18,6 +19,8 @@ static Example: FC<()> = |ctx, _props| { set_event(Some(evt)); }; + log::info!("hello world"); + ctx.render(rsx! { div { @@ -35,21 +38,35 @@ static Example: FC<()> = |ctx, _props| { id: "json" "{event}" } - Example2 { name: "{event}" } + // Example2 { name: "{event}".to_string() } } }) }; -#[derive(Debug, PartialEq)] -struct Props { +#[derive(Debug, PartialEq, Props)] +struct ExampleProps { name: String } -static Example2: FC = |ctx, props| { +static Example2: FC = |ctx, props| { ctx.render(rsx!{ div { h1 {"hello {props.name}"} } }) }; + + +impl Properties for ExampleProps { + type Builder = ExamplePropsBuilder<((),)>; + type StaticOutput = ExampleProps; + fn builder() -> Self::Builder { + ExampleProps::builder() + } + + + unsafe fn into_static(self) -> Self::StaticOutput { + todo!() + } +} diff --git a/packages/web/examples/rsxt.rs b/packages/web/examples/rsxt.rs index 4cb05c598..152aa7cea 100644 --- a/packages/web/examples/rsxt.rs +++ b/packages/web/examples/rsxt.rs @@ -1,23 +1,17 @@ -use bumpalo::Bump; -use dioxus::{events::EventTrigger, prelude::*}; +#![allow(non_snake_case)] use dioxus_core as dioxus; +use dioxus::prelude::*; use dioxus_web::WebsysRenderer; -// creates a proxy address that gets transpiled at compile time -// all asset ids are shuttled through the renderer -// static IMG: ImgAsset = dioxus_asset!(""); - fn main() { - // wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - // console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::new(log::Level::Trace)); + console_error_panic_hook::set_once(); wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)) } -static Example: FC<()> = |ctx, props| { +static Example: FC<()> = |ctx, _props| { let (name, set_name) = use_state(&ctx, || "...?"); - - ctx.render(rsx! { div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto" @@ -47,9 +41,3 @@ static Example: FC<()> = |ctx, props| { } }) }; - - - - - -// onclick: {move |_| set_name("jill")} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index c58d97342..db140b60c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -2,6 +2,7 @@ //! -------------- //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. +use dioxus::prelude::Properties; use fxhash::FxHashMap; use web_sys::{window, Document, Element, Event, Node}; @@ -48,7 +49,7 @@ impl WebsysRenderer { /// Automatically progresses the creation of the VNode tree to completion. /// /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` - pub fn new_with_props(root: FC, root_props: T) -> Self { + pub fn new_with_props(root: FC, root_props: T) -> Self { Self::from_vdom(VirtualDom::new_with_props(root, root_props)) } @@ -59,7 +60,7 @@ impl WebsysRenderer { } pub async fn run(&mut self) -> dioxus_core::error::Result<()> { - let (mut sender, mut receiver) = mpsc::unbounded::(); + let (sender, mut receiver) = mpsc::unbounded::(); let body_element = prepare_websys_dom(); @@ -75,8 +76,10 @@ impl WebsysRenderer { // todo: initialize the event registry properly on the root - self.internal_dom.rebuild()?.iter().for_each(|edit| { - // log::debug!("patching with {:?}", edit); + let edits = self.internal_dom.rebuild()?; + log::debug!("Received edits: {:#?}", edits); + edits.iter().for_each(|edit| { + log::debug!("patching with {:?}", edit); patch_machine.handle_edit(edit); }); // log::debug!("patch stack size {:?}", patch_machine.stack); @@ -92,7 +95,7 @@ impl WebsysRenderer { .progress_with_event(event)? .iter() .for_each(|edit| { - // log::debug!("edit stream {:?}", edit); + log::debug!("edit stream {:?}", edit); patch_machine.handle_edit(edit); });