diff --git a/Cargo.toml b/Cargo.toml index 4cd6e311c..172de3710 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,14 +20,14 @@ dioxus-ssr = { path = "./packages/ssr", optional = true } [features] default = [ "core", - "atoms", "macro", - "ssr", "hooks", - "router", "html", + "ssr", "web", "desktop", + # "atoms", + # "router", ] atoms = [] macro = ["dioxus-core-macro"] @@ -63,8 +63,8 @@ members = [ "packages/html", "packages/hooks", "packages/web", - "packages/webview", "packages/ssr", + "packages/webview", # "packages/atoms", # "packages/docsite", ] diff --git a/README.md b/README.md index 1e59973f8..59c6f688c 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,6 @@ Examples - | - - Releases - @@ -170,10 +166,10 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Suspense | 🛠 | ✅ | schedule future render from future/promise | | Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events | | Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | -| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | | Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | | 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | | Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | +| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | | Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations | | NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index da5eef235..ba5ec951e 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -89,7 +89,7 @@ static Example: FC<()> = |cx| { // Expressions can be used in element position too: {rsx!(p { "More templating!" })} - {html!(

"Even HTML templating!!"

)} + // {html!(

"Even HTML templating!!"

)} // Iterators {(0..10).map(|i| rsx!(li { "{i}" }))} diff --git a/examples/testbed.rs b/examples/testbed.rs index 1d03ac7b4..240c75a76 100644 --- a/examples/testbed.rs +++ b/examples/testbed.rs @@ -2,7 +2,7 @@ use std::cell::Cell; use dioxus::prelude::*; use dioxus_core::{ - nodes::{NodeKey, VElement, VText}, + nodes::{VElement, VText}, RealDomNode, }; @@ -23,6 +23,9 @@ const Example: FC<()> = |cx| { Fragment { Fragment { "h1" + div { + + } } "h2" } diff --git a/notes/ARCHITECTURE.md b/notes/ARCHITECTURE.md index 46727de5a..ab06d8cd8 100644 --- a/notes/ARCHITECTURE.md +++ b/notes/ARCHITECTURE.md @@ -50,3 +50,24 @@ div { ## Subtree memoization The rsx! macro needs to be *really* smart. If it detects that no dynamics are pumped into the macro, then it opts to use the "const" flavors of the element build functions we know and love. This has to be done at build time rather than runtime since components may return basically anything. Using the const flavor enables is_static which encourages Dioxus to do a ptr compare instead of a value compare to short circuit through the diffing. Due to const folding in Rust, entire subtrees can be ruled out at compile time. + +It would be interesting to fix the issue of dynamic subtrees by hashing each structure (or just the const structures) or the macro call itself. That way, each call gets its own identifier and we can make sure that two unique structures have different IDs and aren't just opaque to dioxus. + +```rust +let s1 = LazyNodes::new("1", move |_| { + if rand() { + f.element() + } else { + f.element() + } +}); +let s2 = LazyNodes::new("1", move |f| { + if rand() { + f.element() + } else { + f.element() + } +}); +// produces the same ID with different structures +// perhaps just make this +``` diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 72e239514..e24d8c98b 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -188,7 +188,7 @@ impl ToTokens for Component { #name, #builder, #key_token, - #children + __cx.bump().alloc(#children) ) }) } diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index a08fa129b..222d6fb18 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -23,37 +23,18 @@ pub struct Element { impl ToTokens for Element { fn to_tokens(&self, tokens: &mut TokenStream2) { let name = &self.name; + let attr = &self.attributes; + let childs = &self.children; + let listeners = &self.listeners; tokens.append_all(quote! { - __cx.element(dioxus_elements::#name) - }); - - // By gating these methods, we can keep the output of `cargo expand` readable. - // We also prevent the issue where zero-sized arrays fail to have propery type inference. - - if self.attributes.len() > 0 { - let attr = &self.attributes; - tokens.append_all(quote! { - .attributes([ #(#attr),* ]) - }) - } - - if self.children.len() > 0 { - let childs = &self.children; - tokens.append_all(quote! { - .children([ #(#childs),* ]) - }); - } - - if self.listeners.len() > 0 { - let listeners = &self.listeners; - tokens.append_all(quote! { - .listeners([ #(#listeners),* ]) - }); - } - - tokens.append_all(quote! { - .finish() + __cx.element( + dioxus_elements::#name, + __cx.bump().alloc([ #(#listeners),* ]), + __cx.bump().alloc([ #(#attr),* ]), + __cx.bump().alloc([ #(#childs),* ]), + None, + ) }); } } @@ -83,7 +64,13 @@ impl Parse for Element { } if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { - parse_element_body(&content, &mut attributes, &mut listeners, &mut key)?; + parse_element_body( + &content, + &mut attributes, + &mut listeners, + &mut key, + name.clone(), + )?; } else { children.push(content.parse::()?); } @@ -110,6 +97,7 @@ impl Parse for Element { /// Parse a VElement's Attributes /// ======================================= struct ElementAttr { + element_name: Ident, name: Ident, value: AttrType, namespace: Option, @@ -130,6 +118,7 @@ fn parse_element_body( attrs: &mut Vec, listeners: &mut Vec, key: &mut Option, + element_name: Ident, ) -> Result<()> { let mut name = Ident::parse_any(stream)?; let name_str = name.to_string(); @@ -159,6 +148,7 @@ fn parse_element_body( name, value: ty, namespace: None, + element_name: element_name.clone(), }); return Ok(()); } @@ -186,6 +176,7 @@ fn parse_element_body( name, value: ty, namespace: Some("style".to_string()), + element_name: element_name.clone(), }); } @@ -227,13 +218,15 @@ fn parse_element_body( name, value: ty, namespace: None, + element_name, }); Ok(()) } impl ToTokens for ElementAttr { fn to_tokens(&self, tokens: &mut TokenStream2) { - let name = self.name.to_string(); + let el_name = &self.element_name; + let name_str = self.name.to_string(); let nameident = &self.name; let namespace = match &self.namespace { @@ -243,12 +236,22 @@ impl ToTokens for ElementAttr { match &self.value { AttrType::BumpText(value) => tokens.append_all(quote! { - __cx.attr(#name, format_args_f!(#value), #namespace) + dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value)) }), - + // __cx.attr(#name, format_args_f!(#value), #namespace, false) + // + // AttrType::BumpText(value) => tokens.append_all(quote! { + // __cx.attr(#name, format_args_f!(#value), #namespace, false) + // }), AttrType::FieldTokens(exp) => tokens.append_all(quote! { - __cx.attr(#name, #exp, #namespace) + dioxus_elements::#el_name.#nameident(__cx, #exp) }), + // __cx.attr(#name_str, #exp, #namespace, false) + + // AttrType::FieldTokens(exp) => tokens.append_all(quote! { + // dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value)) + // __cx.attr(#name_str, #exp, #namespace, false) + // }), // todo: move event handlers on to the elements or onto the nodefactory AttrType::Event(event) => tokens.append_all(quote! { diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs index 2c56acad9..6ad337238 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/core-macro/src/rsx/mod.rs @@ -97,12 +97,16 @@ impl ToTokens for RsxRender { // The `in cx` pattern allows directly rendering Some(ident) => out_tokens.append_all(quote! { #ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ + use dioxus_elements::GlobalAttributes; + #inner })) }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ + use dioxus_elements::GlobalAttributes; + #inner }) }), diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 2b58ae251..34a811347 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index eaa9ba214..c662c6732 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -41,5 +41,5 @@ smallvec = "1.6.1" [features] -default = [] +default = ["serialize"] serialize = ["slotmap/serde", "serde"] diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 3b74d0472..b12a8f69c 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -1,20 +1,20 @@ fn main() {} -use dioxus::*; -use dioxus_core as dioxus; -use dioxus_core::prelude::*; +// use dioxus::*; +// use dioxus_core as dioxus; +// use dioxus_core::prelude::*; -static Example: FC<()> = |cx| { - let list = (0..10).map(|f| LazyNodes::new(move |f| todo!())); +// static Example: FC<()> = |cx| { +// let list = (0..10).map(|f| LazyNodes::new(move |f| todo!())); - cx.render(LazyNodes::new(move |cx| { - let bump = cx.bump(); - cx.raw_element("div") - .children([ - cx.text(format_args!("hello")), - cx.text(format_args!("hello")), - cx.fragment_from_iter(list), - ]) - .finish() - })) -}; +// cx.render(LazyNodes::new(move |cx| { +// let bump = cx.bump(); +// cx.raw_element("div") +// .children([ +// cx.text(format_args!("hello")), +// cx.text(format_args!("hello")), +// cx.fragment_from_iter(list), +// ]) +// .finish() +// })) +// }; diff --git a/packages/core/examples/async.rs b/packages/core/examples/async.rs index a6a0749a7..9b4509dc9 100644 --- a/packages/core/examples/async.rs +++ b/packages/core/examples/async.rs @@ -1,13 +1,13 @@ -use std::pin::Pin; + use dioxus_core::prelude::*; -use std::future::Future; + fn main() {} const App: FC<()> = |cx| { // create a new future - let mut fut = cx.use_hook( + let _fut = cx.use_hook( || { // async { loop {} } @@ -22,3 +22,15 @@ const App: FC<()> = |cx| { todo!() }; + +const Task: FC<()> = |cx| { + // + + let _s = cx.use_task(|| async { "hello world".to_string() }); + + todo!() +}; + +fn use_mut(_cx: Context

, _f: impl FnOnce() -> T) -> &mut T { + todo!() +} diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index f160e37c5..d0b20241c 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -7,7 +7,7 @@ fn main() {} use dioxus_core::prelude::*; -use dioxus_core::*; + use std::rc::Rc; struct AppProps { @@ -61,7 +61,7 @@ fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> VNode { } impl PartialEq for ChildProps { - fn eq(&self, other: &Self) -> bool { + fn eq(&self, _other: &Self) -> bool { false } } diff --git a/packages/core/examples/fragment.rs b/packages/core/examples/fragment.rs index 479a2607d..3d4b97b8b 100644 --- a/packages/core/examples/fragment.rs +++ b/packages/core/examples/fragment.rs @@ -1,4 +1,4 @@ -use dioxus_core::prelude::*; + fn main() { diff --git a/packages/core/examples/html.rs b/packages/core/examples/html.rs index 67c56e856..96ce94986 100644 --- a/packages/core/examples/html.rs +++ b/packages/core/examples/html.rs @@ -68,7 +68,7 @@ impl BasicElement for div { todo!() } - fn new(factory: Factory) -> Self { + fn new(_factory: Factory) -> Self { todo!() } } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index c8e519413..82415d45f 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -59,8 +59,8 @@ impl SharedArena { pub fn with_scope<'b, O: 'static>( &'b self, - id: ScopeIdx, - f: impl FnOnce(&'b mut Scope) -> O, + _id: ScopeIdx, + _f: impl FnOnce(&'b mut Scope) -> O, ) -> Result { todo!() } @@ -69,8 +69,8 @@ impl SharedArena { // this is useful for merging lifetimes pub fn with_scope_vnode<'b>( &self, - id: ScopeIdx, - f: impl FnOnce(&mut Scope) -> &VNode<'b>, + _id: ScopeIdx, + _f: impl FnOnce(&mut Scope) -> &VNode<'b>, ) -> Result<&VNode<'b>> { todo!() } diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index ade95d7db..ab28508af 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -1,20 +1,7 @@ -use crate::hooklist::HookList; -use crate::{arena::SharedArena, innerlude::*}; -use appendlist::AppendList; +use crate::innerlude::*; use bumpalo::Bump; -use slotmap::DefaultKey; -use slotmap::SlotMap; -use std::marker::PhantomData; -use std::{ - any::{Any, TypeId}, - cell::{Cell, RefCell}, - collections::{HashMap, HashSet, VecDeque}, - fmt::Debug, - future::Future, - ops::Deref, - pin::Pin, - rc::{Rc, Weak}, -}; + +use std::cell::RefCell; pub struct ActiveFrame { // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken pub generation: RefCell, @@ -33,11 +20,11 @@ impl ActiveFrame { Self::from_frames( BumpFrame { bump: Bump::new(), - head_node: VNode::static_text(""), + head_node: NodeFactory::static_text(""), }, BumpFrame { bump: Bump::new(), - head_node: VNode::static_text(""), + head_node: NodeFactory::static_text(""), }, ) } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index f5bc7ea79..c23e3d631 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,7 +5,7 @@ //! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder //! that ensures compile-time required and optional fields on cx. -use crate::innerlude::FC; +use crate::innerlude::{Context, LazyNodes, VNode, FC}; pub trait Properties: Sized { type Builder; @@ -50,7 +50,6 @@ pub fn fc_to_builder(_: FC) -> T::Builder { /// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. /// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash. -use crate::prelude::*; #[allow(non_upper_case_globals, non_snake_case)] pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> { cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children()))) diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 6396d5e02..fdc35809f 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -1,15 +1,12 @@ -use crate::hooklist::HookList; -use crate::{arena::SharedArena, innerlude::*}; -use appendlist::AppendList; -use bumpalo::Bump; -use slotmap::DefaultKey; -use slotmap::SlotMap; +use crate::innerlude::*; + +use futures_util::FutureExt; + use std::marker::PhantomData; + use std::{ - any::{Any, TypeId}, - cell::{Cell, RefCell}, - collections::{HashMap, HashSet, VecDeque}, - fmt::Debug, + any::TypeId, + cell::RefCell, future::Future, ops::Deref, pin::Pin, @@ -39,9 +36,8 @@ use std::{ pub struct Context<'src, T> { pub props: &'src T, pub scope: &'src Scope, - pub tasks: &'src RefCell>, } -pub type PinnedTask = Pin>>; +// pub type PinnedTask = Pin>>; impl<'src, T> Copy for Context<'src, T> {} impl<'src, T> Clone for Context<'src, T> { @@ -49,7 +45,6 @@ impl<'src, T> Clone for Context<'src, T> { Self { props: self.props, scope: self.scope, - tasks: self.tasks, } } } @@ -134,7 +129,7 @@ impl<'src, P> Context<'src, P> { self, initializer: Init, runner: Run, - cleanup: Cleanup, + _cleanup: Cleanup, ) -> Output where State: 'static, @@ -263,31 +258,13 @@ Any function prefixed with "use" should not be called conditionally. ) } - pub fn suspend VNode<'src> + 'src>( - &'src self, - fut: &'src mut Pin + 'static>>, - callback: Fut, - ) -> VNode<'src> { - use futures_util::FutureExt; - match fut.now_or_never() { - Some(out) => { - let suspended_cx = SuspendedContext {}; - let nodes = callback(suspended_cx, out); - return nodes; - } - None => { - // we need to register this task - VNode::Suspended { - real: Cell::new(RealDomNode::empty()), - } - } - } - } - /// `submit_task` will submit the future to be polled. /// /// This is useful when you have some async task that needs to be progressed. /// + /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to + /// happen must occur within the future or scheduled for after the future completes (through schedule_update ) + /// /// ## Explanation /// Dioxus will step its internal event loop if the future returns if the future completes while waiting. /// @@ -298,11 +275,102 @@ Any function prefixed with "use" should not be called conditionally. /// /// /// - pub fn submit_task(&self, task: &'src mut PinnedTask) -> TaskHandle { - self.tasks.borrow_mut().push(task); + pub fn submit_task(&self, task: FiberTask) -> TaskHandle { + let r = (self.scope.task_submitter)(task); + // self.scope.submit_task(task); + // let r = task.then(|f| async { + // // + // }); + // self.use_hook(|| Box::new(r), |_| {}, |_| {}); + // *task = task.then(|f| async { + // // + // t + // // () + // }); + // let new_fut = task.then(|f| async { + // // + // () + // }); + // self.tasks.borrow_mut().push(new_fut); TaskHandle { _p: PhantomData {} } } + + /// Awaits the given task, forcing the component to re-render when the value is ready. + /// + /// + pub fn use_task(&self, task_initializer: Init) -> &mut Option + where + Out: 'static, + Fut: Future, + Fut: 'static, + Init: FnOnce() -> Fut + 'src, + { + struct TaskHook { + task_dump: Rc>>, + value: Option, + } + + // whenever the task is complete, save it into th + self.use_hook( + move || { + let task_fut = task_initializer(); + + let task_dump = Rc::new(RefCell::new(None)); + + let slot = task_dump.clone(); + let update = self.schedule_update(); + let originator = self.scope.arena_idx.clone(); + + self.submit_task(Box::pin(task_fut.then(move |output| async move { + *slot.as_ref().borrow_mut() = Some(output); + update(); + EventTrigger { + event: VirtualEvent::FiberEvent, + originator, + priority: EventPriority::Low, + real_node_id: None, + } + }))); + + TaskHook { + task_dump, + value: None, + } + }, + |hook| { + if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() { + hook.value = Some(val); + } + &mut hook.value + }, + |_| {}, + ) + } + + /// Asynchronously render new nodes once the given future has completed. + /// + /// # Easda + /// + /// + /// + /// + /// # Example + /// + /// + pub fn use_suspense( + &'src self, + _task_initializer: impl FnOnce() -> Fut, + _callback: impl FnOnce(SuspendedContext, Out) -> VNode<'src> + 'src, + ) -> VNode<'src> + where + Out: 'src, + Fut: Future, + { + // self.use_hook(|| , runner, cleanup) + + todo!() + } } pub struct TaskHandle<'src> { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index ee39f62bf..560cef5a9 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -49,7 +49,7 @@ //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue}; -use fxhash::{FxHashMap, FxHashSet}; +use fxhash::FxHashSet; use std::any::Any; @@ -107,18 +107,6 @@ pub trait RealDom<'a> { fn raw_node_as_any_mut(&self) -> &mut dyn Any; } -/// 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. -/// -/// By re-entering via NodeDiff, we can connect disparate edits together into a single EditList. This batching of edits -/// leads to very fast re-renders (all done in a single animation frame). -/// -/// It also means diffing two trees is only ever complex as diffing a single smaller tree, and then re-entering at a -/// 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 were both updated via -/// subscriptions and props changes. pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> { pub dom: &'real mut Dom, pub components: &'bump SharedArena, @@ -158,50 +146,48 @@ where // // each function call assumes the stack is fresh (empty). pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { - match (old_node, new_node) { + match (&old_node.kind, &new_node.kind) { // Handle the "sane" cases first. // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. - (VNode::Text(old), VNode::Text(new)) => { - if old.is_static { - log::debug!("encountered static text node: {:?}", old.text); - } - + (VNodeKind::Text(old), VNodeKind::Text(new)) => { + let root = old_node.dom_id.get(); if old.text != new.text { - self.dom.push(old.dom_id.get()); + self.dom.push(root); log::debug!("Text has changed {}, {}", old.text, new.text); self.dom.set_text(new.text); self.dom.pop(); } - new.dom_id.set(old.dom_id.get()); + new_node.dom_id.set(root); } - (VNode::Element(old), VNode::Element(new)) => { + (VNodeKind::Element(old), VNodeKind::Element(new)) => { // If the element type is completely different, the element needs to be re-rendered completely // This is an optimization React makes due to how users structure their code // // In Dioxus, this is less likely to occur unless through a fragment + let root = old_node.dom_id.get(); if new.tag_name != old.tag_name || new.namespace != old.namespace { - self.dom.push(old.dom_id.get()); + self.dom.push(root); let meta = self.create(new_node); self.dom.replace_with(meta.added_to_stack); self.dom.pop(); return; } - let oldid = old.dom_id.get(); - new.dom_id.set(oldid); + new_node.dom_id.set(root); // push it just in case - self.dom.push(oldid); + // TODO: remove this - it clogs up things and is inefficient + self.dom.push(root); self.diff_listeners(old.listeners, new.listeners); self.diff_attr(old.attributes, new.attributes, new.namespace); self.diff_children(old.children, new.children); self.dom.pop(); } - (VNode::Component(old), VNode::Component(new)) => { + (VNodeKind::Component(old), VNodeKind::Component(new)) => { log::warn!("diffing components? {:#?}", new.user_fc); if old.user_fc == new.user_fc { // Make sure we're dealing with the same component (by function pointer) @@ -257,7 +243,7 @@ where } } - (VNode::Fragment(old), VNode::Fragment(new)) => { + (VNodeKind::Fragment(old), VNodeKind::Fragment(new)) => { // This is the case where options or direct vnodes might be used. // In this case, it's faster to just skip ahead to their diff if old.children.len() == 1 && new.children.len() == 1 { @@ -278,15 +264,19 @@ where // We also walk the "real node" list to make sure all latent roots are claened up // This covers the case any time a fragment or component shows up with pretty much anything else ( - VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_), - VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_), + VNodeKind::Component(_) + | VNodeKind::Fragment(_) + | VNodeKind::Text(_) + | VNodeKind::Element(_), + VNodeKind::Component(_) + | VNodeKind::Fragment(_) + | VNodeKind::Text(_) + | VNodeKind::Element(_), ) => { // Choose the node to use as the placeholder for replacewith - let back_node = match old_node { + let back_node = match old_node.kind { // We special case these two types to avoid allocating the small-vecs - VNode::Element(_) | VNode::Text(_) => old_node - .get_mounted_id(&self.components) - .expect("Element and text always have a real node"), + VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(), _ => { let mut old_iter = RealChildIterator::new(old_node, &self.components); @@ -314,17 +304,17 @@ where } // TODO - (VNode::Suspended { .. }, _) => todo!(), - (_, VNode::Suspended { .. }) => todo!(), + (VNodeKind::Suspended { .. }, _) => todo!(), + (_, VNodeKind::Suspended { .. }) => todo!(), } } } // When we create new nodes, we need to propagate some information back up the call chain. // This gives the caller some information on how to handle things like insertins, appending, and subtree discarding. -struct CreateMeta { - is_static: bool, - added_to_stack: u32, +pub struct CreateMeta { + pub is_static: bool, + pub added_to_stack: u32, } impl CreateMeta { @@ -349,15 +339,15 @@ where // When this function returns, the new node is on top of the change list stack: // // [... node] - fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta { + pub fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta { log::warn!("Creating node! ... {:#?}", node); - match node { - VNode::Text(text) => { + match &node.kind { + VNodeKind::Text(text) => { let real_id = self.dom.create_text_node(text.text); - text.dom_id.set(real_id); + node.dom_id.set(real_id); CreateMeta::new(text.is_static, 1) } - VNode::Element(el) => { + VNodeKind::Element(el) => { // we have the potential to completely eliminate working on this node in the future(!) // // This can only be done if all of the elements properties (attrs, children, listeners, etc) are static @@ -366,14 +356,14 @@ where let mut is_static: bool = true; let VElement { - key, tag_name, listeners, attributes, children, namespace, - dom_id, - is_static: el_is_static, + static_attrs: _, + static_children: _, + static_listeners: _, } = el; let real_id = if let Some(namespace) = namespace { @@ -381,9 +371,10 @@ where } else { self.dom.create_element(tag_name, None) }; - dom_id.set(real_id); + node.dom_id.set(real_id); listeners.iter().enumerate().for_each(|(idx, listener)| { + log::info!("setting listener id to {:#?}", real_id); listener.mounted_node.set(real_id); self.dom .new_event_listener(listener.event, listener.scope, idx, real_id); @@ -408,9 +399,9 @@ where // // TODO move over // if children.len() == 1 { - // if let VNode::Text(text) = &children[0] { + // if let VNodeKind::Text(text) = &children[0].kind { // self.dom.set_text(text.text); - // return; + // return CreateMeta::new(is_static, 1); // } // } @@ -422,17 +413,17 @@ where self.dom.append_children(child_meta.added_to_stack); } - if is_static { - log::debug!("created a static node {:#?}", node); - } else { - log::debug!("created a dynamic node {:#?}", node); - } + // if is_static { + // log::debug!("created a static node {:#?}", node); + // } else { + // log::debug!("created a dynamic node {:#?}", node); + // } - el_is_static.set(is_static); + // el_is_static.set(is_static); CreateMeta::new(is_static, 1) } - VNode::Component(vcomponent) => { + VNodeKind::Component(vcomponent) => { log::debug!("Mounting a new component"); let caller = vcomponent.caller.clone(); @@ -493,7 +484,7 @@ where // Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators). // We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them. // Fragments will just put all their nodes onto the stack after creation - VNode::Fragment(frag) => { + VNodeKind::Fragment(frag) => { let mut nodes_added = 0; for child in frag.children.iter().rev() { // different types of nodes will generate different amounts on the stack @@ -508,9 +499,9 @@ where CreateMeta::new(false, nodes_added) } - VNode::Suspended { real } => { + VNodeKind::Suspended => { let id = self.dom.create_placeholder(); - real.set(id); + node.dom_id.set(id); CreateMeta::new(false, 1) } } @@ -570,7 +561,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // if any characteristics changed, remove and then re-add // if nothing changed, then just move on - let event_type = new_l.event; + let _event_type = new_l.event; for old_l in old { if new_l.event == old_l.event { @@ -612,7 +603,6 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { old: &'bump [Attribute<'bump>], new: &'bump [Attribute<'bump>], namespace: Option<&'bump str>, - // is_namespaced: bool, ) { // Do O(n^2) passes to add/update and remove attributes, since // there are almost always very few attributes. @@ -620,7 +610,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // The "fast" path is when the list of attributes name is identical and in the same order // With the Rsx and Html macros, this will almost always be the case 'outer: for new_attr in new { - if new_attr.is_volatile() { + if new_attr.is_volatile { // self.dom.commit_traversal(); self.dom .set_attribute(new_attr.name, new_attr.value, namespace); @@ -675,13 +665,13 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { if new.len() == 1 { match (&old.first(), &new[0]) { - (Some(VNode::Text(old_vtext)), VNode::Text(new_vtext)) - if old_vtext.text == new_vtext.text => - { - // Don't take this fast path... - } + // (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext)) + // if old_vtext.text == new_vtext.text => + // { + // // Don't take this fast path... + // } - // (_, VNode::Text(text)) => { + // (_, VNodeKind::Text(text)) => { // // self.dom.commit_traversal(); // log::debug!("using optimized text set"); // self.dom.set_text(text.text); @@ -701,15 +691,15 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { return; } - let new_is_keyed = new[0].key().is_some(); - let old_is_keyed = old[0].key().is_some(); + let new_is_keyed = new[0].key.is_some(); + let old_is_keyed = old[0].key.is_some(); debug_assert!( - new.iter().all(|n| n.key().is_some() == new_is_keyed), + new.iter().all(|n| n.key.is_some() == new_is_keyed), "all siblings must be keyed or all siblings must be non-keyed" ); debug_assert!( - old.iter().all(|o| o.key().is_some() == old_is_keyed), + old.iter().all(|o| o.key.is_some() == old_is_keyed), "all siblings must be keyed or all siblings must be non-keyed" ); @@ -754,7 +744,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| { keys.clear(); for child in children { - let key = child.key(); + let key = child.key; debug_assert!( key.is_some(), "if any sibling is keyed, all siblings must be keyed" @@ -797,7 +787,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { .iter() .rev() .zip(new[shared_prefix_count..].iter().rev()) - .take_while(|&(old, new)| old.key() == new.key()) + .take_while(|&(old, new)| old.key == new.key) .count(); let old_shared_suffix_start = old.len() - shared_suffix_count; @@ -833,8 +823,8 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // Upon exit, the change list stack is the same. fn diff_keyed_prefix( &self, - old: &'bump [VNode<'bump>], - new: &'bump [VNode<'bump>], + _old: &'bump [VNode<'bump>], + _new: &'bump [VNode<'bump>], ) -> KeyedPrefixResult { todo!() // self.dom.go_down(); @@ -922,11 +912,11 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // Upon exit from this function, it will be restored to that same state. fn diff_keyed_middle( &self, - old: &[VNode<'bump>], - mut new: &[VNode<'bump>], - shared_prefix_count: usize, - shared_suffix_count: usize, - old_shared_suffix_start: usize, + _old: &[VNode<'bump>], + _new: &[VNode<'bump>], + _shared_prefix_count: usize, + _shared_suffix_count: usize, + _old_shared_suffix_start: usize, ) { todo!() // // Should have already diffed the shared-key prefixes and suffixes. @@ -1150,9 +1140,9 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // When this function exits, the change list stack remains the same. fn diff_keyed_suffix( &self, - old: &[VNode<'bump>], - new: &[VNode<'bump>], - new_shared_suffix_start: usize, + _old: &[VNode<'bump>], + _new: &[VNode<'bump>], + _new_shared_suffix_start: usize, ) { todo!() // debug_assert_eq!(old.len(), new.len()); @@ -1190,7 +1180,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // [... parent child] // todo!() - for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() { + for (_i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() { // [... parent prev_child] // self.dom.go_to_sibling(i); // [... parent this_child] @@ -1259,7 +1249,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { pub fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) { // debug_assert!(self.dom.traversal_is_committed()); for child in old { - if let VNode::Component(vcomp) = child { + if let VNodeKind::Component(_vcomp) = child.kind { // dom // .create_text_node("placeholder for vcomponent"); @@ -1328,23 +1318,23 @@ impl<'a> Iterator for RealChildIterator<'a> { while returned_node.is_none() { if let Some((count, node)) = self.stack.last_mut() { - match node { + match &node.kind { // We can only exit our looping when we get "real" nodes // This includes fragments and components when they're empty (have a single root) - VNode::Element(_) | VNode::Text(_) => { + VNodeKind::Element(_) | VNodeKind::Text(_) => { // We've recursed INTO an element/text // We need to recurse *out* of it and move forward to the next should_pop = true; - returned_node = node.get_mounted_id(&self.scopes); + returned_node = Some(node.dom_id.get()); } // If we get a fragment we push the next child - VNode::Fragment(frag) => { + VNodeKind::Fragment(frag) => { let subcount = *count as usize; if frag.children.len() == 0 { should_pop = true; - returned_node = node.get_mounted_id(&self.scopes); + returned_node = Some(node.dom_id.get()); } if subcount >= frag.children.len() { @@ -1355,11 +1345,11 @@ impl<'a> Iterator for RealChildIterator<'a> { } // Immediately abort suspended nodes - can't do anything with them yet - // VNode::Suspended => should_pop = true, - VNode::Suspended { real } => todo!(), + // VNodeKind::Suspended => should_pop = true, + VNodeKind::Suspended => todo!(), // For components, we load their root and push them onto the stack - VNode::Component(sc) => { + VNodeKind::Component(sc) => { let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap(); // Simply swap the current node on the stack with the root of the component @@ -1388,3 +1378,13 @@ impl<'a> Iterator for RealChildIterator<'a> { returned_node } } + +fn compare_strs(a: &str, b: &str) -> bool { + // Check by pointer, optimizing for static strs + if !std::ptr::eq(a, b) { + // If the pointers are different then check by value + a == b + } else { + true + } +} diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index ecf1ee58a..1fb836e12 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -4,7 +4,7 @@ //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy. -use std::{cell::Cell, ops::Deref, rc::Rc}; + use crate::innerlude::{RealDomNode, ScopeIdx}; @@ -125,7 +125,6 @@ pub mod on { use std::{fmt::Debug, ops::Deref, rc::Rc}; use crate::{ - innerlude::ElementBuilder, innerlude::NodeFactory, innerlude::{Attribute, Listener, RealDomNode, VNode}, }; diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 5af0ddbdb..08c5dec24 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(non_snake_case, dead_code, unused_must_use, unreachable_code)] //! Dioxus Core //! ---------- //! @@ -8,25 +9,20 @@ //! //! -#[cfg(feature = "serialize")] -pub mod serialize; +pub use crate::innerlude::{ + format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory, + Properties, RealDom, RealDomNode, ScopeIdx, VNode, VNodeKind, VirtualDom, VirtualEvent, FC, +}; -pub mod arena; -pub mod bumpframe; -pub mod component; -pub mod context; -pub mod diff; -pub mod error; -pub mod events; -pub mod hooklist; -pub mod nodebuilder; -pub mod nodes; -pub mod scope; -pub mod signals; -pub mod styles; -pub mod tasks; -pub mod util; -pub mod virtual_dom; +pub mod prelude { + pub use crate::component::{fc_to_builder, Fragment, Properties}; + pub use crate::context::Context; + pub use crate::innerlude::DioxusElement; + pub use crate::innerlude::{LazyNodes, NodeFactory, FC}; + pub use crate::nodes::VNode; + pub use crate::VirtualDom; + pub use dioxus_core_macro::{format_args_f, html, rsx, Props}; +} // types used internally that are important pub(crate) mod innerlude { @@ -36,7 +32,6 @@ pub(crate) mod innerlude { pub use crate::diff::*; pub use crate::error::*; pub use crate::events::*; - pub use crate::nodebuilder::*; pub use crate::nodes::*; pub use crate::scope::*; pub use crate::serialize::*; @@ -46,28 +41,26 @@ pub(crate) mod innerlude { pub type FC

= fn(Context

) -> VNode; - // Re-export the FC macro - pub use crate::nodebuilder as builder; - pub use dioxus_core_macro::{html, rsx}; -} - -pub use crate::{ - innerlude::{ - DioxusElement, DomEdit, LazyNodes, NodeFactory, RealDom, RealDomNode, ScopeIdx, FC, - }, - virtual_dom::VirtualDom, -}; - -pub mod prelude { - pub use crate::component::{fc_to_builder, Fragment, Properties}; - pub use crate::context::Context; - pub use crate::innerlude::{LazyNodes, NodeFactory, FC}; - pub use crate::nodebuilder::DioxusElement; - pub use crate::nodes::VNode; - pub use crate::VirtualDom; - pub use dioxus_core_macro::{format_args_f, html, rsx, Props}; + pub use dioxus_core_macro::{format_args_f, html, rsx}; } pub mod exports { // export important things here } + +pub mod arena; +pub mod bumpframe; +pub mod component; +pub mod context; +pub mod diff; +pub mod error; +pub mod events; +pub mod hooklist; +pub mod nodes; +pub mod scope; +#[cfg(feature = "serialize")] +pub mod serialize; +pub mod signals; +pub mod tasks; +pub mod util; +pub mod virtual_dom; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9aaffec1a..142167f2d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,225 +4,70 @@ //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick. use crate::{ - arena::SharedArena, events::VirtualEvent, - innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC}, - nodebuilder::NodeFactory, + innerlude::{Context, Properties, RealDomNode, Scope, ScopeIdx, FC}, }; -use appendlist::AppendList; -use bumpalo::Bump; use std::{ cell::{Cell, RefCell}, fmt::{Arguments, Debug, Formatter}, + marker::PhantomData, rc::Rc, }; +pub struct VNode<'src> { + pub kind: VNodeKind<'src>, + pub dom_id: Cell, + pub key: Option<&'src str>, +} + /// Tools for the base unit of the virtual dom - the VNode /// VNodes are intended to be quickly-allocated, lightweight enum values. /// /// Components will be generating a lot of these very quickly, so we want to /// limit the amount of heap allocations / overly large enum sizes. -pub enum VNode<'src> { - /// An element node (node type `ELEMENT_NODE`). - Element(&'src VElement<'src>), - - /// A text node (node type `TEXT_NODE`). +pub enum VNodeKind<'src> { Text(VText<'src>), - - /// A fragment is a list of elements that might have a dynamic order. - /// Normally, children will have a fixed order. However, Fragments allow a dynamic order and must be diffed differently. - /// - /// Fragments don't have a single mount into the dom, so their position is characterized by the head and tail nodes. - /// - /// Fragments may have children and keys - Fragment(&'src VFragment<'src>), - - /// A "suspended component" - /// This is a masqeurade over an underlying future that needs to complete - /// When the future is completed, the VNode will then trigger a render and the `real` field gets populated - Suspended { real: Cell }, - - /// A User-defined componen node (node type COMPONENT_NODE) + Element(&'src VElement<'src>), + Fragment(VFragment<'src>), Component(&'src VComponent<'src>), + Suspended, } -// it's okay to clone because vnodes are just references to places into the bump -impl<'a> Clone for VNode<'a> { - fn clone(&self) -> Self { - match self { - VNode::Element(element) => VNode::Element(element), - VNode::Text(old) => VNode::Text(old.clone()), - VNode::Fragment(fragment) => VNode::Fragment(fragment), - VNode::Component(component) => VNode::Component(component), - VNode::Suspended { real } => VNode::Suspended { real: real.clone() }, - } - } -} - -impl<'old, 'new> VNode<'old> { - // performs a somewhat costly clone of this vnode into another bump - // this is used when you want to drag nodes from an old frame into a new frame - // There is no way to safely drag listeners over (no way to clone a closure) - // - // This method will only be called if a component was once a real node and then becomes suspended - fn deep_clone_to_new_bump(&self, new: &'new Bump) -> VNode<'new> { - match self { - VNode::Element(el) => { - let new_el: VElement<'new> = VElement { - key: NodeKey::NONE, - // key: el.key.clone(), - tag_name: el.tag_name, - // wipe listeners on deep clone, there's no way to know what other bump material they might be referencing (nodes, etc) - listeners: &[], - attributes: { - let attr_vec = bumpalo::collections::Vec::new_in(new); - attr_vec.into_bump_slice() - }, - children: { - let attr_vec = bumpalo::collections::Vec::new_in(new); - attr_vec.into_bump_slice() - }, - namespace: el.namespace.clone(), - dom_id: el.dom_id.clone(), - is_static: el.is_static.clone(), - }; - - VNode::Element(new.alloc_with(move || new_el)) - } - VNode::Text(_) => todo!(), - VNode::Fragment(_) => todo!(), - VNode::Suspended { real } => todo!(), - VNode::Component(_) => todo!(), - } - } -} - -impl<'a> VNode<'a> { - /// Low-level constructor for making a new `Node` of type element with given - /// parts. - /// - /// This is primarily intended for JSX and templating proc-macros to compile - /// down into. If you are building nodes by-hand, prefer using the - /// `dodrio::builder::*` APIs. - #[inline] - pub fn element( - bump: &'a Bump, - key: NodeKey<'a>, - tag_name: &'static str, - listeners: &'a [Listener<'a>], - attributes: &'a [Attribute<'a>], - children: &'a [VNode<'a>], - namespace: Option<&'static str>, - ) -> VNode<'a> { - let element = bump.alloc_with(|| VElement { - key, - tag_name, - listeners, - attributes, - children, - namespace, - dom_id: Cell::new(RealDomNode::empty()), - is_static: Cell::new(false), - }); - VNode::Element(element) - } - - pub fn static_text(text: &'static str) -> VNode { - VNode::Text(VText { - text, - is_static: true, - dom_id: Cell::new(RealDomNode::empty()), - }) - } - /// Construct a new text node with the given text. - pub fn text(bump: &'a Bump, args: Arguments) -> VNode<'a> { - match args.as_str() { - Some(text) => VNode::static_text(text), - None => { - use bumpalo::core_alloc::fmt::Write; - let mut s = bumpalo::collections::String::new_in(bump); - s.write_fmt(args).unwrap(); - VNode::Text(VText { - text: s.into_bump_str(), - is_static: false, - dom_id: Cell::new(RealDomNode::empty()), - }) - } - } - } - - #[inline] - pub(crate) fn key(&self) -> NodeKey { - match &self { - VNode::Text { .. } => NodeKey::NONE, - VNode::Element(e) => e.key, - VNode::Fragment(frag) => frag.key, - VNode::Component(c) => c.key, - - // todo suspend should be allowed to have keys - VNode::Suspended { .. } => NodeKey::NONE, - } - } - - fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> { - todo!() - } - - pub fn is_real(&self) -> bool { - match self { - VNode::Element(_) => true, - VNode::Text(_) => true, - VNode::Fragment(_) => false, - VNode::Suspended { .. } => false, - VNode::Component(_) => false, - } - } - - pub fn get_mounted_id(&self, components: &SharedArena) -> Option { - match self { - VNode::Element(el) => Some(el.dom_id.get()), - VNode::Text(te) => Some(te.dom_id.get()), - VNode::Fragment(frag) => frag.void_root.get(), - VNode::Suspended { .. } => todo!(), - VNode::Component(el) => todo!(), - } - } -} - -impl Debug for VNode<'_> { - fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match self { - VNode::Element(el) => write!(s, "element, {}", el.tag_name), - VNode::Text(t) => write!(s, "text, {}", t.text), - VNode::Fragment(_) => write!(s, "fragment"), - VNode::Suspended { .. } => write!(s, "suspended"), - VNode::Component(_) => write!(s, "component"), - } - } -} - -#[derive(Clone)] pub struct VText<'src> { pub text: &'src str, pub is_static: bool, - pub dom_id: Cell, } -// ======================================================== -// VElement (div, h1, etc), attrs, keys, listener handle -// ======================================================== +pub struct VFragment<'src> { + pub children: &'src [VNode<'src>], + pub is_static: bool, +} -#[derive(Clone)] +pub trait DioxusElement { + const TAG_NAME: &'static str; + const NAME_SPACE: Option<&'static str>; + #[inline] + fn tag_name(&self) -> &'static str { + Self::TAG_NAME + } + #[inline] + fn namespace(&self) -> Option<&'static str> { + Self::NAME_SPACE + } +} pub struct VElement<'a> { - /// Elements have a tag name, zero or more attributes, and zero or more - pub key: NodeKey<'a>, + // tag is always static pub tag_name: &'static str, - pub listeners: &'a [Listener<'a>], - pub attributes: &'a [Attribute<'a>], - pub children: &'a [VNode<'a>], pub namespace: Option<&'static str>, - pub dom_id: Cell, - pub is_static: Cell, + + pub static_listeners: bool, + pub listeners: &'a [Listener<'a>], + + pub static_attrs: bool, + pub attributes: &'a [Attribute<'a>], + + pub static_children: bool, + pub children: &'a [VNode<'a>], } /// An attribute on a DOM node, such as `id="my-thing"` or @@ -232,175 +77,185 @@ pub struct Attribute<'a> { pub name: &'static str, pub value: &'a str, pub is_static: bool, - - /// If an attribute is "namespaced", then it belongs to a group - /// The most common namespace is the "style" namespace - // pub is_dynamic: bool, + pub is_volatile: bool, + // Doesn't exist in the html spec, mostly used to denote "style" tags - could be for any type of group pub namespace: Option<&'static str>, } -impl<'a> Attribute<'a> { - /// Get this attribute's name, such as `"id"` in `

`. - #[inline] - pub fn name(&self) -> &'a str { - self.name - } - - /// The attribute value, such as `"my-thing"` in `
`. - #[inline] - pub fn value(&self) -> &'a str { - self.value - } - - /// Certain attributes are considered "volatile" and can change via user - /// input that we can't see when diffing against the old virtual DOM. For - /// these attributes, we want to always re-set the attribute on the physical - /// DOM node, even if the old and new virtual DOM nodes have the same value. - #[inline] - pub(crate) fn is_volatile(&self) -> bool { - match self.name { - "value" | "checked" | "selected" => true, - _ => false, - } - } -} - -pub struct ListenerHandle { - pub event: &'static str, - pub scope: ScopeIdx, - pub id: usize, -} - /// An event listener. +/// IE onclick, onkeydown, etc pub struct Listener<'bump> { /// The type of event to listen for. pub(crate) event: &'static str, - - /// Which scope? - /// This might not actually be relevant pub scope: ScopeIdx, - pub mounted_node: &'bump Cell, - - /// The callback to invoke when the event happens. pub(crate) callback: &'bump dyn FnMut(VirtualEvent), } -/// The key for keyed children. -/// -/// Keys must be unique among siblings. -/// -/// If any sibling is keyed, then they all must be keyed. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct NodeKey<'a>(pub(crate) Option<&'a str>); - -impl<'a> Default for NodeKey<'a> { - fn default() -> NodeKey<'a> { - NodeKey::NONE - } -} -impl<'a> NodeKey<'a> { - /// The default, lack of a key. - pub const NONE: NodeKey<'a> = NodeKey(None); - - /// Is this key `NodeKey::NONE`? - #[inline] - pub fn is_none(&self) -> bool { - *self == Self::NONE - } - - /// Is this key not `NodeKey::NONE`? - #[inline] - pub fn is_some(&self) -> bool { - !self.is_none() - } - - /// Create a new `NodeKey`. - /// - /// `key` must not be `u32::MAX`. - #[inline] - pub fn new(key: &'a str) -> Self { - NodeKey(Some(key)) - } - - #[inline] - pub fn new_opt(key: Option<&'a str>) -> Self { - NodeKey(key) - } -} - -// ============================== -// Custom components -// ============================== - /// Virtual Components for custom user-defined components /// Only supports the functional syntax -pub type StableScopeAddres = Option; -pub type VCompAssociatedScope = Option; - pub struct VComponent<'src> { - pub key: NodeKey<'src>, - - // pub void_root: Cell>, - pub ass_scope: Cell, - - // todo: swap the RC out with - pub caller: Rc VNode>, - - pub children: &'src [VNode<'src>], - - pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>, - + pub ass_scope: Cell>, + pub(crate) caller: Rc VNode>, + pub(crate) children: &'src [VNode<'src>], + pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>, pub is_static: bool, // a pointer into the bump arena (given by the 'src lifetime) - // raw_props: Box, - raw_props: *const (), + pub(crate) raw_props: *const (), // a pointer to the raw fn typ - pub user_fc: *const (), + pub(crate) user_fc: *const (), } -impl<'a> VComponent<'a> { - /// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl) - /// If it is set to true, then this method will be called which implements automatic memoization. - /// - /// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false" - pub fn new( - cx: &NodeFactory<'a>, +/// This struct provides an ergonomic API to quickly build VNodes. +/// +/// NodeFactory is used to build VNodes in the component's memory space. +/// This struct adds metadata to the final VNode about listeners, attributes, and children +#[derive(Copy, Clone)] +pub struct NodeFactory<'a> { + pub scope_ref: &'a Scope, + pub listener_id: &'a Cell, +} + +impl<'a> NodeFactory<'a> { + #[inline] + pub fn bump(&self) -> &'a bumpalo::Bump { + &self.scope_ref.cur_frame().bump + } + + pub const fn const_text(&self, text: &'static str) -> VNodeKind<'static> { + VNodeKind::Text(VText { + is_static: true, + text, + }) + } + + pub const fn const_fragment(&self, children: &'static [VNode<'static>]) -> VNodeKind<'static> { + VNodeKind::Fragment(VFragment { + children, + is_static: true, + }) + } + + pub fn static_text(text: &'static str) -> VNode { + VNode { + dom_id: RealDomNode::empty_cell(), + key: None, + kind: VNodeKind::Text(VText { + text, + is_static: true, + }), + } + } + + pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) { + match args.as_str() { + Some(static_str) => (static_str, true), + None => { + use bumpalo::core_alloc::fmt::Write; + let mut s = bumpalo::collections::String::new_in(self.bump()); + s.write_fmt(args).unwrap(); + (s.into_bump_str(), false) + } + } + } + + /// Create some text that's allocated along with the other vnodes + pub fn text(&self, args: Arguments) -> VNode<'a> { + let (text, is_static) = self.raw_text(args); + VNode { + dom_id: RealDomNode::empty_cell(), + key: None, + kind: VNodeKind::Text(VText { text, is_static }), + } + } + + pub fn raw_element( + &self, + _tag: &'static str, + _listeners: &[Listener], + _attributes: &[Attribute], + _children: &'a [VNode<'a>], + ) -> VNode<'a> { + todo!() + } + + pub fn element( + &self, + el: impl DioxusElement, + listeners: &'a [Listener<'a>], + attributes: &'a [Attribute<'a>], + children: &'a [VNode<'a>], + key: Option<&'a str>, + ) -> VNode<'a> { + let mut queue = self.scope_ref.listeners.borrow_mut(); + for listener in listeners { + let mounted = listener.mounted_node as *const _ as *mut _; + let callback = listener.callback as *const _ as *mut _; + queue.push((mounted, callback)) + } + + VNode { + dom_id: RealDomNode::empty_cell(), + key, + kind: VNodeKind::Element(self.bump().alloc(VElement { + tag_name: el.tag_name(), + namespace: el.namespace(), + static_listeners: false, + listeners, + static_attrs: false, + attributes, + static_children: false, + children, + })), + } + } + + pub fn suspended() -> VNode<'static> { + VNode { + dom_id: RealDomNode::empty_cell(), + key: None, + kind: VNodeKind::Suspended, + } + } + + pub fn attr( + &self, + name: &'static str, + val: Arguments, + namespace: Option<&'static str>, + is_volatile: bool, + ) -> Attribute<'a> { + let (value, is_static) = self.raw_text(val); + Attribute { + name, + value, + is_static, + namespace, + is_volatile, + } + } + + pub fn virtual_child

( + &self, component: FC

, props: P, - key: Option<&'a str>, + key: Option<&'a str>, // key: NodeKey<'a>, children: &'a [VNode<'a>], - ) -> Self { - let bump = cx.bump(); - let user_fc = component as *const (); - - let props = bump.alloc(props); + ) -> VNode<'a> + where + P: Properties + 'a, + { + // We don't want the fat part of the fat pointer + // This function does static dispatch so we don't need any VTable stuff + let props = self.bump().alloc(props); let raw_props = props as *const P as *const (); - let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| { + let user_fc = component as *const (); + + let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| { move |other: &VComponent| { - // Safety: - // ------ - // - // Invariants: - // - Component function pointers are the same - // - Generic properties on the same function pointer are the same - // - Lifetime of P borrows from its parent - // - The parent scope still exists when method is called - // - Casting from T to *const () is portable - // - Casting raw props to P can only happen when P is static - // - // Explanation: - // We are guaranteed that the props will be of the same type because - // there is no way to create a VComponent other than this `new` method. - // - // Therefore, if the render functions are identical (by address), then so will be - // props type paramter (because it is the same render function). Therefore, we can be - // sure that it is safe to interperet the previous props raw pointer as the same props - // type. From there, we can call the props' "memoize" method to see if we can - // avoid re-rendering the component. if user_fc == other.user_fc { let real_other = unsafe { &*(other.raw_props as *const _ as *const P) }; let props_memoized = unsafe { props.memoize(&real_other) }; @@ -414,81 +269,185 @@ impl<'a> VComponent<'a> { } })); - let key = match key { - Some(key) => NodeKey::new(key), - None => NodeKey(None), - }; - - let caller = create_component_caller(component, raw_props); - - // If the component does not have children, has no props (we can't memoize props), and has no no key, then we don't - // need to bother diffing it in the future - // - // This is more of an optimization to prevent unnecessary descending through the tree during diffing, rather than - // actually speeding up the diff process itself - let is_static = children.len() == 0 && P::IS_STATIC && key.is_none(); - - Self { - user_fc, - comparator, - raw_props, - children, - ass_scope: Cell::new(None), + VNode { key, - caller, - is_static, - // void_root: Cell::new(None), + dom_id: Cell::new(RealDomNode::empty()), + kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent { + user_fc, + comparator, + raw_props, + children, + caller: NodeFactory::create_component_caller(component, raw_props), + is_static: children.len() == 0 && P::IS_STATIC && key.is_none(), + ass_scope: Cell::new(None), + })), + } + } + + pub fn create_component_caller<'g, P: 'g>( + component: FC

, + raw_props: *const (), + ) -> Rc Fn(&'r Scope) -> VNode<'r>> { + type Captured<'a> = Rc Fn(&'r Scope) -> VNode<'r> + 'a>; + let caller: Captured = Rc::new(move |scp: &Scope| -> VNode { + // cast back into the right lifetime + let safe_props: &'_ P = unsafe { &*(raw_props as *const P) }; + let cx: Context

= Context { + props: safe_props, + scope: scp, + }; + + let res = component(cx); + + let g2 = unsafe { std::mem::transmute(res) }; + + g2 + }); + unsafe { std::mem::transmute::<_, Captured<'static>>(caller) } + } + + pub fn fragment_from_iter( + self, + node_iter: impl IntoIterator>, + ) -> VNode<'a> { + let mut nodes = bumpalo::collections::Vec::new_in(self.bump()); + // TODO throw an error if there are nodes without keys + for node in node_iter.into_iter() { + nodes.push(node.into_vnode(self)); + } + VNode { + dom_id: RealDomNode::empty_cell(), + key: None, + kind: VNodeKind::Fragment(VFragment { + children: nodes.into_bump_slice(), + is_static: false, + }), } } } -type Captured<'a> = Rc Fn(&'r Scope) -> VNode<'r> + 'a>; - -pub fn create_component_caller<'a, P: 'a>( - user_component: FC

, - raw_props: *const (), -) -> Rc Fn(&'r Scope) -> VNode<'r>> { - let g: Captured = Rc::new(move |scp: &Scope| -> VNode { - // cast back into the right lifetime - let safe_props: &'_ P = unsafe { &*(raw_props as *const P) }; - let tasks = RefCell::new(Vec::new()); - let cx: Context

= Context { - props: safe_props, - scope: scp, - tasks: &tasks, - }; - - let g = user_component(cx); - - for task in tasks.borrow_mut().drain(..) { - scp.submit_task(task); - } - - let g2 = unsafe { std::mem::transmute(g) }; - - g2 - }); - let r: Captured<'static> = unsafe { std::mem::transmute(g) }; - r +impl<'a> IntoIterator for VNode<'a> { + type Item = VNode<'a>; + type IntoIter = std::iter::Once; + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self) + } +} +impl<'a> IntoVNode<'a> for VNode<'a> { + fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { + self + } } -pub struct VFragment<'src> { - pub key: NodeKey<'src>, - pub children: &'src [VNode<'src>], - pub void_root: Cell>, +impl<'a> IntoVNode<'a> for &VNode<'a> { + fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { + self.clone() + } } -impl<'a> VFragment<'a> { - pub fn new(key: Option<&'a str>, children: &'a [VNode<'a>]) -> Self { - let key = match key { - Some(key) => NodeKey::new(key), - None => NodeKey(None), - }; +pub trait IntoVNode<'a> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; +} +// Wrap the the node-builder closure in a concrete type. +// --- +// This is a bit of a hack to implement the IntoVNode trait for closure types. +pub struct LazyNodes<'a, G> +where + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +{ + inner: G, + _p: PhantomData<&'a ()>, +} + +impl<'a, G> LazyNodes<'a, G> +where + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +{ + pub fn new(f: G) -> Self { Self { - key, - children, - void_root: Cell::new(None), + inner: f, + _p: PhantomData {}, + } + } +} + +// Cover the cases where nodes are used by macro. +// Likely used directly. +// --- +// let nodes = rsx!{ ... }; +// rsx! { {nodes } } +impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G> +where + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +{ + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + (self.inner)(cx) + } +} + +// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator +impl<'a, G> IntoIterator for LazyNodes<'a, G> +where + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +{ + type Item = Self; + type IntoIter = std::iter::Once; + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self) + } +} + +impl IntoVNode<'_> for () { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + cx.fragment_from_iter(None as Option) + } +} + +impl IntoVNode<'_> for Option<()> { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + cx.fragment_from_iter(None as Option) + } +} + +impl Debug for NodeFactory<'_> { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +// it's okay to clone because vnodes are just references to places into the bump +impl<'a> Clone for VNode<'a> { + fn clone(&self) -> Self { + let kind = match &self.kind { + VNodeKind::Element(element) => VNodeKind::Element(element), + VNodeKind::Text(old) => VNodeKind::Text(VText { + text: old.text, + is_static: old.is_static, + }), + VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment { + children: fragment.children, + is_static: fragment.is_static, + }), + VNodeKind::Component(component) => VNodeKind::Component(component), + VNodeKind::Suspended => VNodeKind::Suspended, + }; + VNode { + kind, + dom_id: self.dom_id.clone(), + key: self.key.clone(), + } + } +} + +impl Debug for VNode<'_> { + fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match &self.kind { + VNodeKind::Element(el) => write!(s, "element, {}", el.tag_name), + VNodeKind::Text(t) => write!(s, "text, {}", t.text), + VNodeKind::Fragment(_) => write!(s, "fragment"), + VNodeKind::Suspended { .. } => write!(s, "suspended"), + VNodeKind::Component(_) => write!(s, "component"), } } } diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/oldbuilder.rs similarity index 68% rename from packages/core/src/nodebuilder.rs rename to packages/core/src/oldbuilder.rs index b0ecbbfca..c3d52b0c0 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/oldbuilder.rs @@ -1,25 +1,3 @@ -//! Helpers for building virtual DOM VNodes. - -use std::{ - any::Any, - borrow::BorrowMut, - cell::{Cell, RefCell}, - fmt::Arguments, - intrinsics::transmute, - marker::PhantomData, - u128, -}; - -use bumpalo::Bump; - -use crate::{ - events::VirtualEvent, - innerlude::{Properties, RealDomNode, Scope, VComponent, VFragment, VText, FC}, - nodes::{Attribute, Listener, NodeKey, VNode}, -}; - -/// A virtual DOM element builder. -/// /// Typically constructed with element-specific constructors, eg the `div` /// function for building `

` elements or the `button` function for building /// `