diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index d0676bcd4..429f15eb7 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -10,22 +10,34 @@ 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] +# todo: use wast for faster load/compile dioxus-core-macro = { path = "../core-macro", version = "0.1.1" } -# Backs some static data -once_cell = "1.5.2" - -# Backs the scope creation and reutilization +# Backs scopes and graphs between parent and children generational-arena = { version = "0.2.8", features = ["serde"] } + # Bumpalo backs the VNode creation bumpalo = { version = "3.6.0", features = ["collections"] } -# all the arenas 👿 +# Backs hook data - might move away from this arena typed-arena = "2.0.1" -id-arena = "2.2.1" + +# custom error type thiserror = "1.0.23" + +# faster hashmaps fxhash = "0.2.1" + +# Used in diffing longest-increasing-subsequence = "0.1.0" -serde = { version = "1.0.123", features = ["derive"] } + +# internall used log = "0.4.14" + +serde = { version = "1.0.123", features = ["derive"], optional=true } + +# todo, remove uuid = {version = "0.8.2", features=["serde", "v4"]} + +[features] +default = [] diff --git a/packages/core/README.md b/packages/core/README.md index 120335e40..814f41a87 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -2,6 +2,8 @@ This is the core crate for the Dioxus Virtual DOM. This README will focus on the technical design and layout of this Virtual DOM implementation. If you want to read more about using Dioxus, then check out the Dioxus crate, documentation, and website. +We reserve the "dioxus" name and aggregate all the various renderers under it. If you want just a single dioxus renderer, then chose from "dioxus-web", "dioxus-desktop", etc. + ## Internals Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts: diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 0e2873ea9..eb26db781 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -1,3 +1,4 @@ +use dioxus_core::virtual_dom::VirtualDom; use dioxus_core::{component::Properties, prelude::*}; fn main() -> Result<(), ()> { diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 0b692a594..68470bbcc 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -3,42 +3,54 @@ //! //! Renderers don't actually need to own the virtual dom (it's up to the implementer). -use crate::prelude::VirtualDom; +use crate::virtual_dom::VirtualDom; +use crate::{innerlude::Result, prelude::*}; pub struct DebugRenderer { - vdom: VirtualDom, + internal_dom: VirtualDom, } impl DebugRenderer { - pub fn new(vdom: VirtualDom) -> Self { - Self { vdom } + /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. + /// + /// This means that the root component must either consumes its own context, or statics are used to generate the page. + /// The root component can access things like routing in its context. + pub fn new(root: FC<()>) -> Self { + Self::new_with_props(root, ()) } - pub async fn run(&mut self) -> Result<(), ()> { + /// Create a new text-renderer instance from a functional component root. + /// 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 { + Self::from_vdom(VirtualDom::new_with_props(root, root_props)) + } + + /// Create a new text renderer from an existing Virtual DOM. + pub fn from_vdom(dom: VirtualDom) -> Self { + // todo: initialize the event registry properly + Self { internal_dom: dom } + } + + pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> { Ok(()) } - - pub fn log_dom(&self) {} } -#[cfg(old)] #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; - use crate::scope::Properties; #[test] fn ensure_creation() -> Result<(), ()> { - #[derive(PartialEq)] - struct Creation {} - impl FC for Creation { - fn render(ctx: Context, props: &Self) -> DomTree { - ctx.render(html! {
"hello world"
}) - } - } + static Example: FC<()> = |ctx, props| { + // + ctx.render(html! {
"hello world"
}) + }; - let mut dom = VirtualDom::new_with_props(Creation {}); + let mut dom = VirtualDom::new(Example); + let machine = DiffMachine::new(); Ok(()) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 8fa0807ba..616c4bc6c 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -58,6 +58,7 @@ use std::{ /// 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 create_diffs: bool, pub change_list: EditMachine<'a>, pub diffed: FxHashSet, pub lifecycle_events: VecDeque>, @@ -75,6 +76,7 @@ pub enum LifeCycleEvent<'a> { impl<'a> DiffMachine<'a> { pub fn new() -> Self { Self { + create_diffs: true, lifecycle_events: VecDeque::new(), change_list: EditMachine::new(), diffed: FxHashSet::default(), diff --git a/packages/core/src/error.rs b/packages/core/src/error.rs index 18b78a9a0..3955bee15 100644 --- a/packages/core/src/error.rs +++ b/packages/core/src/error.rs @@ -3,6 +3,9 @@ pub type Result = std::result::Result; #[derive(ThisError, Debug)] pub enum Error { + #[error("Fatal Internal Error: {0}")] + FatalInternal(&'static str), + #[error("No event to progress")] NoEvent, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index c97cc7658..cdc10adc3 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -135,7 +135,6 @@ pub mod prelude { pub use crate::component::{fc_to_builder, Properties}; pub use crate::context::Context; use crate::nodes; - pub use crate::virtual_dom::VirtualDom; pub use nodes::*; // pub use nodes::iterables::IterableNodes; @@ -144,7 +143,6 @@ pub mod prelude { // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate - pub type VirtualNode<'a> = VNode<'a>; // expose our bumpalo type pub use bumpalo; @@ -160,5 +158,6 @@ pub mod prelude { pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; + pub use crate::debug_renderer::DebugRenderer; pub use crate::hooks::*; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 3c1023db4..0d0a88b85 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -146,7 +146,7 @@ impl<'a> VElement<'a> { /// An attribute on a DOM node, such as `id="my-thing"` or /// `href="https://example.com"`. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug)] pub struct Attribute<'a> { pub name: &'static str, pub value: &'a str, @@ -244,7 +244,7 @@ impl NodeKey { } } -#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, PartialEq)] pub struct VText<'bump> { pub text: &'bump str, } diff --git a/packages/core/src/patch.rs b/packages/core/src/patch.rs index 4b4bb12e3..05b653ce9 100644 --- a/packages/core/src/patch.rs +++ b/packages/core/src/patch.rs @@ -16,16 +16,20 @@ //! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom //! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the //! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object. +//! +//! # Known Issues +//! ---- +//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) use crate::innerlude::ScopeIdx; pub type EditList<'src> = Vec>; /// The `Edit` represents a single modifcation of the renderer tree. -/// todo@ jon: allow serde to be optional /// todo @jon, go through and make certain fields static. tag names should be known at compile time -#[derive(Debug, serde::Serialize, serde::Deserialize)] -#[serde(tag = "type")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type"))] +#[derive(Debug)] pub enum Edit<'src_bump> { // ======================================================== // Common Ops: The most common operation types diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 1d275fa70..4948391c1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -126,7 +126,7 @@ impl Scope { /// This function downcasts the function pointer based on the stored props_type /// /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) - pub fn run<'bump>(&'bump mut self) { + pub fn run_scope<'bump>(&'bump mut self) -> Result<()> { let frame = { let frame = self.frames.next(); frame.bump.reset(); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 6562012f7..0cc94c68e 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,53 +1,36 @@ // use crate::{changelist::EditList, nodes::VNode}; -use crate::innerlude::*; +use crate::{error::Error, innerlude::*}; use crate::{patch::Edit, scope::Scope}; -use bumpalo::Bump; use generational_arena::Arena; use std::{ any::TypeId, - cell::RefCell, rc::{Rc, Weak}, }; +use thiserror::private::AsDynError; + +// We actually allocate the properties for components in their parent's properties +// We then expose a handle to use those props for render in the form of "OpaqueComponent" +pub(crate) type OpaqueComponent<'a> = dyn Fn(Context) -> DomTree + 'a; /// 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. pub struct VirtualDom { /// All mounted components are arena allocated to make additions, removals, and references easy to work with /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena. - /// - /// 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 some heuristics on optimizing layout - pub(crate) components: Arena, - // pub(crate) components: RefCell>>, - // pub(crate) components: Rc>>>, + components: Arena, + /// The index of the root component. /// Will not be ready if the dom is fresh - pub(crate) base_scope: ScopeIdx, + base_scope: ScopeIdx, - pub(crate) root_caller: Rc DomTree + 'static>, + // a strong allocation to the "caller" for the original props + #[doc(hidden)] + _root_caller: Rc>, // Type of the original props. This is done so VirtualDom does not need to be generic. #[doc(hidden)] _root_prop_type: std::any::TypeId, - // ====================== - // DIFF RELATED ITEMs - // ====================== - // // todo: encapsulate more state into this so we can better reuse it - pub(crate) diff_bump: Bump, - // // be very very very very very careful - // pub change_list: EditMachine<'static>, - - // // vdom: &'a VirtualDom, - // vdom: *mut Arena>, - - // // vdom: Rc>>>, - // pub cur_idx: ScopeIdx, - - // // todo - // // do an indexmap sorted by height - // dirty_nodes: fxhash::FxHashSet, } impl VirtualDom { @@ -67,52 +50,49 @@ impl VirtualDom { pub fn new_with_props(root: FC

, root_props: P) -> Self { let mut components = Arena::new(); - // the root is kept around - let root_caller: Rc DomTree + 'static> = - Rc::new(move |ctx| root(ctx, &root_props)); - let weak_caller: Weak DomTree + 'static> = Rc::downgrade(&root_caller); - let base_scope = components.insert_with(move |id| Scope::new(weak_caller, id, None)); + // the root is kept around with a "hard" allocation + let root_caller: Rc = Rc::new(move |ctx| root(ctx, &root_props)); + + // we then expose this to the component with a weak allocation + let weak_caller: Weak = Rc::downgrade(&root_caller); + + let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None)); Self { components, - root_caller, + _root_caller: root_caller, base_scope, - diff_bump: Bump::new(), _root_prop_type: TypeId::of::

(), } } + // consume the top of the diff machine event cycle and dump edits into the edit list + pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> { + Ok(()) + } + /// 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> { - 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 - // bump.reset(); - // 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"); - component.run(); - } + self.components + .get_mut(self.base_scope) + .ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))? + .run_scope()?; - // get raw pointer to the arena - let very_unsafe_components = &mut self.components as *mut generational_arena::Arena; + // // get raw pointer to the arena + // let very_unsafe_components = &mut self.components as *mut generational_arena::Arena; - { - let component = self - .components - .get(self.base_scope) - .expect("failed to acquire base scope"); - - diff_machine.diff_node(component.old_frame(), component.new_frame()); - } + // { + // let component = self + // .components + // .get(self.base_scope) + // .expect("failed to acquire base scope"); + // diff_machine.diff_node(component.old_frame(), component.new_frame()); + // } + // let p = &mut self.components; // chew down the the lifecycle events until all dirty nodes are computed while let Some(event) = diff_machine.lifecycle_events.pop_front() { match event { @@ -127,13 +107,16 @@ impl VirtualDom { // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. unsafe { - let p = &mut *(very_unsafe_components); + // let p = &mut *(very_unsafe_components); + // let p = &mut self.components; // todo, hook up the parent/child indexes properly - let idx = p.insert_with(|f| Scope::new(caller, f, None)); - let c = p.get_mut(idx).unwrap(); - c.run(); - diff_machine.diff_node(c.old_frame(), c.new_frame()); + let idx = self.components.insert_with(|f| Scope::new(caller, f, None)); + let c = self.components.get_mut(idx).unwrap(); + // let idx = p.insert_with(|f| Scope::new(caller, f, None)); + // let c = p.get_mut(idx).unwrap(); + c.run_scope(); + // diff_machine.diff_node(c.old_frame(), c.new_frame()); } } LifeCycleEvent::PropsChanged => { @@ -190,7 +173,7 @@ impl VirtualDom { .expect("Borrowing should not fail"); component.call_listener(event); - component.run(); + component.run_scope(); let mut diff_machine = DiffMachine::new(); // let mut diff_machine = DiffMachine::new(&self.diff_bump); diff --git a/packages/core/tests/integration.rs b/packages/core/tests/integration.rs index 4aca69f33..b190bc8ee 100644 --- a/packages/core/tests/integration.rs +++ b/packages/core/tests/integration.rs @@ -1,16 +1,6 @@ -/// -/// -/// -/// -/// -// use crate::prelude::*; - +use dioxus_core::prelude::*; // type VirtualNode = VNode; /// Test a basic usage of a virtual dom + text renderer combo #[test] -fn simple_integration() { - // let dom = VirtualDom::new(|ctx| html! {

Hello World!
}); - // let mut renderer = TextRenderer::new(dom); - // let output = renderer.render(); -} +fn simple_integration() {} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index db140b60c..16cb733a7 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -6,11 +6,9 @@ use dioxus::prelude::Properties; use fxhash::FxHashMap; use web_sys::{window, Document, Element, Event, Node}; +use dioxus::virtual_dom::VirtualDom; pub use dioxus_core as dioxus; -use dioxus_core::{ - events::EventTrigger, - prelude::{VirtualDom, FC}, -}; +use dioxus_core::{events::EventTrigger, prelude::FC}; use futures::{channel::mpsc, SinkExt, StreamExt}; pub mod interpreter;