diff --git a/packages/core/README.md b/packages/core/README.md index ff60c905a..e439ab44b 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -7,16 +7,12 @@ To build new apps with Dioxus or to extend the ecosystem with new hooks or compo ```rust fn app(cx: Scope<()>) -> Element { - cx.render(rsx!( - div { "hello world" } - )) + rsx!(cx, div { "hello world" }) } - fn main() { let mut renderer = SomeRenderer::new(); - // Creating a new virtualdom from a component let mut dom = VirtualDom::new(app); @@ -25,23 +21,35 @@ fn main() { renderer.apply(edits); // Injecting events - dom.handle_message(SchedulerMsg::UserEvent()); + dom.handle_message(SchedulerMsg::Event(UserEvent { + scope_id: None, + priority: EventPriority::High, + element: ElementId(0), + name: "onclick", + data: Arc::new(()), + })); + + // polling asynchronously + dom.wait_for_work().await; + + // working with a deadline + if let Some(edits) = dom.work_with_deadline(|| false) { + renderer.apply(edits); + } + + // getting state of scopes + let scope = dom.get_scope(ScopeId(0)).unwrap(); + + // iterating through the tree + match scope.root_node() { + VNodes::Text(vtext) => dbg!(vtext), + VNodes::Element(vel) => dbg!(vel), + _ => todo!() + } } - - - - - - - - - - - - ``` ## Internals diff --git a/packages/core/examples/simple_hello.rs b/packages/core/examples/simple_hello.rs new file mode 100644 index 000000000..94a897e7c --- /dev/null +++ b/packages/core/examples/simple_hello.rs @@ -0,0 +1,9 @@ +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; + +// very tiny hello world +fn main() { + dioxus::VirtualDom::new(|cx| rsx!(cx, "hello world")); +} diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs deleted file mode 100644 index 71de3cf99..000000000 --- a/packages/core/src/component.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible -//! for components to be used within Nodes. -//! -//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is -//! if the type supports 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::{Element, Scope}, - LazyNodes, -}; - -pub struct FragmentProps<'a>(Element<'a>); -pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>); -impl<'a> FragmentBuilder<'a, false> { - pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> { - FragmentBuilder(children) - } -} -impl<'a, const A: bool> FragmentBuilder<'a, A> { - pub fn build(self) -> FragmentProps<'a> { - FragmentProps(self.0) - } -} - -/// Access the children elements passed into the component -/// -/// This enables patterns where a component is passed children from its parent. -/// -/// ## Details -/// -/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions -/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure -/// on the props that takes Context. -/// -/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other -/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's -/// props are valid for the static lifetime. -/// -/// ## Example -/// -/// ```rust, ignore -/// fn App(cx: Scope<()>) -> Element { -/// cx.render(rsx!{ -/// CustomCard { -/// h1 {}2 -/// p {} -/// } -/// }) -/// } -/// -/// #[derive(PartialEq, Props)] -/// struct CardProps { -/// children: Element -/// } -/// -/// fn CustomCard(cx: Scope) -> Element { -/// cx.render(rsx!{ -/// div { -/// h1 {"Title card"} -/// {cx.props.children} -/// } -/// }) -/// } -/// ``` -impl<'a> Properties for FragmentProps<'a> { - type Builder = FragmentBuilder<'a, false>; - const IS_STATIC: bool = false; - fn builder() -> Self::Builder { - FragmentBuilder(None) - } - unsafe fn memoize(&self, _other: &Self) -> bool { - false - } -} - -/// Create inline fragments using Component syntax. -/// -/// ## Details -/// -/// Fragments capture a series of children without rendering extra nodes. -/// -/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and -/// a key is needed to identify each item. -/// -/// ## Example -/// -/// ```rust, ignore -/// rsx!{ -/// Fragment { key: "abc" } -/// } -/// ``` -/// -/// ## Usage -/// -/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. -/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments. -/// -/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro. -/// -/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. -#[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { - let i = cx.props.0.as_ref().map(|f| f.decouple()); - cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(i)))) -} - -/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus -/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the -/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe. -/// -/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However, -/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored. -/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure); -/// -/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However, -/// this is only safe if the props are 'static - otherwise you might borrow references after-free. -/// -/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally, -/// we advise the use of smart pointers in cases where memoization is important. -/// -/// ## Example -/// -/// For props that are 'static: -/// ```rust, ignore ignore -/// #[derive(Props, PartialEq)] -/// struct MyProps { -/// data: String -/// } -/// ``` -/// -/// For props that borrow: -/// -/// ```rust, ignore ignore -/// #[derive(Props)] -/// struct MyProps<'a >{ -/// data: &'a str -/// } -/// ``` -pub trait Properties: Sized { - type Builder; - const IS_STATIC: bool; - fn builder() -> Self::Builder; - - /// Memoization can only happen if the props are valid for the 'static lifetime - /// - /// # Safety - /// The user must know if their props are static, but if they make a mistake, UB happens - /// Therefore it's unsafe to memoize. - unsafe fn memoize(&self, other: &Self) -> bool; -} - -impl Properties for () { - type Builder = EmptyBuilder; - const IS_STATIC: bool = true; - fn builder() -> Self::Builder { - EmptyBuilder {} - } - unsafe fn memoize(&self, _other: &Self) -> bool { - true - } -} -// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method -// that the macros use to anonymously complete prop construction. -pub struct EmptyBuilder; -impl EmptyBuilder { - pub fn build(self) {} -} - -/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern -/// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { - T::builder() -} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 1aff93be7..6d8129eed 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,7 +1,6 @@ #![allow(non_snake_case)] #![doc = include_str!("../README.md")] -pub(crate) mod component; pub(crate) mod diff; pub(crate) mod lazynodes; pub(crate) mod mutations; @@ -10,7 +9,6 @@ pub(crate) mod scopes; pub(crate) mod virtual_dom; pub(crate) mod innerlude { - pub use crate::component::*; pub(crate) use crate::diff::*; pub use crate::lazynodes::*; pub use crate::mutations::*; @@ -69,12 +67,12 @@ pub use crate::innerlude::{ }; pub mod prelude { - pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::innerlude::Scope; pub use crate::innerlude::{ Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, ScopeState, }; pub use crate::nodes::VNode; + pub use crate::virtual_dom::{fc_to_builder, Fragment, Properties}; pub use crate::VirtualDom; } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index d652ba326..1d3d360ce 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -697,6 +697,51 @@ impl Drop for VirtualDom { } } +struct PollTasks<'a>(&'a mut ScopeArena); + +impl<'a> Future for PollTasks<'a> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let mut all_pending = true; + + let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![]; + let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![]; + + // Poll every scope manually + for fut in self.0.pending_futures.borrow().iter().copied() { + let scope = self.0.get_scope(fut).expect("Scope should never be moved"); + + let mut items = scope.items.borrow_mut(); + + // really this should just be retain_mut but that doesn't exist yet + while let Some(mut task) = items.tasks.pop() { + if task.as_mut().poll(cx).is_ready() { + all_pending = false + } else { + unfinished_tasks.push(task); + } + } + + if unfinished_tasks.is_empty() { + scopes_to_clear.push(fut); + } + + items.tasks.extend(unfinished_tasks.drain(..)); + } + + for scope in scopes_to_clear { + self.0.pending_futures.borrow_mut().remove(&scope); + } + + // Resolve the future if any singular task is ready + match all_pending { + true => Poll::Pending, + false => Poll::Ready(()), + } + } +} + #[derive(Debug)] pub enum SchedulerMsg { // events from the host @@ -810,47 +855,165 @@ pub enum EventPriority { Low = 0, } -struct PollTasks<'a>(&'a mut ScopeArena); - -impl<'a> Future for PollTasks<'a> { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let mut all_pending = true; - - let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![]; - let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![]; - - // Poll every scope manually - for fut in self.0.pending_futures.borrow().iter().copied() { - let scope = self.0.get_scope(fut).expect("Scope should never be moved"); - - let mut items = scope.items.borrow_mut(); - - // really this should just be retain_mut but that doesn't exist yet - while let Some(mut task) = items.tasks.pop() { - if task.as_mut().poll(cx).is_ready() { - all_pending = false - } else { - unfinished_tasks.push(task); - } - } - - if unfinished_tasks.is_empty() { - scopes_to_clear.push(fut); - } - - items.tasks.extend(unfinished_tasks.drain(..)); - } - - for scope in scopes_to_clear { - self.0.pending_futures.borrow_mut().remove(&scope); - } - - // Resolve the future if any singular task is ready - match all_pending { - true => Poll::Pending, - false => Poll::Ready(()), - } +pub struct FragmentProps<'a>(Element<'a>); +pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>); +impl<'a> FragmentBuilder<'a, false> { + pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> { + FragmentBuilder(children) } } +impl<'a, const A: bool> FragmentBuilder<'a, A> { + pub fn build(self) -> FragmentProps<'a> { + FragmentProps(self.0) + } +} + +/// Access the children elements passed into the component +/// +/// This enables patterns where a component is passed children from its parent. +/// +/// ## Details +/// +/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions +/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure +/// on the props that takes Context. +/// +/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other +/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's +/// props are valid for the static lifetime. +/// +/// ## Example +/// +/// ```rust, ignore +/// fn App(cx: Scope<()>) -> Element { +/// cx.render(rsx!{ +/// CustomCard { +/// h1 {}2 +/// p {} +/// } +/// }) +/// } +/// +/// #[derive(PartialEq, Props)] +/// struct CardProps { +/// children: Element +/// } +/// +/// fn CustomCard(cx: Scope) -> Element { +/// cx.render(rsx!{ +/// div { +/// h1 {"Title card"} +/// {cx.props.children} +/// } +/// }) +/// } +/// ``` +impl<'a> Properties for FragmentProps<'a> { + type Builder = FragmentBuilder<'a, false>; + const IS_STATIC: bool = false; + fn builder() -> Self::Builder { + FragmentBuilder(None) + } + unsafe fn memoize(&self, _other: &Self) -> bool { + false + } +} + +/// Create inline fragments using Component syntax. +/// +/// ## Details +/// +/// Fragments capture a series of children without rendering extra nodes. +/// +/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and +/// a key is needed to identify each item. +/// +/// ## Example +/// +/// ```rust, ignore +/// rsx!{ +/// Fragment { key: "abc" } +/// } +/// ``` +/// +/// ## Usage +/// +/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. +/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments. +/// +/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro. +/// +/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. +#[allow(non_upper_case_globals, non_snake_case)] +pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { + let i = cx.props.0.as_ref().map(|f| f.decouple()); + cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(i)))) +} + +/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus +/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the +/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe. +/// +/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However, +/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored. +/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure); +/// +/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However, +/// this is only safe if the props are 'static - otherwise you might borrow references after-free. +/// +/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally, +/// we advise the use of smart pointers in cases where memoization is important. +/// +/// ## Example +/// +/// For props that are 'static: +/// ```rust, ignore ignore +/// #[derive(Props, PartialEq)] +/// struct MyProps { +/// data: String +/// } +/// ``` +/// +/// For props that borrow: +/// +/// ```rust, ignore ignore +/// #[derive(Props)] +/// struct MyProps<'a >{ +/// data: &'a str +/// } +/// ``` +pub trait Properties: Sized { + type Builder; + const IS_STATIC: bool; + fn builder() -> Self::Builder; + + /// Memoization can only happen if the props are valid for the 'static lifetime + /// + /// # Safety + /// The user must know if their props are static, but if they make a mistake, UB happens + /// Therefore it's unsafe to memoize. + unsafe fn memoize(&self, other: &Self) -> bool; +} + +impl Properties for () { + type Builder = EmptyBuilder; + const IS_STATIC: bool = true; + fn builder() -> Self::Builder { + EmptyBuilder {} + } + unsafe fn memoize(&self, _other: &Self) -> bool { + true + } +} +// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method +// that the macros use to anonymously complete prop construction. +pub struct EmptyBuilder; +impl EmptyBuilder { + pub fn build(self) {} +} + +/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern +/// to initialize a component's props. +pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { + T::builder() +}