Merge pull request #41 from DioxusLabs/jk/combine_scope

Concept: combine scope and props together into a single Context<Props>
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2021-12-14 22:48:45 -05:00
commit 97e014fb46
38 files changed
+403 -496

No files matched your search

+4 -4
View File
@@ -20,7 +20,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c); dioxus::desktop::launch(App, |c| c);
} }
fn App(cx: Context, props: &()) -> Element { fn App(cx: Scope, props: &()) -> Element {
let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f); let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
let first = text.get_mut(0).unwrap(); let first = text.get_mut(0).unwrap();
@@ -39,7 +39,7 @@ struct C1Props<'a> {
text: &'a mut String, text: &'a mut String,
} }
fn Child1(cx: Context, props: &C1Props) -> Element { fn Child1(cx: Scope, props: &C1Props) -> Element {
let (left, right) = props.text.split_once("=").unwrap(); let (left, right) = props.text.split_once("=").unwrap();
cx.render(rsx! { cx.render(rsx! {
@@ -55,7 +55,7 @@ struct C2Props<'a> {
text: &'a str, text: &'a str,
} }
fn Child2(cx: Context, props: &C2Props) -> Element { fn Child2(cx: Scope, props: &C2Props) -> Element {
cx.render(rsx! { cx.render(rsx! {
Child3 { Child3 {
text: props.text text: props.text
@@ -68,7 +68,7 @@ struct C3Props<'a> {
text: &'a str, text: &'a str,
} }
fn Child3(cx: Context, props: &C3Props) -> Element { fn Child3(cx: Scope, props: &C3Props) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { "{props.text}"} div { "{props.text}"}
}) })
+1 -1
View File
@@ -120,7 +120,7 @@ struct CalculatorKeyProps<'a> {
children: Element, children: Element,
} }
fn CalculatorKey<'a>(cx: Context, props: &CalculatorKeyProps) -> Element { fn CalculatorKey<'a>(cx: Scope, props: &CalculatorKeyProps) -> Element {
rsx!(cx, button { rsx!(cx, button {
class: "calculator-key {props.name}" class: "calculator-key {props.name}"
onclick: {props.onclick} onclick: {props.onclick}
+1 -1
View File
@@ -61,7 +61,7 @@ struct HorseyProps<'a> {
children: ScopeChildren<'a>, children: ScopeChildren<'a>,
} }
fn Horsey<'a>((cx, props): Scope<'a, HorseyProps<'a>>) -> Element { fn Horsey<'a>((cx, props): ScopeState<'a, HorseyProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
button { "pause" } button { "pause" }
+1 -1
View File
@@ -95,7 +95,7 @@ struct ActionButtonProps<'a> {
onclick: &'a dyn Fn(), onclick: &'a dyn Fn(),
} }
fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element { fn ActionButton(cx: Scope, props: &ActionButtonProps) -> Element {
rsx!(cx, div { class: "col-sm-6 smallpad" rsx!(cx, div { class: "col-sm-6 smallpad"
button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(), button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(),
"{props.name}" "{props.name}"
+1 -1
View File
@@ -4,7 +4,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c); dioxus::desktop::launch(App, |c| c);
} }
fn App((cx, props): Scope<()>) -> Element { fn App((cx, props): ScopeState<()>) -> Element {
cx.render(rsx! ( cx.render(rsx! (
div { "Hello, world!" } div { "Hello, world!" }
)) ))
+1 -1
View File
@@ -77,7 +77,7 @@ struct CalculatorKeyProps<'a> {
children: ScopeChildren<'a>, children: ScopeChildren<'a>,
} }
fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element<'a> { fn CalculatorKey<'a>((cx, props): ScopeState<'a, CalculatorKeyProps<'a>>) -> Element<'a> {
cx.render(rsx! { cx.render(rsx! {
button { button {
class: "calculator-key {props.name}" class: "calculator-key {props.name}"
+3 -3
View File
@@ -178,7 +178,7 @@ pub static Example: Component<()> = |cx, props| {
}) })
}; };
fn helper(cx: Context, text: &str) -> Element { fn helper(cx: Scope, text: &str) -> Element {
rsx!(cx, p { "{text}" }) rsx!(cx, p { "{text}" })
} }
@@ -188,7 +188,7 @@ mod baller {
pub struct BallerProps {} pub struct BallerProps {}
/// This component totally balls /// This component totally balls
pub fn Baller(_: Scope<BallerProps>) -> Element { pub fn Baller(_: ScopeState<BallerProps>) -> Element {
todo!() todo!()
} }
} }
@@ -202,7 +202,7 @@ pub struct TallerProps<'a> {
} }
/// This component is taller than most :) /// This component is taller than most :)
pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element { pub fn Taller<'a>(_: ScopeState<'a, TallerProps<'a>>) -> Element {
let b = true; let b = true;
todo!() todo!()
} }
+1 -1
View File
@@ -21,6 +21,6 @@ static App: Component<()> = |cx, props| {
struct MyProps<'a> { struct MyProps<'a> {
text: &'a str, text: &'a str,
} }
fn App2(cx: Context, props: &MyProps) -> Element { fn App2(cx: Scope, props: &MyProps) -> Element {
None None
} }
+1 -1
View File
@@ -85,7 +85,7 @@ pub struct TodoEntryProps {
todo: Rc<TodoItem>, todo: Rc<TodoItem>,
} }
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> Element { pub fn TodoEntry(cx: Scope, props: &TodoEntryProps) -> Element {
let mut is_editing = use_state(cx, || false); let mut is_editing = use_state(cx, || false);
let mut contents = use_state(cx, || String::from("")); let mut contents = use_state(cx, || String::from(""));
let todo = &props.todo; let todo = &props.todo;
+1 -1
View File
@@ -45,7 +45,7 @@ struct RowProps {
row_id: usize, row_id: usize,
label: Label, label: Label,
} }
fn Row((cx, props): Scope<RowProps>) -> Element { fn Row((cx, props): ScopeState<RowProps>) -> Element {
let [adj, col, noun] = props.label.0; let [adj, col, noun] = props.label.0;
cx.render(rsx! { cx.render(rsx! {
tr { tr {
+1 -1
View File
@@ -53,7 +53,7 @@ struct RowProps {
row_id: usize, row_id: usize,
label: Label, label: Label,
} }
fn Row(cx: Context, props: &RowProps) -> Element { fn Row(cx: Scope, props: &RowProps) -> Element {
let [adj, col, noun] = props.label.0; let [adj, col, noun] = props.label.0;
cx.render(rsx! { cx.render(rsx! {
tr { tr {
+51
View File
@@ -0,0 +1,51 @@
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {}
fn App(cx: Scope<()>) -> Element {
cx.render(rsx!(div {
App2 {
p: "asd"
}
}))
}
#[derive(Props)]
struct Borrowed<'a> {
p: &'a str,
}
fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
let g = eat2(&cx);
todo!()
}
fn eat2(s: &ScopeState) {}
fn eat(f: &str) {}
fn bleat() {
let blah = String::from("asd");
eat(&blah);
}
// struct Lower {}
// #[derive(Clone, Copy)]
// struct Upper {}
// impl std::ops::Deref for Upper {
// type Target = Lower;
// fn deref(&self) -> &Self::Target {
// todo!()
// }
// }
// fn mark(f: &Lower) {}
// fn bark() {
// let up = Upper {};
// mark(&up);
// }
+2 -6
View File
@@ -9,7 +9,7 @@ fn main() {
let _ = VirtualDom::new(App); let _ = VirtualDom::new(App);
} }
fn App(cx: Context, _props: &()) -> Element { fn App(cx: Scope<()>) -> Element {
// //
cx.render(rsx!( cx.render(rsx!(
div { div {
@@ -18,11 +18,7 @@ fn App(cx: Context, _props: &()) -> Element {
)) ))
} }
struct ChildProps<'a> { fn Child(cx: Scope<()>) -> Element {
click_handler: EventHandler<'a>,
}
fn Child(cx: Context, _props: &()) -> Element {
// //
cx.render(rsx!( cx.render(rsx!(
div { div {
+16
View File
@@ -0,0 +1,16 @@
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_hooks::use_state;
use dioxus_html as dioxus_elements;
fn main() {}
fn App(cx: Scope<()>) -> Element {
let color = use_state(&cx, || "white");
cx.render(rsx!(
div { onclick: move |_| color.set("red"), "red" }
div { onclick: move |_| color.set("blue"), "blue" }
))
}
+4 -4
View File
@@ -20,7 +20,7 @@ use dioxus_html as dioxus_elements;
use rand::prelude::*; use rand::prelude::*;
fn main() { fn main() {
static App: Component<()> = |cx, _| { static App: Component<()> = |cx| {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| { let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng); let label = Label::new(&mut rng);
@@ -50,11 +50,11 @@ struct RowProps {
row_id: usize, row_id: usize,
label: Label, label: Label,
} }
fn Row(cx: Context, props: &RowProps) -> Element { fn Row(cx: Scope<RowProps>) -> Element {
let [adj, col, noun] = props.label.0; let [adj, col, noun] = cx.props.label.0;
cx.render(rsx! { cx.render(rsx! {
tr { tr {
td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", "{cx.props.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ } td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
a { class: "lbl", "{adj}" "{col}" "{noun}" } a { class: "lbl", "{adj}" "{col}" "{noun}" }
} }
+5 -5
View File
@@ -9,7 +9,7 @@ fn main() {
let _ = VirtualDom::new(Parent); let _ = VirtualDom::new(Parent);
} }
fn Parent(cx: Context, _props: &()) -> Element { fn Parent(cx: Scope<()>) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f); let value = cx.use_hook(|_| String::new(), |f| f);
cx.render(rsx! { cx.render(rsx! {
@@ -25,11 +25,11 @@ struct ChildProps<'a> {
name: &'a str, name: &'a str,
} }
fn Child(cx: Context, props: &ChildProps) -> Element { fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
h1 { "it's nested" } h1 { "it's nested" }
Child2 { name: props.name } Child2 { name: cx.props.name }
} }
}) })
} }
@@ -39,8 +39,8 @@ struct Grandchild<'a> {
name: &'a str, name: &'a str,
} }
fn Child2(cx: Context, props: &Grandchild) -> Element { fn Child2<'a>(cx: Scope<'a, Grandchild<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { "Hello {props.name}!" } div { "Hello {cx.props.name}!" }
}) })
} }
+16 -12
View File
@@ -5,17 +5,20 @@
//! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder //! 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. //! that ensures compile-time required and optional fields on cx.
use crate::innerlude::{Context, Element, LazyNodes, VPortal}; use crate::{
innerlude::{Element, Scope},
LazyNodes,
};
pub struct FragmentProps(Element); pub struct FragmentProps<'a>(Element<'a>);
pub struct FragmentBuilder<const BUILT: bool>(Element); pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
impl FragmentBuilder<false> { impl<'a> FragmentBuilder<'a, false> {
pub fn children(self, children: Option<VPortal>) -> FragmentBuilder<true> { pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
FragmentBuilder(children) FragmentBuilder(children)
} }
} }
impl<const A: bool> FragmentBuilder<A> { impl<'a, const A: bool> FragmentBuilder<'a, A> {
pub fn build(self) -> FragmentProps { pub fn build(self) -> FragmentProps<'a> {
FragmentProps(self.0) FragmentProps(self.0)
} }
} }
@@ -60,8 +63,8 @@ impl<const A: bool> FragmentBuilder<A> {
/// }) /// })
/// } /// }
/// ``` /// ```
impl Properties for FragmentProps { impl<'a> Properties for FragmentProps<'a> {
type Builder = FragmentBuilder<false>; type Builder = FragmentBuilder<'a, false>;
const IS_STATIC: bool = false; const IS_STATIC: bool = false;
fn builder() -> Self::Builder { fn builder() -> Self::Builder {
FragmentBuilder(None) FragmentBuilder(None)
@@ -97,8 +100,9 @@ impl Properties for FragmentProps {
/// ///
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// 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)] #[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment(cx: Context, props: &FragmentProps) -> Element { pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(&props.0)))) 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 /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
@@ -165,6 +169,6 @@ impl EmptyBuilder {
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
/// to initialize a component's props. /// to initialize a component's props.
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &'a T) -> Element) -> T::Builder { pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
T::builder() T::builder()
} }
+23 -72
View File
@@ -239,10 +239,10 @@ impl<'bump> DiffStack<'bump> {
} }
impl<'bump> DiffState<'bump> { impl<'bump> DiffState<'bump> {
pub fn diff_scope(&mut self, id: &ScopeId) { pub fn diff_scope(&mut self, id: ScopeId) {
let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id)); let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id));
self.stack.push(DiffInstruction::Diff { old, new }); self.stack.push(DiffInstruction::Diff { old, new });
self.stack.scope_stack.push(*id); self.stack.scope_stack.push(id);
let scope = self.scopes.get_scope(id).unwrap(); let scope = self.scopes.get_scope(id).unwrap();
self.stack.element_stack.push(scope.container); self.stack.element_stack.push(scope.container);
self.work(|| false); self.work(|| false);
@@ -288,12 +288,6 @@ impl<'bump> DiffState<'bump> {
1 1
} }
VNode::Portal(linked) => {
let node = unsafe { &*linked.node };
let node: &VNode = unsafe { std::mem::transmute(node) };
self.push_all_nodes(node)
}
VNode::Fragment(_) | VNode::Component(_) => { VNode::Fragment(_) | VNode::Component(_) => {
// //
let mut added = 0; let mut added = 0;
@@ -357,7 +351,6 @@ impl<'bump> DiffState<'bump> {
VNode::Element(element) => self.create_element_node(element, node), VNode::Element(element) => self.create_element_node(element, node),
VNode::Fragment(frag) => self.create_fragment_node(frag), VNode::Fragment(frag) => self.create_fragment_node(frag),
VNode::Component(component) => self.create_component_node(*component), VNode::Component(component) => self.create_component_node(*component),
VNode::Portal(linked) => self.create_linked_node(linked),
} }
} }
@@ -406,7 +399,7 @@ impl<'bump> DiffState<'bump> {
self.stack.add_child_count(1); self.stack.add_child_count(1);
if let Some(cur_scope_id) = self.stack.current_scope() { if let Some(cur_scope_id) = self.stack.current_scope() {
let scope = self.scopes.get_scope(&cur_scope_id).unwrap(); let scope = self.scopes.get_scope(cur_scope_id).unwrap();
for listener in *listeners { for listener in *listeners {
self.attach_listener_to_scope(listener, scope); self.attach_listener_to_scope(listener, scope);
@@ -443,11 +436,11 @@ impl<'bump> DiffState<'bump> {
let parent_idx = self.stack.current_scope().unwrap(); let parent_idx = self.stack.current_scope().unwrap();
// Insert a new scope into our component list // Insert a new scope into our component list
let parent_scope = self.scopes.get_scope(&parent_idx).unwrap(); let parent_scope = self.scopes.get_scope(parent_idx).unwrap();
let height = parent_scope.height + 1; let height = parent_scope.height + 1;
let subtree = parent_scope.subtree.get(); let subtree = parent_scope.subtree.get();
let parent_scope = unsafe { self.scopes.get_scope_raw(&parent_idx) }; let parent_scope = unsafe { self.scopes.get_scope_raw(parent_idx) };
let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) }; let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) };
let fc_ptr = vcomponent.user_fc; let fc_ptr = vcomponent.user_fc;
@@ -461,7 +454,7 @@ impl<'bump> DiffState<'bump> {
vcomponent.associated_scope.set(Some(new_idx)); vcomponent.associated_scope.set(Some(new_idx));
if !vcomponent.can_memoize { if !vcomponent.can_memoize {
let cur_scope = self.scopes.get_scope(&parent_idx).unwrap(); let cur_scope = self.scopes.get_scope(parent_idx).unwrap();
let extended = unsafe { std::mem::transmute(vcomponent) }; let extended = unsafe { std::mem::transmute(vcomponent) };
cur_scope.items.borrow_mut().borrowed_props.push(extended); cur_scope.items.borrow_mut().borrowed_props.push(extended);
} else { } else {
@@ -469,7 +462,7 @@ impl<'bump> DiffState<'bump> {
} }
// TODO: add noderefs to current noderef list Noderefs // TODO: add noderefs to current noderef list Noderefs
let _new_component = self.scopes.get_scope(&new_idx).unwrap(); let _new_component = self.scopes.get_scope(new_idx).unwrap();
log::debug!( log::debug!(
"initializing component {:?} with height {:?}", "initializing component {:?} with height {:?}",
@@ -478,9 +471,9 @@ impl<'bump> DiffState<'bump> {
); );
// Run the scope for one iteration to initialize it // Run the scope for one iteration to initialize it
if self.scopes.run_scope(&new_idx) { if self.scopes.run_scope(new_idx) {
// Take the node that was just generated from running the component // Take the node that was just generated from running the component
let nextnode = self.scopes.fin_head(&new_idx); let nextnode = self.scopes.fin_head(new_idx);
self.stack.create_component(new_idx, nextnode); self.stack.create_component(new_idx, nextnode);
// todo: subtrees // todo: subtrees
@@ -493,16 +486,6 @@ impl<'bump> DiffState<'bump> {
self.mutations.dirty_scopes.insert(new_idx); self.mutations.dirty_scopes.insert(new_idx);
} }
fn create_linked_node(&mut self, link: &'bump VPortal) {
if link.scope_id.get().is_none() {
if let Some(cur_scope) = self.stack.current_scope() {
link.scope_id.set(Some(cur_scope));
}
}
let node: &'bump VNode<'static> = unsafe { &*link.node };
self.create_node(unsafe { std::mem::transmute(node) });
}
// ================================= // =================================
// Tools for diffing nodes // Tools for diffing nodes
// ================================= // =================================
@@ -520,12 +503,11 @@ impl<'bump> DiffState<'bump> {
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
(Placeholder(old), Placeholder(new)) => new.dom_id.set(old.dom_id.get()), (Placeholder(old), Placeholder(new)) => new.dom_id.set(old.dom_id.get()),
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node), (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
(Portal(old), Portal(new)) => self.diff_linked_nodes(old, new),
// Anything else is just a basic replace and create // Anything else is just a basic replace and create
( (
Portal(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
Portal(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
) => self ) => self
.stack .stack
.create_node(new_node, MountType::Replace { old: old_node }), .create_node(new_node, MountType::Replace { old: old_node }),
@@ -613,7 +595,7 @@ impl<'bump> DiffState<'bump> {
// //
// TODO: take a more efficient path than this // TODO: take a more efficient path than this
if let Some(cur_scope_id) = self.stack.current_scope() { if let Some(cur_scope_id) = self.stack.current_scope() {
let scope = self.scopes.get_scope(&cur_scope_id).unwrap(); let scope = self.scopes.get_scope(cur_scope_id).unwrap();
if old.listeners.len() == new.listeners.len() { if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
@@ -736,7 +718,7 @@ impl<'bump> DiffState<'bump> {
// make sure the component's caller function is up to date // make sure the component's caller function is up to date
let scope = unsafe { let scope = unsafe {
self.scopes self.scopes
.get_scope_mut(&scope_addr) .get_scope_mut(scope_addr)
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr)) .unwrap_or_else(|| panic!("could not find {:?}", scope_addr))
}; };
@@ -745,10 +727,10 @@ impl<'bump> DiffState<'bump> {
// React doesn't automatically memoize, but we do. // React doesn't automatically memoize, but we do.
let props_are_the_same = old.comparator.unwrap(); let props_are_the_same = old.comparator.unwrap();
if (self.force_diff || !props_are_the_same(new)) && self.scopes.run_scope(&scope_addr) { if (self.force_diff || !props_are_the_same(new)) && self.scopes.run_scope(scope_addr) {
self.diff_node( self.diff_node(
self.scopes.wip_head(&scope_addr), self.scopes.wip_head(scope_addr),
self.scopes.fin_head(&scope_addr), self.scopes.fin_head(scope_addr),
); );
} }
@@ -773,19 +755,6 @@ impl<'bump> DiffState<'bump> {
self.diff_children(old.children, new.children); self.diff_children(old.children, new.children);
} }
fn diff_linked_nodes(&mut self, old: &'bump VPortal, new: &'bump VPortal) {
if !std::ptr::eq(old.node, new.node) {
// if the ptrs are the same then theyr're the same
let old: &VNode = unsafe { std::mem::transmute(&*old.node) };
let new: &VNode = unsafe { std::mem::transmute(&*new.node) };
self.diff_node(old, new);
}
if new.scope_id.get().is_none() {
todo!("attach the link to the scope - when children are not created");
}
}
// ============================================= // =============================================
// Utilities for creating new diff instructions // Utilities for creating new diff instructions
// ============================================= // =============================================
@@ -1226,16 +1195,12 @@ impl<'bump> DiffState<'bump> {
VNode::Text(t) => break t.dom_id.get(), VNode::Text(t) => break t.dom_id.get(),
VNode::Element(t) => break t.dom_id.get(), VNode::Element(t) => break t.dom_id.get(),
VNode::Placeholder(t) => break t.dom_id.get(), VNode::Placeholder(t) => break t.dom_id.get(),
VNode::Portal(l) => {
let node: &VNode = unsafe { std::mem::transmute(&*l.node) };
self.find_last_element(node);
}
VNode::Fragment(frag) => { VNode::Fragment(frag) => {
search_node = frag.children.last(); search_node = frag.children.last();
} }
VNode::Component(el) => { VNode::Component(el) => {
let scope_id = el.associated_scope.get().unwrap(); let scope_id = el.associated_scope.get().unwrap();
search_node = Some(self.scopes.root_node(&scope_id)); search_node = Some(self.scopes.root_node(scope_id));
} }
} }
} }
@@ -1252,11 +1217,7 @@ impl<'bump> DiffState<'bump> {
} }
VNode::Component(el) => { VNode::Component(el) => {
let scope_id = el.associated_scope.get().unwrap(); let scope_id = el.associated_scope.get().unwrap();
search_node = Some(self.scopes.root_node(&scope_id)); search_node = Some(self.scopes.root_node(scope_id));
}
VNode::Portal(link) => {
let node = unsafe { std::mem::transmute(&*link.node) };
search_node = Some(node);
} }
VNode::Text(t) => break t.dom_id.get(), VNode::Text(t) => break t.dom_id.get(),
VNode::Element(t) => break t.dom_id.get(), VNode::Element(t) => break t.dom_id.get(),
@@ -1292,17 +1253,12 @@ impl<'bump> DiffState<'bump> {
} }
VNode::Component(c) => { VNode::Component(c) => {
let node = self.scopes.fin_head(&c.associated_scope.get().unwrap()); let node = self.scopes.fin_head(c.associated_scope.get().unwrap());
self.replace_node(node, nodes_created); self.replace_node(node, nodes_created);
let scope_id = c.associated_scope.get().unwrap(); let scope_id = c.associated_scope.get().unwrap();
log::debug!("Destroying scope {:?}", scope_id); log::debug!("Destroying scope {:?}", scope_id);
self.scopes.try_remove(&scope_id).unwrap(); self.scopes.try_remove(scope_id).unwrap();
}
VNode::Portal(l) => {
let node: &'bump VNode<'bump> = unsafe { std::mem::transmute(&*l.node) };
self.replace_node(node, nodes_created);
} }
} }
} }
@@ -1349,11 +1305,6 @@ impl<'bump> DiffState<'bump> {
self.remove_nodes(f.children, gen_muts); self.remove_nodes(f.children, gen_muts);
} }
VNode::Portal(l) => {
let node = unsafe { std::mem::transmute(&*l.node) };
self.remove_nodes(Some(node), gen_muts);
}
VNode::Component(c) => { VNode::Component(c) => {
self.destroy_vomponent(c, gen_muts); self.destroy_vomponent(c, gen_muts);
} }
@@ -1363,14 +1314,14 @@ impl<'bump> DiffState<'bump> {
fn destroy_vomponent(&mut self, vc: &VComponent, gen_muts: bool) { fn destroy_vomponent(&mut self, vc: &VComponent, gen_muts: bool) {
let scope_id = vc.associated_scope.get().unwrap(); let scope_id = vc.associated_scope.get().unwrap();
let root = self.scopes.root_node(&scope_id); let root = self.scopes.root_node(scope_id);
self.remove_nodes(Some(root), gen_muts); self.remove_nodes(Some(root), gen_muts);
log::debug!("Destroying scope {:?}", scope_id); log::debug!("Destroying scope {:?}", scope_id);
self.scopes.try_remove(&scope_id).unwrap(); self.scopes.try_remove(scope_id).unwrap();
} }
/// Adds a listener closure to a scope during diff. /// Adds a listener closure to a scope during diff.
fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &Scope) { fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &ScopeState) {
let long_listener = unsafe { std::mem::transmute(listener) }; let long_listener = unsafe { std::mem::transmute(listener) };
scope.items.borrow_mut().listeners.push(long_listener) scope.items.borrow_mut().listeners.push(long_listener)
} }
+9 -9
View File
@@ -17,24 +17,24 @@ pub(crate) mod innerlude {
pub use crate::mutations::*; pub use crate::mutations::*;
pub use crate::nodes::*; pub use crate::nodes::*;
pub use crate::scope::*; pub use crate::scope::*;
pub use crate::scopearena::*; pub(crate) use crate::scopearena::*;
pub use crate::virtual_dom::*; pub use crate::virtual_dom::*;
pub type Element = Option<VPortal>; pub type Element<'a> = Option<VNode<'a>>;
pub type Component<P> = for<'a> fn(Context<'a>, &'a P) -> Element; pub type Component<P> = for<'a> fn(Scope<'a, P>) -> Element<'a>;
} }
pub use crate::innerlude::{ pub use crate::innerlude::{
Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler, Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom, ScopeId, ScopeState, UserEvent, VElement, VFragment, VNode, VirtualDom,
}; };
pub mod prelude { pub mod prelude {
pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::component::{fc_to_builder, Fragment, Properties};
pub use crate::innerlude::Context; pub use crate::innerlude::Scope;
pub use crate::innerlude::{ pub use crate::innerlude::{
Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, Scope, Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, ScopeState,
}; };
pub use crate::nodes::VNode; pub use crate::nodes::VNode;
pub use crate::VirtualDom; pub use crate::VirtualDom;
@@ -42,7 +42,7 @@ pub mod prelude {
pub mod exports { pub mod exports {
//! Important dependencies that are used by the rest of the library //! Important dependencies that are used by the rest of the library
// the foundation of this library //! Feel free to just add the dependencies in your own Crates.toml
pub use bumpalo; pub use bumpalo;
pub use futures_channel; pub use futures_channel;
} }
+18 -121
View File
@@ -4,7 +4,7 @@
//! cheap and *very* fast to construct - building a full tree should be quick. //! cheap and *very* fast to construct - building a full tree should be quick.
use crate::{ use crate::{
innerlude::{Context, Element, Properties, Scope, ScopeId}, innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
lazynodes::LazyNodes, lazynodes::LazyNodes,
}; };
use bumpalo::{boxed::Box as BumpBox, Bump}; use bumpalo::{boxed::Box as BumpBox, Bump};
@@ -114,22 +114,6 @@ pub enum VNode<'src> {
/// } /// }
/// ``` /// ```
Placeholder(&'src VPlaceholder), Placeholder(&'src VPlaceholder),
/// A VNode that is actually a pointer to some nodes rather than the nodes directly. Useful when rendering portals
/// or eliding lifetimes on VNodes through runtime checks.
///
/// Linked VNodes can only be made through the [`Context::render`] method
///
/// Typically, linked nodes are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
/// an `rsx!` call.
///
/// # Example
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
///
/// let node: NodeLink = vdom.render_vnode(rsx!( "hello" ));
/// ```
Portal(VPortal),
} }
impl<'src> VNode<'src> { impl<'src> VNode<'src> {
@@ -141,7 +125,6 @@ impl<'src> VNode<'src> {
VNode::Fragment(f) => f.key, VNode::Fragment(f) => f.key,
VNode::Text(_t) => None, VNode::Text(_t) => None,
VNode::Placeholder(_f) => None, VNode::Placeholder(_f) => None,
VNode::Portal(_c) => None,
} }
} }
@@ -160,7 +143,6 @@ impl<'src> VNode<'src> {
VNode::Text(el) => el.dom_id.get(), VNode::Text(el) => el.dom_id.get(),
VNode::Element(el) => el.dom_id.get(), VNode::Element(el) => el.dom_id.get(),
VNode::Placeholder(el) => el.dom_id.get(), VNode::Placeholder(el) => el.dom_id.get(),
VNode::Portal(_) => None,
VNode::Fragment(_) => None, VNode::Fragment(_) => None,
VNode::Component(_) => None, VNode::Component(_) => None,
} }
@@ -184,11 +166,6 @@ impl<'src> VNode<'src> {
children: f.children, children: f.children,
key: f.key, key: f.key,
}), }),
VNode::Portal(c) => VNode::Portal(VPortal {
scope_id: c.scope_id.clone(),
link_idx: c.link_idx.clone(),
node: c.node,
}),
} }
} }
} }
@@ -207,7 +184,6 @@ impl Debug for VNode<'_> {
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
} }
VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc), VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
VNode::Portal(c) => write!(s, "VNode::VCached {{ scope_id: {:?} }}", c.scope_id.get()),
} }
} }
} }
@@ -341,6 +317,7 @@ pub struct Listener<'bump> {
pub(crate) callback: EventHandler<'bump>, pub(crate) callback: EventHandler<'bump>,
} }
/// The callback based into element event listeners.
pub struct EventHandler<'bump> { pub struct EventHandler<'bump> {
pub callback: &'bump RefCell<Option<ListenerCallback<'bump>>>, pub callback: &'bump RefCell<Option<ListenerCallback<'bump>>>,
} }
@@ -355,6 +332,7 @@ impl EventHandler<'_> {
self.callback.replace(None); self.callback.replace(None);
} }
} }
type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'bump>; type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'bump>;
impl Copy for EventHandler<'_> {} impl Copy for EventHandler<'_> {}
@@ -366,32 +344,6 @@ impl Clone for EventHandler<'_> {
} }
} }
/// A cached node is a "pointer" to a "rendered" node in a particular scope
///
/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it
///
/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key
/// is essentially a unique key to guarantee safe usage of the Node.
///
/// Linked VNodes can only be made through the [`Context::render`] method
///
/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
/// an `rsx!` call.
///
/// todo: remove the raw pointer and use runtime checks instead
#[derive(Debug)]
pub struct VPortal {
pub(crate) link_idx: Cell<usize>,
pub(crate) scope_id: Cell<Option<ScopeId>>,
pub(crate) node: *const VNode<'static>,
}
impl PartialEq for VPortal {
fn eq(&self, other: &Self) -> bool {
self.node == other.node
}
}
/// Virtual Components for custom user-defined components /// Virtual Components for custom user-defined components
/// Only supports the functional syntax /// Only supports the functional syntax
pub struct VComponent<'src> { pub struct VComponent<'src> {
@@ -410,7 +362,7 @@ pub struct VComponent<'src> {
pub(crate) bump_props: *const (), pub(crate) bump_props: *const (),
// during the "teardown" process we'll take the caller out so it can be dropped properly // during the "teardown" process we'll take the caller out so it can be dropped properly
pub(crate) caller: &'src dyn Fn(&'src Scope) -> Element, pub(crate) caller: &'src dyn Fn(&'src ScopeState) -> Element,
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>, pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
@@ -552,7 +504,7 @@ impl<'a> NodeFactory<'a> {
pub fn component<P>( pub fn component<P>(
&self, &self,
component: fn(Context<'a>, &'a P) -> Element, component: fn(Scope<'a, P>) -> Element,
props: P, props: P,
key: Option<Arguments>, key: Option<Arguments>,
) -> VNode<'a> ) -> VNode<'a>
@@ -610,10 +562,10 @@ impl<'a> NodeFactory<'a> {
let key = key.map(|f| self.raw_text(f).0); let key = key.map(|f| self.raw_text(f).0);
let caller: &'a mut dyn Fn(&'a Scope) -> Element = let caller: &'a mut dyn Fn(&'a ScopeState) -> Element =
bump.alloc(move |scope: &Scope| -> Element { bump.alloc(move |scope: &ScopeState| -> Element {
let props: &'_ P = unsafe { &*(bump_props as *const P) }; let props: &'_ P = unsafe { &*(bump_props as *const P) };
component(scope, props) component(Scope { scope, props })
}); });
let can_memoize = P::IS_STATIC; let can_memoize = P::IS_STATIC;
@@ -705,7 +657,7 @@ impl<'a> NodeFactory<'a> {
pub fn create_children( pub fn create_children(
self, self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>, node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
) -> Element { ) -> Element<'a> {
let bump = self.bump; let bump = self.bump;
let mut nodes = bumpalo::collections::Vec::new_in(bump); let mut nodes = bumpalo::collections::Vec::new_in(bump);
@@ -723,33 +675,11 @@ impl<'a> NodeFactory<'a> {
// TODO // TODO
// We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
//
// if cfg!(debug_assertions) {
// if children.len() > 1 {
// if children.last().unwrap().key().is_none() {
// log::error!(
// r#"
// Warning: Each child in an array or iterator should have a unique "key" prop.
// Not providing a key will lead to poor performance with lists.
// See docs.rs/dioxus for more information.
// ---
// To help you identify where this error is coming from, we've generated a backtrace.
// "#,
// );
// }
// }
// }
let frag = VNode::Fragment(VFragment { Some(VNode::Fragment(VFragment {
children, children,
key: None, key: None,
}); }))
let ptr = self.bump.alloc(frag) as *const _;
Some(VPortal {
link_idx: Default::default(),
scope_id: Default::default(),
node: unsafe { std::mem::transmute(ptr) },
})
} }
} }
@@ -845,48 +775,15 @@ impl IntoVNode<'_> for Arguments<'_> {
} }
} }
// called cx.render from a helper function impl<'a> IntoVNode<'a> for &Option<VNode<'a>> {
impl IntoVNode<'_> for Option<VPortal> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
fn into_vnode(self, _cx: NodeFactory) -> VNode { let r = self.as_ref().map(|f| f.decouple());
match self { cx.fragment_from_iter(r)
Some(node) => VNode::Portal(node),
None => {
todo!()
}
}
} }
} }
// essentially passing elements through props impl<'a> IntoVNode<'a> for &VNode<'a> {
// just build a new element in place fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
impl IntoVNode<'_> for &Option<VPortal> { self.decouple()
fn into_vnode(self, _cx: NodeFactory) -> VNode {
match self {
Some(node) => VNode::Portal(VPortal {
link_idx: node.link_idx.clone(),
scope_id: node.scope_id.clone(),
node: node.node,
}),
None => {
//
todo!()
}
}
}
}
impl IntoVNode<'_> for VPortal {
fn into_vnode(self, _cx: NodeFactory) -> VNode {
VNode::Portal(self)
}
}
impl IntoVNode<'_> for &VPortal {
fn into_vnode(self, _cx: NodeFactory) -> VNode {
VNode::Portal(VPortal {
link_idx: self.link_idx.clone(),
scope_id: self.scope_id.clone(),
node: self.node,
})
} }
} }
+38 -31
View File
@@ -31,7 +31,28 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
/// cx.render(rsx!{ div {"Hello, {props.name}"} }) /// cx.render(rsx!{ div {"Hello, {props.name}"} })
/// } /// }
/// ``` /// ```
pub type Context<'a> = &'a Scope; pub struct Scope<'a, P> {
pub scope: &'a ScopeState,
pub props: &'a P,
}
impl<P> Copy for Scope<'_, P> {}
impl<P> Clone for Scope<'_, P> {
fn clone(&self) -> Self {
Self {
scope: self.scope,
props: self.props,
}
}
}
impl<'a, P> std::ops::Deref for Scope<'a, P> {
// rust will auto deref again to the original 'a lifetime at the call site
type Target = &'a ScopeState;
fn deref(&self) -> &Self::Target {
&self.scope
}
}
/// A component's unique identifier. /// A component's unique identifier.
/// ///
@@ -50,8 +71,8 @@ pub struct ScopeId(pub usize);
/// ///
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
/// use case they might have. /// use case they might have.
pub struct Scope { pub struct ScopeState {
pub(crate) parent_scope: Option<*mut Scope>, pub(crate) parent_scope: Option<*mut ScopeState>,
pub(crate) container: ElementId, pub(crate) container: ElementId,
@@ -67,7 +88,7 @@ pub struct Scope {
pub(crate) frames: [BumpFrame; 2], pub(crate) frames: [BumpFrame; 2],
pub(crate) caller: *const dyn Fn(&Scope) -> Element, pub(crate) caller: *const dyn Fn(&ScopeState) -> Element,
pub(crate) items: RefCell<SelfReferentialItems<'static>>, pub(crate) items: RefCell<SelfReferentialItems<'static>>,
@@ -87,7 +108,7 @@ pub struct SelfReferentialItems<'a> {
} }
// Public methods exposed to libraries and components // Public methods exposed to libraries and components
impl Scope { impl ScopeState {
/// Get the subtree ID that this scope belongs to. /// Get the subtree ID that this scope belongs to.
/// ///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
@@ -331,20 +352,15 @@ impl Scope {
/// cx.render(lazy_tree) /// cx.render(lazy_tree)
/// } /// }
///``` ///```
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> { pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
let bump = &self.wip_frame().bump; let fac = NodeFactory {
bump: &self.wip_frame().bump,
let owned_node: VNode<'src> = rsx.map(|f| f.call(NodeFactory { bump }))?; };
let alloced_vnode: &'src mut VNode<'src> = bump.alloc(owned_node); match rsx {
let node_ptr: *mut VNode<'src> = alloced_vnode as *mut _; Some(s) => Some(s.call(fac)),
None => todo!(),
let node: *mut VNode<'static> = unsafe { std::mem::transmute(node_ptr) }; }
// rsx.map(|f| f.call(fac))
Some(VPortal {
scope_id: Cell::new(Some(self.our_arena_idx)),
link_idx: Cell::new(0),
node,
})
} }
/// Store a value between renders /// Store a value between renders
@@ -451,7 +467,7 @@ impl Scope {
pub(crate) struct BumpFrame { pub(crate) struct BumpFrame {
pub bump: Bump, pub bump: Bump,
pub nodes: RefCell<Vec<*const VNode<'static>>>, pub nodes: Cell<*const VNode<'static>>,
} }
impl BumpFrame { impl BumpFrame {
pub(crate) fn new(capacity: usize) -> Self { pub(crate) fn new(capacity: usize) -> Self {
@@ -463,21 +479,12 @@ impl BumpFrame {
is_static: false, is_static: false,
}); });
let node = bump.alloc(VNode::Text(unsafe { std::mem::transmute(node) })); let node = bump.alloc(VNode::Text(unsafe { std::mem::transmute(node) }));
let nodes = RefCell::new(vec![node as *const _]); let nodes = Cell::new(node as *const _);
Self { bump, nodes } Self { bump, nodes }
} }
pub(crate) fn assign_nodelink(&self, node: &VPortal) {
let mut nodes = self.nodes.borrow_mut();
let len = nodes.len();
nodes.push(node.node);
node.link_idx.set(len);
}
} }
#[test] #[test]
fn sizeof() { fn sizeof() {
dbg!(std::mem::size_of::<Scope>()); dbg!(std::mem::size_of::<ScopeState>());
} }
+50 -84
View File
@@ -24,9 +24,9 @@ pub(crate) struct ScopeArena {
bump: Bump, bump: Bump,
pub pending_futures: RefCell<FxHashSet<ScopeId>>, pub pending_futures: RefCell<FxHashSet<ScopeId>>,
scope_counter: Cell<usize>, scope_counter: Cell<usize>,
pub scopes: RefCell<FxHashMap<ScopeId, *mut Scope>>, pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>, pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
free_scopes: RefCell<Vec<*mut Scope>>, free_scopes: RefCell<Vec<*mut ScopeState>>,
nodes: RefCell<Slab<*const VNode<'static>>>, nodes: RefCell<Slab<*const VNode<'static>>>,
pub(crate) sender: UnboundedSender<SchedulerMsg>, pub(crate) sender: UnboundedSender<SchedulerMsg>,
} }
@@ -68,23 +68,23 @@ impl ScopeArena {
/// Safety: /// Safety:
/// - Obtaining a mutable refernece to any Scope is unsafe /// - Obtaining a mutable refernece to any Scope is unsafe
/// - Scopes use interior mutability when sharing data into components /// - Scopes use interior mutability when sharing data into components
pub(crate) fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
unsafe { self.scopes.borrow().get(id).map(|f| &**f) } unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
} }
pub(crate) unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> { pub(crate) unsafe fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
self.scopes.borrow().get(id).copied() self.scopes.borrow().get(&id).copied()
} }
pub(crate) unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> { pub(crate) unsafe fn get_scope_mut(&self, id: ScopeId) -> Option<&mut ScopeState> {
self.scopes.borrow().get(id).map(|s| &mut **s) self.scopes.borrow().get(&id).map(|s| &mut **s)
} }
pub(crate) fn new_with_key( pub(crate) fn new_with_key(
&self, &self,
fc_ptr: *const (), fc_ptr: *const (),
caller: *const dyn Fn(&Scope) -> Element, caller: *const dyn Fn(&ScopeState) -> Element,
parent_scope: Option<*mut Scope>, parent_scope: Option<*mut ScopeState>,
container: ElementId, container: ElementId,
height: u32, height: u32,
subtree: u32, subtree: u32,
@@ -102,27 +102,27 @@ impl ScopeArena {
scope.our_arena_idx = new_scope_id; scope.our_arena_idx = new_scope_id;
scope.container = container; scope.container = container;
scope.frames[0].nodes.get_mut().push({ // scope.frames[0].nodes.get_mut().push({
let vnode = scope.frames[0] // let vnode = scope.frames[0]
.bump // .bump
.alloc(VNode::Text(scope.frames[0].bump.alloc(VText { // .alloc(VNode::Text(scope.frames[0].bump.alloc(VText {
dom_id: Default::default(), // dom_id: Default::default(),
is_static: false, // is_static: false,
text: "", // text: "",
}))); // })));
unsafe { std::mem::transmute(vnode as *mut VNode) } // unsafe { std::mem::transmute(vnode as *mut VNode) }
}); // });
scope.frames[1].nodes.get_mut().push({ // scope.frames[1].nodes.get_mut().push({
let vnode = scope.frames[1] // let vnode = scope.frames[1]
.bump // .bump
.alloc(VNode::Text(scope.frames[1].bump.alloc(VText { // .alloc(VNode::Text(scope.frames[1].bump.alloc(VText {
dom_id: Default::default(), // dom_id: Default::default(),
is_static: false, // is_static: false,
text: "", // text: "",
}))); // })));
unsafe { std::mem::transmute(vnode as *mut VNode) } // unsafe { std::mem::transmute(vnode as *mut VNode) }
}); // });
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope); let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
debug_assert!(any_item.is_none()); debug_assert!(any_item.is_none());
@@ -136,31 +136,9 @@ impl ScopeArena {
} }
}; };
let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)]; let frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
frames[0].nodes.get_mut().push({ let scope = self.bump.alloc(ScopeState {
let vnode = frames[0]
.bump
.alloc(VNode::Text(frames[0].bump.alloc(VText {
dom_id: Default::default(),
is_static: false,
text: "",
})));
unsafe { std::mem::transmute(vnode as *mut VNode) }
});
frames[1].nodes.get_mut().push({
let vnode = frames[1]
.bump
.alloc(VNode::Text(frames[1].bump.alloc(VText {
dom_id: Default::default(),
is_static: false,
text: "",
})));
unsafe { std::mem::transmute(vnode as *mut VNode) }
});
let scope = self.bump.alloc(Scope {
sender: self.sender.clone(), sender: self.sender.clone(),
container, container,
our_arena_idx: new_scope_id, our_arena_idx: new_scope_id,
@@ -193,13 +171,13 @@ impl ScopeArena {
new_scope_id new_scope_id
} }
pub fn try_remove(&self, id: &ScopeId) -> Option<()> { pub fn try_remove(&self, id: ScopeId) -> Option<()> {
self.ensure_drop_safety(id); self.ensure_drop_safety(id);
// Safety: // Safety:
// - ensure_drop_safety ensures that no references to this scope are in use // - ensure_drop_safety ensures that no references to this scope are in use
// - this raw pointer is removed from the map // - this raw pointer is removed from the map
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(id).unwrap() }; let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
// we're just reusing scopes so we need to clear it out // we're just reusing scopes so we need to clear it out
scope.hook_vals.get_mut().drain(..).for_each(|state| { scope.hook_vals.get_mut().drain(..).for_each(|state| {
@@ -216,9 +194,6 @@ impl ScopeArena {
scope.is_subtree_root.set(false); scope.is_subtree_root.set(false);
scope.subtree.set(0); scope.subtree.set(0);
scope.frames[0].nodes.get_mut().clear();
scope.frames[1].nodes.get_mut().clear();
scope.frames[0].bump.reset(); scope.frames[0].bump.reset();
scope.frames[1].bump.reset(); scope.frames[1].bump.reset();
@@ -257,8 +232,6 @@ impl ScopeArena {
self.nodes.borrow_mut().remove(id.0); self.nodes.borrow_mut().remove(id.0);
} }
// These methods would normally exist on `scope` but they need access to *all* of the scopes
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
/// causing UB in our tree. /// causing UB in our tree.
/// ///
@@ -271,7 +244,7 @@ impl ScopeArena {
/// ///
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped. /// be dropped.
pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) { pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
if let Some(scope) = self.get_scope(scope_id) { if let Some(scope) = self.get_scope(scope_id) {
let mut items = scope.items.borrow_mut(); let mut items = scope.items.borrow_mut();
@@ -284,7 +257,7 @@ impl ScopeArena {
.get() .get()
.expect("VComponents should be associated with a valid Scope"); .expect("VComponents should be associated with a valid Scope");
self.ensure_drop_safety(&scope_id); self.ensure_drop_safety(scope_id);
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
drop_props(); drop_props();
@@ -298,7 +271,7 @@ impl ScopeArena {
} }
} }
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { pub(crate) fn run_scope(&self, id: ScopeId) -> bool {
// Cycle to the next frame and then reset it // Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it. // This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners // Remove all the outdated listeners
@@ -327,25 +300,20 @@ impl ScopeArena {
debug_assert!(items.tasks.is_empty()); debug_assert!(items.tasks.is_empty());
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
scope.wip_frame().nodes.borrow_mut().clear(); // scope.wip_frame().nodes
} }
let render: &dyn Fn(&Scope) -> Element = unsafe { &*scope.caller }; let render: &dyn Fn(&ScopeState) -> Element = unsafe { &*scope.caller };
if let Some(link) = render(scope) {
// right now, it's a panic to render a nodelink from another scope
// todo: enable this. it should (reasonably) work even if it doesnt make much sense
assert_eq!(link.scope_id.get(), Some(*id));
// nodelinks are not assigned when called and must be done so through the create/diff phase
// however, we need to link this one up since it will never be used in diffing
scope.wip_frame().assign_nodelink(&link);
debug_assert_eq!(scope.wip_frame().nodes.borrow().len(), 1);
if let Some(node) = render(scope) {
if !scope.items.borrow().tasks.is_empty() { if !scope.items.borrow().tasks.is_empty() {
self.pending_futures.borrow_mut().insert(*id); self.pending_futures.borrow_mut().insert(id);
} }
let frame = scope.wip_frame();
let node = frame.bump.alloc(node);
frame.nodes.set(unsafe { std::mem::transmute(node) });
// make the "wip frame" contents the "finished frame" // make the "wip frame" contents the "finished frame"
// any future dipping into completed nodes after "render" will go through "fin head" // any future dipping into completed nodes after "render" will go through "fin head"
scope.cycle_frame(); scope.cycle_frame();
@@ -380,24 +348,22 @@ impl ScopeArena {
} }
// The head of the bumpframe is the first linked NodeLink // The head of the bumpframe is the first linked NodeLink
pub fn wip_head(&self, id: &ScopeId) -> &VNode { pub fn wip_head(&self, id: ScopeId) -> &VNode {
let scope = self.get_scope(id).unwrap(); let scope = self.get_scope(id).unwrap();
let frame = scope.wip_frame(); let frame = scope.wip_frame();
let nodes = frame.nodes.borrow(); let node = unsafe { &*frame.nodes.get() };
let node: &VNode = unsafe { &**nodes.get(0).unwrap() };
unsafe { std::mem::transmute::<&VNode, &VNode>(node) } unsafe { std::mem::transmute::<&VNode, &VNode>(node) }
} }
// The head of the bumpframe is the first linked NodeLink // The head of the bumpframe is the first linked NodeLink
pub fn fin_head(&self, id: &ScopeId) -> &VNode { pub fn fin_head(&self, id: ScopeId) -> &VNode {
let scope = self.get_scope(id).unwrap(); let scope = self.get_scope(id).unwrap();
let frame = scope.fin_frame(); let frame = scope.fin_frame();
let nodes = frame.nodes.borrow(); let node = unsafe { &*frame.nodes.get() };
let node: &VNode = unsafe { &**nodes.get(0).unwrap() };
unsafe { std::mem::transmute::<&VNode, &VNode>(node) } unsafe { std::mem::transmute::<&VNode, &VNode>(node) }
} }
pub fn root_node(&self, id: &ScopeId) -> &VNode { pub fn root_node(&self, id: ScopeId) -> &VNode {
self.fin_head(id) self.fin_head(id)
} }
} }
+44 -30
View File
@@ -105,7 +105,8 @@ use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll};
pub struct VirtualDom { pub struct VirtualDom {
base_scope: ScopeId, base_scope: ScopeId,
_root_props: Box<dyn Any>, // it's stored here so the props are dropped when the VirtualDom is dropped
_root_caller: Box<dyn for<'r> Fn(&'r ScopeState) -> Element<'r> + 'static>,
scopes: Box<ScopeArena>, scopes: Box<ScopeArena>,
@@ -132,7 +133,7 @@ impl VirtualDom {
/// ///
/// # Example /// # Example
/// ```rust, ignore /// ```rust, ignore
/// fn Example(cx: Context, props: &()) -> Element { /// fn Example(cx: Scope<()>) -> Element {
/// cx.render(rsx!( div { "hello world" } )) /// cx.render(rsx!( div { "hello world" } ))
/// } /// }
/// ///
@@ -161,8 +162,8 @@ impl VirtualDom {
/// name: &'static str /// name: &'static str
/// } /// }
/// ///
/// fn Example(cx: Context, props: &SomeProps) -> Element { /// fn Example(cx: Scope<SomeProps>) -> Element {
/// cx.render(rsx!{ div{ "hello {cx.name}" } }) /// cx.render(rsx!{ div{ "hello {cx.props.name}" } })
/// } /// }
/// ///
/// let dom = VirtualDom::new(Example); /// let dom = VirtualDom::new(Example);
@@ -194,21 +195,36 @@ impl VirtualDom {
sender: UnboundedSender<SchedulerMsg>, sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>, receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self { ) -> Self {
let scopes = ScopeArena::new(sender.clone()); // move these two things onto the heap so we have stable ptrs
let scopes = Box::new(ScopeArena::new(sender.clone()));
let root_props = Box::new(root_props);
let mut caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) }); // create the root caller which will properly drop its props when the VirtualDom is dropped
let caller_ref: *mut dyn Fn(&Scope) -> Element = caller.as_mut() as *mut _; let mut _root_caller: Box<dyn for<'r> Fn(&'r ScopeState) -> Element<'r> + 'static> =
let base_scope = scopes.new_with_key(root as _, caller_ref, None, ElementId(0), 0, 0); Box::new(move |scope: &ScopeState| -> Element {
// Safety: The props at this pointer can never be moved.
// Also, this closure will never be ran when the VirtualDom is destroyed.
// This is where the root lifetime of the VirtualDom originates.
let props: *const P = root_props.as_ref();
let props = unsafe { &*props };
root(Scope { scope, props })
});
// safety: the raw pointer is aliased or used after this point.
let caller: *mut dyn Fn(&ScopeState) -> Element = _root_caller.as_mut();
let base_scope = scopes.new_with_key(root as _, caller, None, ElementId(0), 0, 0);
let pending_messages = VecDeque::new();
let mut dirty_scopes = IndexSet::new(); let mut dirty_scopes = IndexSet::new();
dirty_scopes.insert(base_scope); dirty_scopes.insert(base_scope);
// todo: add a pending message to the scheduler to start the scheduler?
let pending_messages = VecDeque::new();
Self { Self {
scopes: Box::new(scopes), scopes,
base_scope, base_scope,
receiver, receiver,
_root_props: caller, _root_caller,
pending_messages, pending_messages,
dirty_scopes, dirty_scopes,
sender, sender,
@@ -221,17 +237,17 @@ impl VirtualDom {
/// directly. /// directly.
/// ///
/// # Example /// # Example
pub fn base_scope(&self) -> &Scope { pub fn base_scope(&self) -> &ScopeState {
self.get_scope(&self.base_scope).unwrap() self.get_scope(self.base_scope).unwrap()
} }
/// Get the [`Scope`] for a component given its [`ScopeId`] /// Get the [`ScopeState`] for a component given its [`ScopeId`]
/// ///
/// # Example /// # Example
/// ///
/// ///
/// ///
pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a Scope> { pub fn get_scope<'a>(&'a self, id: ScopeId) -> Option<&'a ScopeState> {
self.scopes.get_scope(id) self.scopes.get_scope(id)
} }
@@ -388,11 +404,11 @@ impl VirtualDom {
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
self.dirty_scopes self.dirty_scopes
.retain(|id| scopes.get_scope(id).is_some()); .retain(|id| scopes.get_scope(*id).is_some());
self.dirty_scopes.sort_by(|a, b| { self.dirty_scopes.sort_by(|a, b| {
let h1 = scopes.get_scope(a).unwrap().height; let h1 = scopes.get_scope(*a).unwrap().height;
let h2 = scopes.get_scope(b).unwrap().height; let h2 = scopes.get_scope(*b).unwrap().height;
h1.cmp(&h2).reverse() h1.cmp(&h2).reverse()
}); });
@@ -400,15 +416,13 @@ impl VirtualDom {
if !ran_scopes.contains(&scopeid) { if !ran_scopes.contains(&scopeid) {
ran_scopes.insert(scopeid); ran_scopes.insert(scopeid);
if self.scopes.run_scope(&scopeid) { if self.scopes.run_scope(scopeid) {
let (old, new) = ( let (old, new) =
self.scopes.wip_head(&scopeid), (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
self.scopes.fin_head(&scopeid),
);
diff_state.stack.push(DiffInstruction::Diff { new, old }); diff_state.stack.push(DiffInstruction::Diff { new, old });
diff_state.stack.scope_stack.push(scopeid); diff_state.stack.scope_stack.push(scopeid);
let scope = scopes.get_scope(&scopeid).unwrap(); let scope = scopes.get_scope(scopeid).unwrap();
diff_state.stack.element_stack.push(scope.container); diff_state.stack.element_stack.push(scope.container);
} }
} }
@@ -455,10 +469,10 @@ impl VirtualDom {
let mut diff_state = DiffState::new(&self.scopes); let mut diff_state = DiffState::new(&self.scopes);
let scope_id = self.base_scope; let scope_id = self.base_scope;
if self.scopes.run_scope(&scope_id) { if self.scopes.run_scope(scope_id) {
diff_state diff_state
.stack .stack
.create_node(self.scopes.fin_head(&scope_id), MountType::Append); .create_node(self.scopes.fin_head(scope_id), MountType::Append);
diff_state.stack.element_stack.push(ElementId(0)); diff_state.stack.element_stack.push(ElementId(0));
diff_state.stack.scope_stack.push(scope_id); diff_state.stack.scope_stack.push(scope_id);
@@ -497,7 +511,7 @@ impl VirtualDom {
/// ///
/// let edits = dom.diff(); /// let edits = dom.diff();
/// ``` /// ```
pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option<Mutations<'a>> { pub fn hard_diff<'a>(&'a mut self, scope_id: ScopeId) -> Option<Mutations<'a>> {
let mut diff_machine = DiffState::new(&self.scopes); let mut diff_machine = DiffState::new(&self.scopes);
if self.scopes.run_scope(scope_id) { if self.scopes.run_scope(scope_id) {
diff_machine.force_diff = true; diff_machine.force_diff = true;
@@ -519,7 +533,7 @@ impl VirtualDom {
/// let nodes = dom.render_nodes(rsx!("div")); /// let nodes = dom.render_nodes(rsx!("div"));
/// ``` /// ```
pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> { pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
let scope = self.scopes.get_scope(&self.base_scope).unwrap(); let scope = self.scopes.get_scope(self.base_scope).unwrap();
let frame = scope.wip_frame(); let frame = scope.wip_frame();
let factory = NodeFactory { bump: &frame.bump }; let factory = NodeFactory { bump: &frame.bump };
let node = lazy_nodes.unwrap().call(factory); let node = lazy_nodes.unwrap().call(factory);
@@ -731,7 +745,7 @@ impl<'a> Future for PollTasks<'a> {
let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![]; let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
// Poll every scope manually // Poll every scope manually
for fut in self.0.pending_futures.borrow().iter() { for fut in self.0.pending_futures.borrow().iter().copied() {
let scope = self.0.get_scope(fut).expect("Scope should never be moved"); let scope = self.0.get_scope(fut).expect("Scope should never be moved");
let mut items = scope.items.borrow_mut(); let mut items = scope.items.borrow_mut();
@@ -746,7 +760,7 @@ impl<'a> Future for PollTasks<'a> {
} }
if unfinished_tasks.is_empty() { if unfinished_tasks.is_empty() {
scopes_to_clear.push(*fut); scopes_to_clear.push(fut);
} }
items.tasks.extend(unfinished_tasks.drain(..)); items.tasks.extend(unfinished_tasks.drain(..));
+5 -5
View File
@@ -10,7 +10,7 @@ fn test_borrowed_state() {
let _ = VirtualDom::new(Parent); let _ = VirtualDom::new(Parent);
} }
fn Parent(cx: Context, _props: &()) -> Element { fn Parent(cx: Scope<()>) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f); let value = cx.use_hook(|_| String::new(), |f| &*f);
cx.render(rsx! { cx.render(rsx! {
@@ -28,11 +28,11 @@ struct ChildProps<'a> {
name: &'a str, name: &'a str,
} }
fn Child(cx: Context, props: &ChildProps) -> Element { fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
h1 { "it's nested" } h1 { "it's nested" }
Child2 { name: props.name } Child2 { name: cx.props.name }
} }
}) })
} }
@@ -42,8 +42,8 @@ struct Grandchild<'a> {
name: &'a str, name: &'a str,
} }
fn Child2(cx: Context, props: &Grandchild) -> Element { fn Child2<'a>(cx: Scope<'a, Grandchild<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { "Hello {props.name}!" } div { "Hello {cx.props.name}!" }
}) })
} }
+11 -11
View File
@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test] #[test]
fn test_original_diff() { fn test_original_diff() {
static APP: Component<()> = |cx, props| { static APP: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
div { div {
@@ -57,7 +57,7 @@ fn test_original_diff() {
#[test] #[test]
fn create() { fn create() {
static APP: Component<()> = |cx, props| { static APP: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
div { div {
@@ -120,7 +120,7 @@ fn create() {
#[test] #[test]
fn create_list() { fn create_list() {
static APP: Component<()> = |cx, props| { static APP: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
{(0..3).map(|f| rsx!{ div { {(0..3).map(|f| rsx!{ div {
"hello" "hello"
@@ -169,7 +169,7 @@ fn create_list() {
#[test] #[test]
fn create_simple() { fn create_simple() {
static APP: Component<()> = |cx, props| { static APP: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div {} div {}
div {} div {}
@@ -207,7 +207,7 @@ fn create_simple() {
} }
#[test] #[test]
fn create_components() { fn create_components() {
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
Child { "abc1" } Child { "abc1" }
Child { "abc2" } Child { "abc2" }
@@ -215,15 +215,15 @@ fn create_components() {
}) })
}; };
#[derive(Props, PartialEq)] #[derive(Props)]
struct ChildProps { struct ChildProps<'a> {
children: Element, children: Element<'a>,
} }
fn Child(cx: Context, props: &ChildProps) -> Element { fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
h1 {} h1 {}
div { {&props.children} } div { {&cx.props.children} }
p {} p {}
}) })
} }
@@ -273,7 +273,7 @@ fn create_components() {
} }
#[test] #[test]
fn anchors() { fn anchors() {
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })} {true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })} {false.then(|| rsx!{ div { "goodbye" } })}
+1 -1
View File
@@ -15,7 +15,7 @@ mod test_logging;
fn new_dom() -> VirtualDom { fn new_dom() -> VirtualDom {
const IS_LOGGING_ENABLED: bool = false; const IS_LOGGING_ENABLED: bool = false;
test_logging::set_up_logging(IS_LOGGING_ENABLED); test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new(|cx, props| todo!()) VirtualDom::new(|cx| todo!())
} }
use DomEdit::*; use DomEdit::*;
+20 -20
View File
@@ -22,8 +22,8 @@ fn manual_diffing() {
value: Shared<&'static str>, value: Shared<&'static str>,
} }
static App: Component<AppProps> = |cx, props| { static App: Component<AppProps> = |cx| {
let val = props.value.lock().unwrap(); let val = cx.props.value.lock().unwrap();
cx.render(rsx! { div { "{val}" } }) cx.render(rsx! { div { "{val}" } })
}; };
@@ -46,8 +46,8 @@ fn manual_diffing() {
#[test] #[test]
fn events_generate() { fn events_generate() {
static App: Component<()> = |cx, _| { static App: Component<()> = |cx| {
let mut count = use_state(cx, || 0); let mut count = use_state(&cx, || 0);
let inner = match *count { let inner = match *count {
0 => { 0 => {
@@ -105,8 +105,8 @@ fn events_generate() {
#[test] #[test]
fn components_generate() { fn components_generate() {
static App: Component<()> = |cx, _| { static App: Component<()> = |cx| {
let mut render_phase = use_state(cx, || 0); let mut render_phase = use_state(&cx, || 0);
render_phase += 1; render_phase += 1;
cx.render(match *render_phase { cx.render(match *render_phase {
@@ -122,7 +122,7 @@ fn components_generate() {
}) })
}; };
static Child: Component<()> = |cx, _| { static Child: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
h1 {} h1 {}
}) })
@@ -141,7 +141,7 @@ fn components_generate() {
] ]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
@@ -153,7 +153,7 @@ fn components_generate() {
] ]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
@@ -165,7 +165,7 @@ fn components_generate() {
] ]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
@@ -174,13 +174,13 @@ fn components_generate() {
] ]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },] [CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
@@ -192,7 +192,7 @@ fn components_generate() {
] ]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
@@ -208,7 +208,7 @@ fn components_generate() {
] ]
); );
let edits = dom.hard_diff(&ScopeId(0)).unwrap(); let edits = dom.hard_diff(ScopeId(0)).unwrap();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
@@ -222,8 +222,8 @@ fn components_generate() {
#[test] #[test]
fn component_swap() { fn component_swap() {
// simple_logger::init(); // simple_logger::init();
static App: Component<()> = |cx, _| { static App: Component<()> = |cx| {
let mut render_phase = use_state(cx, || 0); let mut render_phase = use_state(&cx, || 0);
render_phase += 1; render_phase += 1;
cx.render(match *render_phase { cx.render(match *render_phase {
@@ -261,7 +261,7 @@ fn component_swap() {
}) })
}; };
static NavBar: Component<()> = |cx, _| { static NavBar: Component<()> = |cx| {
println!("running navbar"); println!("running navbar");
cx.render(rsx! { cx.render(rsx! {
h1 { h1 {
@@ -271,7 +271,7 @@ fn component_swap() {
}) })
}; };
static NavLink: Component<()> = |cx, _| { static NavLink: Component<()> = |cx| {
println!("running navlink"); println!("running navlink");
cx.render(rsx! { cx.render(rsx! {
h1 { h1 {
@@ -280,7 +280,7 @@ fn component_swap() {
}) })
}; };
static Dashboard: Component<()> = |cx, _| { static Dashboard: Component<()> = |cx| {
println!("running dashboard"); println!("running dashboard");
cx.render(rsx! { cx.render(rsx! {
div { div {
@@ -289,7 +289,7 @@ fn component_swap() {
}) })
}; };
static Results: Component<()> = |cx, _| { static Results: Component<()> = |cx| {
println!("running results"); println!("running results");
cx.render(rsx! { cx.render(rsx! {
div { div {
+2 -2
View File
@@ -13,12 +13,12 @@ mod test_logging;
fn shared_state_test() { fn shared_state_test() {
struct MySharedState(&'static str); struct MySharedState(&'static str);
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.provide_state(MySharedState("world!")); cx.provide_state(MySharedState("world!"));
cx.render(rsx!(Child {})) cx.render(rsx!(Child {}))
}; };
static Child: Component<()> = |cx, props| { static Child: Component<()> = |cx| {
let shared = cx.consume_state::<MySharedState>()?; let shared = cx.consume_state::<MySharedState>()?;
cx.render(rsx!("Hello, {shared.0}")) cx.render(rsx!("Hello, {shared.0}"))
}; };
+6 -6
View File
@@ -19,7 +19,7 @@ use DomEdit::*;
#[test] #[test]
fn app_runs() { fn app_runs() {
static App: Component<()> = |cx, props| rsx!(cx, div{"hello"} ); static App: Component<()> = |cx| rsx!(cx, div{"hello"} );
let mut vdom = VirtualDom::new(App); let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild(); let edits = vdom.rebuild();
@@ -27,7 +27,7 @@ fn app_runs() {
#[test] #[test]
fn fragments_work() { fn fragments_work() {
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
div{"hello"} div{"hello"}
div{"goodbye"} div{"goodbye"}
@@ -41,7 +41,7 @@ fn fragments_work() {
#[test] #[test]
fn lists_work() { fn lists_work() {
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
{(0..6).map(|f| rsx!(span{ "{f}" }))} {(0..6).map(|f| rsx!(span{ "{f}" }))}
@@ -54,7 +54,7 @@ fn lists_work() {
#[test] #[test]
fn conditional_rendering() { fn conditional_rendering() {
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
{true.then(|| rsx!(span{ "a" }))} {true.then(|| rsx!(span{ "a" }))}
@@ -87,13 +87,13 @@ fn conditional_rendering() {
#[test] #[test]
fn child_components() { fn child_components() {
static App: Component<()> = |cx, props| { static App: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
{true.then(|| rsx!(Child { }))} {true.then(|| rsx!(Child { }))}
{false.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))}
)) ))
}; };
static Child: Component<()> = |cx, props| { static Child: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
h1 {"goodbye"} h1 {"goodbye"}
+4 -4
View File
@@ -1,4 +1,4 @@
use dioxus_core::{prelude::Context, ScopeId}; use dioxus_core::{ScopeId, ScopeState};
use std::{ use std::{
cell::{Cell, Ref, RefCell, RefMut}, cell::{Cell, Ref, RefCell, RefMut},
collections::HashSet, collections::HashSet,
@@ -59,7 +59,7 @@ impl<T> ProvidedStateInner<T> {
/// ///
/// ///
/// ///
pub fn use_shared_state<'a, T: 'static>(cx: Context<'a>) -> Option<UseSharedState<'a, T>> { pub fn use_shared_state<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
cx.use_hook( cx.use_hook(
|_| { |_| {
let scope_id = cx.scope_id(); let scope_id = cx.scope_id();
@@ -110,7 +110,7 @@ impl<T> Drop for SharedStateInner<T> {
} }
pub struct UseSharedState<'a, T: 'static> { pub struct UseSharedState<'a, T: 'static> {
pub(crate) cx: Context<'a>, pub(crate) cx: &'a ScopeState,
pub(crate) value: &'a Rc<RefCell<T>>, pub(crate) value: &'a Rc<RefCell<T>>,
pub(crate) root: &'a Rc<RefCell<ProvidedStateInner<T>>>, pub(crate) root: &'a Rc<RefCell<ProvidedStateInner<T>>>,
pub(crate) needs_notification: &'a Cell<bool>, pub(crate) needs_notification: &'a Cell<bool>,
@@ -175,7 +175,7 @@ where
/// ///
/// ///
/// ///
pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T) { pub fn use_provide_state<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) {
cx.use_hook( cx.use_hook(
|_| { |_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner { let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
+4 -5
View File
@@ -1,4 +1,4 @@
use dioxus_core::Context; use dioxus_core::ScopeState;
use futures::Future; use futures::Future;
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
@@ -7,10 +7,9 @@ use std::{
}; };
pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>( pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
cx: Context<'a>, cx: &'a ScopeState,
mut f: impl FnMut() -> F + 'a, mut f: impl FnMut() -> F + 'a,
) -> CoroutineHandle { ) -> CoroutineHandle<'a> {
//
cx.use_hook( cx.use_hook(
move |_| State { move |_| State {
running: Default::default(), running: Default::default(),
@@ -56,7 +55,7 @@ struct State {
} }
pub struct CoroutineHandle<'a> { pub struct CoroutineHandle<'a> {
cx: Context<'a>, cx: &'a ScopeState,
inner: &'a State, inner: &'a State,
} }
impl Clone for CoroutineHandle<'_> { impl Clone for CoroutineHandle<'_> {
+7 -7
View File
@@ -4,7 +4,7 @@
//! //!
//! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions. //! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions.
use dioxus_core::prelude::Context; use dioxus_core::prelude::ScopeState;
use futures::Future; use futures::Future;
use std::{ use std::{
cell::{Cell, Ref, RefCell, RefMut}, cell::{Cell, Ref, RefCell, RefMut},
@@ -13,7 +13,7 @@ use std::{
rc::Rc, rc::Rc,
}; };
pub fn use_model<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseModel<T> { pub fn use_model<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseModel<'a, T> {
cx.use_hook( cx.use_hook(
|_| UseModelInner { |_| UseModelInner {
update_scheduled: Cell::new(false), update_scheduled: Cell::new(false),
@@ -70,16 +70,16 @@ impl<'a, T: 'static> UseModel<'a, T> {
(self.read(), self) (self.read(), self)
} }
pub fn start(&self, f: impl FnOnce() -> ModelTask) { pub fn start(&self, _f: impl FnOnce() -> ModelTask) {
todo!() todo!()
} }
} }
// keep a coroutine going // keep a coroutine going
pub fn use_model_coroutine<T, F: Future<Output = ()> + 'static>( pub fn use_model_coroutine<'a, T, F: Future<Output = ()> + 'static>(
cx: Context, cx: &'a ScopeState,
model: UseModel<T>, _model: UseModel<T>,
f: impl FnOnce(AppModels) -> F, _f: impl FnOnce(AppModels) -> F,
) -> UseModelCoroutine { ) -> UseModelCoroutine {
cx.use_hook( cx.use_hook(
|_| { |_| {
+2 -2
View File
@@ -3,9 +3,9 @@ use std::{
rc::Rc, rc::Rc,
}; };
use dioxus_core::Context; use dioxus_core::ScopeState;
pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> { pub fn use_ref<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseRef<'a, T> {
cx.use_hook( cx.use_hook(
|_| UseRefInner { |_| UseRefInner {
update_scheduled: Cell::new(false), update_scheduled: Cell::new(false),
+24 -15
View File
@@ -1,4 +1,4 @@
use dioxus_core::prelude::Context; use dioxus_core::prelude::*;
use std::{ use std::{
cell::{Cell, Ref, RefCell, RefMut}, cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display}, fmt::{Debug, Display},
@@ -6,11 +6,21 @@ use std::{
rc::Rc, rc::Rc,
}; };
pub trait UseStateA<'a, T> {
fn use_state(&self, initial_state_fn: impl FnOnce() -> T) -> UseState<'a, T>;
}
impl<'a, P, T> UseStateA<'a, T> for Scope<'a, P> {
fn use_state(&self, initial_state_fn: impl FnOnce() -> T) -> UseState<'a, T> {
use_state(self.scope, initial_state_fn)
}
}
/// Store state between component renders! /// Store state between component renders!
/// ///
/// ## Dioxus equivalent of useState, designed for Rust /// ## Dioxus equivalent of useState, designed for Rust
/// ///
/// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and /// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render. /// modify state between component renders. When the state is updated, the component will re-render.
/// ///
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system. /// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
@@ -34,23 +44,22 @@ use std::{
/// ///
/// ///
/// Usage: /// Usage:
/// ```ignore
/// const Example: FC<()> = |cx, props|{
/// let counter = use_state(cx, || 0);
/// let increment = |_| counter += 1;
/// let decrement = |_| counter += 1;
/// ///
/// html! { /// ```ignore
/// <div> /// const Example: Component<()> = |cx| {
/// <h1>"Counter: {counter}" </h1> /// let counter = use_state(&cx, || 0);
/// <button onclick={increment}> "Increment" </button> ///
/// <button onclick={decrement}> "Decrement" </button> /// cx.render(rsx! {
/// </div> /// div {
/// } /// h1 { "Counter: {counter}" }
/// button { onclick: move |_| counter += 1, "Increment" }
/// button { onclick: move |_| counter -= 1, "Decrement" }
/// }
/// ))
/// } /// }
/// ``` /// ```
pub fn use_state<'a, T: 'static>( pub fn use_state<'a, T: 'static>(
cx: Context<'a>, cx: &'a ScopeState,
initial_state_fn: impl FnOnce() -> T, initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> { ) -> UseState<'a, T> {
cx.use_hook( cx.use_hook(
+8 -8
View File
@@ -1,11 +1,11 @@
use dioxus_core::prelude::Context; // use dioxus_core::prelude::Context;
use std::{ // use std::{
borrow::{Borrow, BorrowMut}, // borrow::{Borrow, BorrowMut},
cell::{Cell, Ref, RefCell, RefMut}, // cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display}, // fmt::{Debug, Display},
ops::Not, // ops::Not,
rc::Rc, // rc::Rc,
}; // };
// /// Store state between component renders! // /// Store state between component renders!
// /// // ///
+9 -9
View File
@@ -66,7 +66,7 @@ impl<R: Routable> RouterService<R> {
/// This hould only be used once per app /// This hould only be used once per app
/// ///
/// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
pub fn use_router<R: Routable>(cx: Context, mut parse: impl FnMut(&str) -> R + 'static) -> &R { pub fn use_router<R: Routable>(cx: &ScopeState, mut parse: impl FnMut(&str) -> R + 'static) -> &R {
// for the web, attach to the history api // for the web, attach to the history api
cx.use_hook( cx.use_hook(
|f| { |f| {
@@ -133,12 +133,12 @@ pub fn use_router<R: Routable>(cx: Context, mut parse: impl FnMut(&str) -> R + '
) )
} }
pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<R>>> { pub fn use_router_service<R: Routable>(cx: &ScopeState) -> Option<&Rc<RouterService<R>>> {
cx.use_hook(|_| cx.consume_state::<RouterService<R>>(), |f| f.as_ref()) cx.use_hook(|_| cx.consume_state::<RouterService<R>>(), |f| f.as_ref())
} }
#[derive(Props)] #[derive(Props)]
pub struct LinkProps<R: Routable> { pub struct LinkProps<'a, R: Routable> {
to: R, to: R,
/// The url that gets pushed to the history stack /// The url that gets pushed to the history stack
@@ -157,16 +157,16 @@ pub struct LinkProps<R: Routable> {
href: fn(&R) -> String, href: fn(&R) -> String,
#[builder(default)] #[builder(default)]
children: Element, children: Element<'a>,
} }
pub fn Link<R: Routable>(cx: Context, props: &LinkProps<R>) -> Element { pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
let service = use_router_service::<R>(cx)?; let service = use_router_service::<R>(&cx)?;
cx.render(rsx! { cx.render(rsx! {
a { a {
href: format_args!("{}", (props.href)(&props.to)), href: format_args!("{}", (cx.props.href)(&cx.props.to)),
onclick: move |_| service.push_route(props.to.clone()), onclick: move |_| service.push_route(cx.props.to.clone()),
{&props.children}, // todo!() {&cx.props.children},
} }
}) })
} }
+7 -10
View File
@@ -94,7 +94,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
"{:}", "{:}",
TextRenderer { TextRenderer {
cfg: SsrConfig::default(), cfg: SsrConfig::default(),
root: vdom.get_scope(&scope).unwrap().root_node(), root: vdom.get_scope(scope).unwrap().root_node(),
vdom: Some(vdom) vdom: Some(vdom)
} }
)) ))
@@ -158,9 +158,6 @@ impl<'a> TextRenderer<'a, '_> {
} }
write!(f, "<!-- -->")?; write!(f, "<!-- -->")?;
} }
VNode::Portal(link) => {
todo!();
}
VNode::Element(el) => { VNode::Element(el) => {
if self.cfg.indent { if self.cfg.indent {
for _ in 0..il { for _ in 0..il {
@@ -247,7 +244,7 @@ impl<'a> TextRenderer<'a, '_> {
let idx = vcomp.associated_scope.get().unwrap(); let idx = vcomp.associated_scope.get().unwrap();
if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) { if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
let new_node = vdom.get_scope(&idx).unwrap().root_node(); let new_node = vdom.get_scope(idx).unwrap().root_node();
self.html_render(new_node, f, il + 1)?; self.html_render(new_node, f, il + 1)?;
} else { } else {
} }
@@ -301,13 +298,13 @@ mod tests {
use dioxus_core_macro::*; use dioxus_core_macro::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
static SIMPLE_APP: Component<()> = |cx, _| { static SIMPLE_APP: Component<()> = |cx| {
cx.render(rsx!(div { cx.render(rsx!(div {
"hello world!" "hello world!"
})) }))
}; };
static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx, _| { static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
title: "About W3Schools" title: "About W3Schools"
@@ -326,14 +323,14 @@ mod tests {
}) })
}; };
static NESTED_APP: Component<()> = |cx, _| { static NESTED_APP: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
div { div {
SIMPLE_APP {} SIMPLE_APP {}
} }
)) ))
}; };
static FRAGMENT_APP: Component<()> = |cx, _| { static FRAGMENT_APP: Component<()> = |cx| {
cx.render(rsx!( cx.render(rsx!(
div { "f1" } div { "f1" }
div { "f2" } div { "f2" }
@@ -389,7 +386,7 @@ mod tests {
#[test] #[test]
fn styles() { fn styles() {
static STLYE_APP: Component<()> = |cx, _| { static STLYE_APP: Component<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { color: "blue", font_size: "46px" } div { color: "blue", font_size: "46px" }
}) })
+1 -1
View File
@@ -175,7 +175,7 @@ struct ActionButtonProps<'a> {
onclick: &'a dyn Fn(), onclick: &'a dyn Fn(),
} }
fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element { fn ActionButton(cx: Scope, props: &ActionButtonProps) -> Element {
rsx!(cx, div { class: "col-sm-6 smallpad" rsx!(cx, div { class: "col-sm-6 smallpad"
button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(), button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(),
"{props.name}" "{props.name}"