wip: staticify?

This commit is contained in:
Jonathan Kelley committed 2021-03-11 12:27:01 -05:00
1 parent 78d093a945
commit 5ad81885e4
21 files changed
+429 -342

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ members = [
"packages/dioxus", "packages/dioxus",
"packages/core-macro", "packages/core-macro",
"packages/core", "packages/core",
# "packages/web", "packages/web",
# "packages/router", # "packages/router",
# "packages/ssr", # "packages/ssr",
# "packages/webview", # "packages/webview",
+1 -1
View File
@@ -16,7 +16,7 @@ once_cell = "1.7.2"
proc-macro-hack = "0.5.19" proc-macro-hack = "0.5.19"
proc-macro2 = "1.0.6" proc-macro2 = "1.0.6"
quote = "1.0" quote = "1.0"
syn = { version = "1.0.11", features = ["full"] } syn = { version = "1.0.11", features = ["full", "extra-traits"] }
# testing # testing
[dev-dependencies] [dev-dependencies]
-1
View File
@@ -11,7 +11,6 @@ use proc_macro2::TokenStream;
use syn::parse::Error; use syn::parse::Error;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use quote::quote; use quote::quote;
pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> { pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
+2 -10
View File
@@ -10,10 +10,8 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
# dodrio-derive = { path = "../html-macro-2", version = "0.1.0" }
# dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
# dioxus-html-2 = { path = "../html-macro-2", version = "0.1.0" }
dioxus-core-macro = { path = "../core-macro", version = "0.1.1" } dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
# Backs some static data # Backs some static data
once_cell = "1.5.2" once_cell = "1.5.2"
@@ -22,18 +20,12 @@ generational-arena = { version = "0.2.8", features = ["serde"] }
# Bumpalo backs the VNode creation # Bumpalo backs the VNode creation
bumpalo = { version = "3.6.0", features = ["collections"] } bumpalo = { version = "3.6.0", features = ["collections"] }
owning_ref = "0.4.1"
# all the arenas 👿 # all the arenas 👿
typed-arena = "2.0.1" typed-arena = "2.0.1"
toolshed = "0.8.1"
id-arena = "2.2.1" id-arena = "2.2.1"
thiserror = "1.0.23" thiserror = "1.0.23"
fxhash = "0.2.1" fxhash = "0.2.1"
longest-increasing-subsequence = "0.1.0" longest-increasing-subsequence = "0.1.0"
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
log = "0.4.14" log = "0.4.14"
pretty_env_logger = "0.4.0" uuid = {version = "0.8.2", features=["serde", "v4"]}
fstrings = "0.2.3"
# ouroboros = "0.8.0"
# hashbrown = { version = "0.9.1", features = ["bumpalo"] }
+5 -2
View File
@@ -7,8 +7,6 @@
fn main() {} fn main() {}
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
struct Props { struct Props {
@@ -73,9 +71,14 @@ impl PartialEq for ChildProps<'_> {
impl<'a> Properties for ChildProps<'a> { impl<'a> Properties for ChildProps<'a> {
type Builder = ChildPropsBuilder<'a, ((), ())>; type Builder = ChildPropsBuilder<'a, ((), ())>;
type StaticOutput = ChildProps<'static>;
fn builder() -> <Self as Properties>::Builder { fn builder() -> <Self as Properties>::Builder {
ChildProps::builder() ChildProps::builder()
} }
unsafe fn into_static(self) -> Self::StaticOutput {
unsafe { std::mem::transmute(self) }
}
} }
fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree { fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree {
+5 -3
View File
@@ -1,9 +1,6 @@
use dioxus_core::component::fc_to_builder; use dioxus_core::component::fc_to_builder;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
static BLAH: FC<()> = |ctx, _props| { static BLAH: FC<()> = |ctx, _props| {
let g = "asd".to_string(); let g = "asd".to_string();
ctx.render(rsx! { ctx.render(rsx! {
@@ -30,7 +27,12 @@ fn main() {}
impl Properties for ExampleProps { impl Properties for ExampleProps {
type Builder = ExamplePropsBuilder<((),)>; type Builder = ExamplePropsBuilder<((),)>;
type StaticOutput = ExampleProps;
fn builder() -> Self::Builder { fn builder() -> Self::Builder {
ExampleProps::builder() ExampleProps::builder()
} }
unsafe fn into_static(self) -> Self {
self
}
} }
-4
View File
@@ -1,12 +1,8 @@
// #[macro_use] // #[macro_use]
extern crate fstrings;
// use dioxus_core::ifmt; // use dioxus_core::ifmt;
// use fstrings::format_args_f; // use fstrings::format_args_f;
fn main() { fn main() {
let bump = bumpalo::Bump::new(); let bump = bumpalo::Bump::new();
let _b = &bump; let _b = &bump;
+5
View File
@@ -27,7 +27,12 @@ static Example: FC<SomeProps> = |ctx, _props| {
// toodo: derive this // toodo: derive this
impl Properties for SomeProps { impl Properties for SomeProps {
type Builder = SomePropsBuilder<((),)>; type Builder = SomePropsBuilder<((),)>;
type StaticOutput = SomeProps;
fn builder() -> Self::Builder { fn builder() -> Self::Builder {
SomeProps::builder() SomeProps::builder()
} }
unsafe fn into_static(self) -> Self {
self
}
} }
+7
View File
@@ -11,7 +11,9 @@ pub type ScopeIdx = generational_arena::Index;
pub trait Properties: PartialEq { pub trait Properties: PartialEq {
type Builder; type Builder;
type StaticOutput: Properties + 'static;
fn builder() -> Self::Builder; fn builder() -> Self::Builder;
unsafe fn into_static(self) -> Self::StaticOutput;
} }
pub struct EmptyBuilder; pub struct EmptyBuilder;
@@ -23,10 +25,15 @@ impl EmptyBuilder {
impl Properties for () { impl Properties for () {
type Builder = EmptyBuilder; type Builder = EmptyBuilder;
type StaticOutput = ();
fn builder() -> Self::Builder { fn builder() -> Self::Builder {
EmptyBuilder {} EmptyBuilder {}
} }
unsafe fn into_static(self) -> Self::StaticOutput {
std::mem::transmute(self)
}
} }
pub fn fc_to_builder<T: Properties>(_f: FC<T>) -> T::Builder { pub fn fc_to_builder<T: Properties>(_f: FC<T>) -> T::Builder {
+135 -101
View File
@@ -32,13 +32,18 @@
//! //!
//! More info on how to improve this diffing algorithm: //! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{ use crate::{innerlude::*, scope::Scoped};
innerlude::*,
};
use bumpalo::Bump; use bumpalo::Bump;
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use generational_arena::Arena;
use uuid::Uuid;
use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; use std::{
cell::{RefCell, RefMut},
cmp::Ordering,
collections::VecDeque,
rc::Rc,
};
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while /// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event. /// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
@@ -50,20 +55,29 @@ use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc};
/// different cursor position. /// different cursor position.
/// ///
/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via /// that were modified by the eventtrigger. This prevents doubly evaluating components if they were both updated via
/// subscriptions and props changes. /// subscriptions and props changes.
pub struct DiffMachine<'a> { pub struct DiffMachine<'a> {
pub change_list: EditMachine<'a>, pub change_list: EditMachine<'a>,
pub diffed: FxHashSet<ScopeIdx>, pub diffed: FxHashSet<ScopeIdx>,
pub need_to_diff: FxHashSet<ScopeIdx>, pub lifecycle_events: VecDeque<LifeCycleEvent>,
}
#[derive(Debug)]
pub enum LifeCycleEvent {
Mount { caller: Caller, id: Uuid },
PropsChanged,
SameProps,
Remove,
} }
impl<'a> DiffMachine<'a> { impl<'a> DiffMachine<'a> {
pub fn new(bump: &'a Bump) -> Self { pub fn new() -> Self {
// pub fn new(bump: &'a Bump) -> Self {
Self { Self {
change_list: EditMachine::new(bump), lifecycle_events: VecDeque::new(),
change_list: EditMachine::new(),
diffed: FxHashSet::default(), diffed: FxHashSet::default(),
need_to_diff: FxHashSet::default(),
} }
} }
@@ -111,18 +125,31 @@ impl<'a> DiffMachine<'a> {
self.diff_children(eold.children, enew.children); self.diff_children(eold.children, enew.children);
} }
(VNode::Component(_cold), VNode::Component(_cnew)) => { (VNode::Component(cold), VNode::Component(cnew)) => {
// if cold.comp != cnew.comp { todo!("should not happen")
// // queue an event to mount this new component // if cold.caller_ref != cnew.caller_ref {
// // todo: create a stable addr
// self.lifecycle_events.push_back(LifeCycleEvent::Mount);
// return; // return;
// } // }
// compare props.... somehow..... // let comparator = &cnew.comparator.0;
// let old_props = cold.raw_props.as_ref();
// let has_changed = comparator(old_props);
todo!("Usage of component VNode not currently supported"); // if has_changed {
// self.lifecycle_events
// .push_back(LifeCycleEvent::PropsChanged);
// return;
// }
// the component is the same and hasn't changed
// migrate props over (so the addr remains stable) but don't rerun the component
} }
// todo: knock out any listeners
(_, VNode::Component(_new)) => { (_, VNode::Component(_new)) => {
// self.lifecycle_events.push_back(LifeCycleEvent::Mount);
// we have no stable reference to work from // we have no stable reference to work from
// push the lifecycle event onto the queue // push the lifecycle event onto the queue
// self.lifecycle_events // self.lifecycle_events
@@ -151,6 +178,100 @@ impl<'a> DiffMachine<'a> {
} }
} }
// Emit instructions to create the given virtual node.
//
// The change list stack may have any shape upon entering this function:
//
// [...]
//
// When this function returns, the new node is on top of the change list stack:
//
// [... node]
fn create(&mut self, node: &VNode<'a>) {
debug_assert!(self.change_list.traversal_is_committed());
match node {
VNode::Text(VText { text }) => {
self.change_list.create_text_node(text);
}
VNode::Element(&VElement {
key: _,
tag_name,
listeners,
attributes,
children,
namespace,
}) => {
// log::info!("Creating {:#?}", node);
if let Some(namespace) = namespace {
self.change_list.create_element_ns(tag_name, namespace);
} else {
self.change_list.create_element(tag_name);
}
listeners.iter().enumerate().for_each(|(_id, listener)| {
self.change_list
.new_event_listener(listener.event, listener.scope, listener.id)
});
for attr in attributes {
self.change_list
.set_attribute(&attr.name, &attr.value, namespace.is_some());
}
// Fast path: if there is a single text child, it is faster to
// create-and-append the text node all at once via setting the
// parent's `textContent` in a single change list instruction than
// to emit three instructions to (1) create a text node, (2) set its
// text content, and finally (3) append the text node to this
// parent.
if children.len() == 1 {
if let VNode::Text(VText { text }) = children[0] {
self.change_list.set_text(text);
return;
}
}
for child in children {
self.create(child);
self.change_list.append_child();
}
}
/*
todo: integrate re-entrace
*/
// NodeKind::Cached(ref c) => {
// cached_roots.insert(c.id);
// let (node, template) = cached_set.get(c.id);
// if let Some(template) = template {
// create_with_template(
// cached_set,
// self.change_list,
// registry,
// template,
// node,
// cached_roots,
// );
// } else {
// create(cached_set, change_list, registry, node, cached_roots);
// }
// }
VNode::Component(component) => {
self.change_list
.create_text_node("placeholder for vcomponent");
let id = uuid::Uuid::new_v4();
self.change_list.save_known_root(id);
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
caller: component.caller.clone(),
id,
});
}
VNode::Suspended => {
todo!("Creation of VNode::Suspended not yet supported")
}
}
}
// Diff event listeners between `old` and `new`. // Diff event listeners between `old` and `new`.
// //
// The listeners' node must be on top of the change list stack: // The listeners' node must be on top of the change list stack:
@@ -787,93 +908,6 @@ impl<'a> DiffMachine<'a> {
// Support methods // Support methods
// ====================== // ======================
// Emit instructions to create the given virtual node.
//
// The change list stack may have any shape upon entering this function:
//
// [...]
//
// When this function returns, the new node is on top of the change list stack:
//
// [... node]
fn create(&mut self, node: &VNode<'a>) {
debug_assert!(self.change_list.traversal_is_committed());
match node {
VNode::Text(VText { text }) => {
self.change_list.create_text_node(text);
}
VNode::Element(&VElement {
key: _,
tag_name,
listeners,
attributes,
children,
namespace,
}) => {
// log::info!("Creating {:#?}", node);
if let Some(namespace) = namespace {
self.change_list.create_element_ns(tag_name, namespace);
} else {
self.change_list.create_element(tag_name);
}
listeners.iter().enumerate().for_each(|(_id, listener)| {
self.change_list
.new_event_listener(listener.event, listener.scope, listener.id)
});
for attr in attributes {
self.change_list
.set_attribute(&attr.name, &attr.value, namespace.is_some());
}
// Fast path: if there is a single text child, it is faster to
// create-and-append the text node all at once via setting the
// parent's `textContent` in a single change list instruction than
// to emit three instructions to (1) create a text node, (2) set its
// text content, and finally (3) append the text node to this
// parent.
if children.len() == 1 {
if let VNode::Text(VText { text }) = children[0] {
self.change_list.set_text(text);
return;
}
}
for child in children {
self.create(child);
self.change_list.append_child();
}
}
/*
todo: integrate re-entrace
*/
// NodeKind::Cached(ref c) => {
// cached_roots.insert(c.id);
// let (node, template) = cached_set.get(c.id);
// if let Some(template) = template {
// create_with_template(
// cached_set,
// self.change_list,
// registry,
// template,
// node,
// cached_roots,
// );
// } else {
// create(cached_set, change_list, registry, node, cached_roots);
// }
// }
VNode::Suspended => {
todo!("Creation of VNode::Suspended not yet supported")
}
VNode::Component(_) => {
todo!("Creation of VNode::Component not yet supported")
}
}
}
// Remove all of a node's children. // Remove all of a node's children.
// //
// The change list stack must have this shape upon entry to this function: // The change list stack must have this shape upon entry to this function:
+21 -1
View File
@@ -10,7 +10,11 @@ pub use use_state_def::use_state;
mod use_state_def { mod use_state_def {
use crate::innerlude::*; use crate::innerlude::*;
use std::{cell::RefCell, ops::DerefMut, rc::Rc}; use std::{
cell::RefCell,
ops::{Deref, DerefMut},
rc::Rc,
};
struct UseState<T: 'static> { struct UseState<T: 'static> {
new_val: Rc<RefCell<Option<T>>>, new_val: Rc<RefCell<Option<T>>>,
@@ -18,6 +22,22 @@ mod use_state_def {
caller: Box<dyn Fn(T) + 'static>, caller: Box<dyn Fn(T) + 'static>,
} }
struct UseStateHandle<'a, T> {
inner: &'a T,
}
impl<'a, T> UseStateHandle<'a, T> {
fn set(&self, new_val: T) {}
fn modify(&self, f: impl FnOnce(&mut T)) {}
}
impl<'a, T> Deref for UseStateHandle<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
/// Store state between component renders! /// Store state between component renders!
/// When called, this hook retrives a stored value and provides a setter to update that value. /// When called, this hook retrives a stored value and provides a setter to update that value.
/// When the setter is called, the component is re-ran with the new value. /// When the setter is called, the component is re-ran with the new value.
+6 -3
View File
@@ -91,14 +91,17 @@ pub(crate) mod innerlude {
// pub(crate) use crate::component::Properties; // pub(crate) use crate::component::Properties;
pub(crate) use crate::component::Properties; pub(crate) use crate::component::Properties;
pub(crate) use crate::context::Context; pub(crate) use crate::context::Context;
pub(crate) use crate::error::{Result}; pub use crate::diff::LifeCycleEvent;
pub use crate::events::EventTrigger; pub(crate) use crate::error::Result;
pub use crate::events::{EventTrigger, VirtualEvent};
use crate::nodes; use crate::nodes;
pub use crate::component::ScopeIdx;
pub use crate::context::hooks::Hook;
pub use crate::nodes::VNode;
pub(crate) use nodes::*; pub(crate) use nodes::*;
pub use crate::component::ScopeIdx;
pub use crate::diff::DiffMachine; pub use crate::diff::DiffMachine;
pub use crate::patch::{EditList, EditMachine}; pub use crate::patch::{EditList, EditMachine};
// pub use crate::patchdx; // pub use crate::patchdx;
+8 -13
View File
@@ -1,18 +1,15 @@
//! Helpers for building virtual DOM VNodes. //! Helpers for building virtual DOM VNodes.
use std::{borrow::BorrowMut}; use std::{borrow::BorrowMut, intrinsics::transmute};
use crate::{ use crate::{
context::NodeCtx, context::NodeCtx,
events::VirtualEvent, events::VirtualEvent,
innerlude::{VComponent, FC}, innerlude::{Properties, VComponent, FC},
nodes::{Attribute, Listener, NodeKey, VNode}, nodes::{Attribute, Listener, NodeKey, VNode},
prelude::VElement, prelude::VElement,
}; };
/// A virtual DOM element builder. /// A virtual DOM element builder.
/// ///
/// Typically constructed with element-specific constructors, eg the `div` /// Typically constructed with element-specific constructors, eg the `div`
@@ -526,12 +523,10 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
// _f: crate::innerlude::FC<T>, // _f: crate::innerlude::FC<T>,
// _props: T // _props: T
pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>( pub fn virtual_child<'a, T: Properties>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
_ctx: &'b NodeCtx<'a>, // alloc props into the bump;
_f: FC<T>,
_p: T, // erase the lifetime
) -> VNode<'a> { let p_static = unsafe { p.into_static() };
// crate::nodes::VComponent VNode::Component(crate::nodes::VComponent::new(f, p_static))
todo!()
// VNode::Component()
} }
+38 -25
View File
@@ -10,12 +10,7 @@ use crate::{
use bumpalo::Bump; use bumpalo::Bump;
use std::fmt::Debug; use std::fmt::Debug;
use std::{ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
any::{Any},
cell::RefCell,
marker::PhantomData,
rc::Rc,
};
/// A domtree represents the result of "Viewing" the context /// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier /// It's a placeholder over vnodes, to make working with lifetimes easier
@@ -271,21 +266,28 @@ impl<'a> VText<'a> {
/// Only supports the functional syntax /// Only supports the functional syntax
pub type StableScopeAddres = Rc<RefCell<Option<usize>>>; pub type StableScopeAddres = Rc<RefCell<Option<usize>>>;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct VComponent<'src> { pub struct VComponent<'src> {
pub stable_addr: StableScopeAddres, pub stable_addr: StableScopeAddres,
pub raw_props: Rc<dyn Any>,
pub comparator: Comparator, pub comparator: Comparator,
pub caller: Caller, pub caller: Caller,
pub caller_ref: *const (),
_p: PhantomData<&'src ()>, _p: PhantomData<&'src ()>,
} }
pub struct Comparator(Box<dyn Fn(&dyn Any) -> bool>); pub trait PropsComparator {}
#[derive(Clone)]
pub struct Comparator(pub Rc<dyn Fn(&dyn PropsComparator) -> bool>);
// pub struct Comparator(pub Rc<dyn Fn(&dyn Any) -> bool>);
impl std::fmt::Debug for Comparator { impl std::fmt::Debug for Comparator {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(()) Ok(())
} }
} }
pub struct Caller(Box<dyn Fn(Context) -> DomTree>); #[derive(Clone)]
pub struct Caller(pub Rc<dyn Fn(Context) -> DomTree>);
impl std::fmt::Debug for Caller { impl std::fmt::Debug for Caller {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(()) Ok(())
@@ -298,30 +300,41 @@ impl<'a> VComponent<'a> {
// - perform comparisons when diffing (memoization) // - perform comparisons when diffing (memoization)
// TODO: lift the requirement that props need to be static // TODO: lift the requirement that props need to be static
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
pub fn new<P: Properties + 'static>(caller: FC<P>, props: P) -> Self { pub fn new<P0: Properties, P: Properties + 'static>(caller: FC<P0>, props: P) -> Self {
let caller_ref = caller as *const ();
let props = Rc::new(props); let props = Rc::new(props);
// used for memoization // used for memoization
let p1 = props.clone(); let p1 = props.clone();
let props_comparator = move |new_props: &dyn Any| -> bool { let props_comparator = move |new_props: ()| -> bool {
new_props false
.downcast_ref::<P>() // let props_comparator = move |new_props: &dyn Any| -> bool {
.map(|new_p| p1.as_ref() == new_p) // new_props
.unwrap_or_else(|| { // .downcast_ref::<P>()
log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); // .map(|new_p| p1.as_ref() == new_p)
false // .unwrap_or_else(|| {
}) // log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL");
// false
// })
}; };
// used for actually rendering the custom component // used for actually rendering the custom component
let p2 = props.clone(); let p2 = props.clone();
let caller = move |ctx: Context| -> DomTree { caller(ctx, p2.as_ref()) }; let caller = move |ctx: Context| -> DomTree {
//
// cast back into the right lifetime
caller(ctx, p2.as_ref())
};
Self { todo!()
_p: PhantomData, // Self {
comparator: Comparator(Box::new(props_comparator)), // caller_ref,
caller: Caller(Box::new(caller)), // raw_props: props,
stable_addr: Rc::new(RefCell::new(None)), // _p: PhantomData,
} // comparator: Comparator(Rc::new(props_comparator)),
// caller: Caller(Rc::new(caller)),
// stable_addr: Rc::new(RefCell::new(None)),
// }
} }
} }
+15 -6
View File
@@ -83,11 +83,13 @@ pub enum Edit<'d> {
// ======================================================== // ========================================================
// push a known node on to the stack // push a known node on to the stack
TraverseToKnown { TraverseToKnown {
node: ScopeIdx, node: uuid::Uuid,
// node: ScopeIdx,
}, },
// Add the current top of the stack to the known nodes // Add the current top of the stack to the known nodes
MakeKnown { MakeKnown {
node: ScopeIdx, node: uuid::Uuid,
// node: ScopeIdx,
}, },
// Remove the current top of the stack from the known nodes // Remove the current top of the stack from the known nodes
RemoveKnown, RemoveKnown,
@@ -126,18 +128,18 @@ pub struct EditMachine<'src> {
pub traversal: Traversal, pub traversal: Traversal,
next_temporary: u32, next_temporary: u32,
forcing_new_listeners: bool, forcing_new_listeners: bool,
pub emitter: EditList<'src>, pub emitter: EditList<'src>,
} }
impl<'b> EditMachine<'b> { impl<'b> EditMachine<'b> {
pub fn new(_bump: &'b bumpalo::Bump) -> Self { pub fn new() -> Self {
// pub fn new(_bump: &'b bumpalo::Bump) -> Self {
// todo: see if bumpalo is needed for edit list // todo: see if bumpalo is needed for edit list
Self { Self {
traversal: Traversal::new(), traversal: Traversal::new(),
next_temporary: 0, next_temporary: 0,
forcing_new_listeners: false, forcing_new_listeners: false,
emitter: EditList::default(), emitter: EditList::<'b>::default(),
} }
} }
} }
@@ -364,11 +366,18 @@ impl<'a> EditMachine<'a> {
// debug!("emit: remove_event_listener({:?})", event); // debug!("emit: remove_event_listener({:?})", event);
} }
pub fn save_known_root(&mut self, id: ScopeIdx) { pub fn save_known_root(&mut self, id: uuid::Uuid) {
// pub fn save_known_root(&mut self, id: ScopeIdx) {
// pub fn save_known_root(&mut self, id: ScopeIdx) {
log::debug!("emit: save_known_root({:?})", id); log::debug!("emit: save_known_root({:?})", id);
self.emitter.push(Edit::MakeKnown { node: id }) self.emitter.push(Edit::MakeKnown { node: id })
} }
pub fn load_known_root(&mut self, id: uuid::Uuid) {
log::debug!("emit: TraverseToKnown({:?})", id);
self.emitter.push(Edit::TraverseToKnown { node: id })
}
// pub fn save_template(&mut self, id: CacheId) { // pub fn save_template(&mut self, id: CacheId) {
// debug_assert!(self.traversal_is_committed()); // debug_assert!(self.traversal_is_committed());
// debug_assert!(!self.has_template(id)); // debug_assert!(!self.has_template(id));
+45 -50
View File
@@ -1,21 +1,12 @@
use crate::component::ScopeIdx;
use crate::context::hooks::Hook;
use crate::innerlude::*; use crate::innerlude::*;
use crate::nodes::VNode;
use bumpalo::Bump; use bumpalo::Bump;
use std::{ use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, ops::Deref};
any::{Any},
cell::RefCell,
marker::PhantomData,
ops::Deref,
};
pub trait Scoped { pub trait Scoped {
fn run(&mut self); fn run(&mut self);
fn compare_props(&self, new: &dyn std::any::Any) -> bool; // fn compare_props(&self, new: &dyn std::any::Any) -> bool;
fn call_listener(&mut self, trigger: EventTrigger); fn call_listener(&mut self, trigger: EventTrigger);
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>; fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>; fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
} }
@@ -33,11 +24,16 @@ pub struct Scope<P: Properties> {
// our own index // our own index
pub myidx: ScopeIdx, pub myidx: ScopeIdx,
// the props //
pub props: P, pub children: HashSet<ScopeIdx>,
// // the props
// pub props: P,
// and the actual render function // and the actual render function
pub caller: FC<P>, pub caller: Caller,
_p: std::marker::PhantomData<P>,
// pub raw_caller: FC<P>,
// ========================== // ==========================
// slightly unsafe stuff // slightly unsafe stuff
@@ -56,43 +52,31 @@ pub struct Scope<P: Properties> {
// - is self-refenrential and therefore needs to point into the bump // - is self-refenrential and therefore needs to point into the bump
// Stores references into the listeners attached to the vnodes // Stores references into the listeners attached to the vnodes
// NEEDS TO BE PRIVATE // NEEDS TO BE PRIVATE
listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>, listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
} }
// instead of having it as a trait method, we use a single function // instead of having it as a trait method, we use a single function
// todo: do the unsafety magic stuff to erase the type of p // todo: do the unsafety magic stuff to erase the type of p
pub fn create_scoped<P: Properties + 'static>( pub fn create_scoped(
caller: FC<P>, // raw_f: FC<P>,
props: P, caller: Caller,
// props: P,
myidx: ScopeIdx, myidx: ScopeIdx,
parent: Option<ScopeIdx>, parent: Option<ScopeIdx>,
) -> Box<dyn Scoped> { ) -> Box<dyn Scoped> {
let hook_arena = typed_arena::Arena::new(); Box::new(Scope::<()> {
let hooks = RefCell::new(Vec::new()); // raw_caller: raw_f,
_p: Default::default(),
let listeners = Default::default();
let old_frame = BumpFrame {
bump: Bump::new(),
head_node: VNode::text(""),
};
let new_frame = BumpFrame {
bump: Bump::new(),
head_node: VNode::text(""),
};
let frames = ActiveFrame::from_frames(old_frame, new_frame);
Box::new(Scope {
caller, caller,
myidx, myidx,
hook_arena, hook_arena: typed_arena::Arena::new(),
hooks, hooks: RefCell::new(Vec::new()),
frames, frames: ActiveFrame::new(),
listeners, children: HashSet::new(),
listeners: Default::default(),
parent, parent,
props, // props,
}) })
} }
@@ -122,9 +106,8 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
}; };
// Note that the actual modification of the vnode head element occurs during this call // Note that the actual modification of the vnode head element occurs during this call
// let _: DomTree = caller(ctx, props); let _: DomTree = (self.caller.0.as_ref())(ctx);
// let _: DomTree = P::render (ctx, &self.props); // let _: DomTree = (self.raw_caller)(ctx, &self.props);
let _: DomTree = (self.caller)(ctx, &self.props);
/* /*
SAFETY ALERT SAFETY ALERT
@@ -145,11 +128,11 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
.expect("Viewing did not happen"); .expect("Viewing did not happen");
} }
fn compare_props(&self, new: &dyn Any) -> bool { // fn compare_props(&self, new: &dyn Any) -> bool {
new.downcast_ref::<P>() // new.downcast_ref::<P>()
.map(|f| &self.props == f) // .map(|f| &self.props == f)
.expect("Props should not be of a different type") // .expect("Props should not be of a different type")
} // }
// A safe wrapper around calling listeners // A safe wrapper around calling listeners
// calling listeners will invalidate the list of listeners // calling listeners will invalidate the list of listeners
@@ -194,7 +177,6 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
// ========================== // ==========================
// Active-frame related code // Active-frame related code
// ========================== // ==========================
// todo, do better with the active frame stuff // todo, do better with the active frame stuff
// somehow build this vnode with a lifetime tied to self // somehow build this vnode with a lifetime tied to self
// This root node has "static" lifetime, but it's really not static. // This root node has "static" lifetime, but it's really not static.
@@ -212,6 +194,19 @@ pub struct BumpFrame {
} }
impl ActiveFrame { impl ActiveFrame {
pub fn new() -> Self {
Self::from_frames(
BumpFrame {
bump: Bump::new(),
head_node: VNode::text(""),
},
BumpFrame {
bump: Bump::new(),
head_node: VNode::text(""),
},
)
}
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
Self { Self {
idx: 0.into(), idx: 0.into(),
+78 -73
View File
@@ -7,7 +7,7 @@ use crate::{
}; };
use bumpalo::Bump; use bumpalo::Bump;
use generational_arena::Arena; use generational_arena::Arena;
use std::{any::TypeId, cell::RefCell, rc::Rc};
/// An integrated virtual node system that progresses events and diffs UI trees. /// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI. /// Differences are converted into patches which a renderer can use to draw the UI.
@@ -17,8 +17,9 @@ pub struct VirtualDom {
/// ///
/// eventually, come up with a better datastructure that reuses boxes for known P types /// eventually, come up with a better datastructure that reuses boxes for known P types
/// like a generational typemap bump arena /// like a generational typemap bump arena
/// -> IE a cache line for each P type with soem heuristics on optimizing layout /// -> IE a cache line for each P type with some heuristics on optimizing layout
pub(crate) components: Arena<Box<dyn Scoped>>, pub(crate) components: Arena<Box<dyn Scoped>>,
// pub(crate) components: RefCell<Arena<Box<dyn Scoped>>>,
// pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>, // pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
/// The index of the root component. /// The index of the root component.
/// Will not be ready if the dom is fresh /// Will not be ready if the dom is fresh
@@ -59,53 +60,87 @@ impl VirtualDom {
/// ///
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree. /// to toss out the entire tree.
pub fn new_with_props<P: Properties + 'static>(_root: FC<P>, _root_props: P) -> Self { pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
// let mut components = Arena::new(); let mut components = Arena::new();
// let mut components = Arena::new();
// Create a reference to the component in the arena // Create a reference to the component in the arena
// Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
// This puts the dom in a usable state on creation, rather than being potentially invalid let caller = Caller(Rc::new(move |ctx| {
//
root(ctx, &root_props)
// DomTree {}
}));
let base_scope = components.insert_with(|id| create_scoped(caller, id, None));
// let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); // let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
todo!() Self {
// Self { components,
// // components: RefCell::new(components), base_scope,
// components: components, diff_bump: Bump::new(),
// // components: Rc::new(RefCell::new(components)), _root_prop_type: TypeId::of::<P>(),
// base_scope, }
// // event_queue: RefCell::new(VecDeque::new()),
// diff_bump: Bump::new(),
// _root_prop_type: TypeId::of::<P>(),
// }
} }
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
// pub fn rebuild<'s>(&'s mut self) -> Result<> {
// pub fn rebuild<'s>(&'s mut self) -> Result<std::cell::Ref<'_, Arena<Box<dyn Scoped>>>> {
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> { pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
log::debug!("rebuilding...");
// Reset and then build a new diff machine // Reset and then build a new diff machine
// The previous edit list cannot be around while &mut is held // The previous edit list cannot be around while &mut is held
// Make sure variance doesnt break this // Make sure variance doesnt break this
self.diff_bump.reset(); // bump.reset();
self.components // Diff from the top
.get_mut(self.base_scope) let mut diff_machine = DiffMachine::new(); // partial borrow
.expect("Root should always exist") {
.run(); let component = self
.components
.get_mut(self.base_scope)
.expect("failed to acquire base scope");
let _b = Bump::new(); component.run();
}
let mut diff_machine = DiffMachine::new(&self.diff_bump); {
// let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope); let component = self
.components
.get(self.base_scope)
.expect("failed to acquire base scope");
// todo!() diff_machine.diff_node(component.old_frame(), component.new_frame());
}
let component = self.components.get(self.base_scope).unwrap(); // 'render: loop {
diff_machine.diff_node(component.old_frame(), component.new_frame()); // for event in &mut diff_machine.lifecycle_events.drain(..) {
let edits = diff_machine.consume(); // log::debug!("event is {:#?}", event);
// self.diff_bump = b; // match event {
// LifeCycleEvent::Mount { caller, id } => {
// diff_machine.change_list.load_known_root(id);
// let idx = self
// .components
// .insert_with(|f| create_scoped(caller, f, None));
// // .insert_with(|f| create_scoped(caller, props, myidx, parent));
// }
// LifeCycleEvent::PropsChanged => {
// //
// break 'render;
// }
// LifeCycleEvent::SameProps => {
// //
// break 'render;
// }
// LifeCycleEvent::Remove => {
// //
// break 'render;
// }
// }
// }
// if diff_machine.lifecycle_events.is_empty() {
// break 'render;
// }
// }
let edits: Vec<Edit<'s>> = diff_machine.consume();
Ok(edits) Ok(edits)
} }
@@ -133,50 +168,20 @@ impl VirtualDom {
/// } /// }
/// ///
/// ``` /// ```
pub fn progress_with_event(&mut self, _event: EventTrigger) -> Result<EditList<'_>> { pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
// self.components let component = self
// .borrow_mut() .components
// .get_mut(event.component_id) .get_mut(event.component_id)
// .map(|f| { .expect("Borrowing should not fail");
// f.call_listener(event);
// f
// })
// .map(|f| f.run())
// .expect("Borrowing should not fail");
// component.call_listener(event); component.call_listener(event);
component.run();
// .expect("Component should exist if an event was triggered"); let mut diff_machine = DiffMachine::new();
// Reset and then build a new diff machine // let mut diff_machine = DiffMachine::new(&self.diff_bump);
// The previous edit list cannot be around while &mut is held
// Make sure variance doesnt break this
// self.diff_bump.reset();
// let mut diff_machine = DiffMachine::new(&mut self, event.component_id);
// let mut diff_machine =
// DiffMachine::new(&self.diff_bump, &mut self.components, event.component_id);
// component.run(); diff_machine.diff_node(component.old_frame(), component.new_frame());
// diff_machine.diff_node(component.old_frame(), component.new_frame());
todo!() Ok(diff_machine.consume())
// Ok(diff_machine.consume())
// Err(crate::error::Error::NoEvent)
// Mark dirty components. Descend from the highest node until all dirty nodes are updated.
// let mut affected_components = Vec::new();
// while let Some(event) = self.pop_event() {
// if let Some(component_idx) = event.index() {
// affected_components.push(component_idx);
// }
// self.process_lifecycle(event)?;
// }
// todo!()
} }
} }
enum LifeCycleEvent {
Mount {},
}
// todo: add some "handle" to the vdom. Or a way of giving out closures that can mutate the vdoms internal data.
+23 -22
View File
@@ -13,26 +13,27 @@ fn main() {
} }
static App: FC<()> = |ctx, _| { static App: FC<()> = |ctx, _| {
log::info!("Ran component"); ctx.render(rsx! {
use dioxus::builder::*; div {
todo!() h1 {"hello"}
// ctx.render(|b| { C1 {}
// div(b) C2 {}
// .child(text("hello")) }
// .listeners([on(b, "click", |_| { })
// // };
// log::info!("button1 clicked!");
// })]) static C1: FC<()> = |ctx, props| {
// .finish() ctx.render(rsx! {
// }) button {
// ctx.render(html! { "numba 1"
// <div onclick={move |_| log::info!("button1 clicked!")}> }
// "Hello" })
// // <div class="flex items-center justify-center flex-col"> };
// // <div class="font-bold text-xl"> "Count is ..." </div>
// // <button onclick={move |_| log::info!("button1 clicked!")}> "increment" </button> static C2: FC<()> = |ctx, props| {
// // <button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button> ctx.render(rsx! {
// // </div> button {
// </div> "numba 2"
// }) }
})
}; };
+21 -4
View File
@@ -6,6 +6,7 @@ fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
} }
@@ -18,6 +19,8 @@ static Example: FC<()> = |ctx, _props| {
set_event(Some(evt)); set_event(Some(evt));
}; };
log::info!("hello world");
ctx.render(rsx! { ctx.render(rsx! {
div { div {
@@ -35,21 +38,35 @@ static Example: FC<()> = |ctx, _props| {
id: "json" id: "json"
"{event}" "{event}"
} }
Example2 { name: "{event}" } // Example2 { name: "{event}".to_string() }
} }
}) })
}; };
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Props)]
struct Props { struct ExampleProps {
name: String name: String
} }
static Example2: FC<Props> = |ctx, props| { static Example2: FC<ExampleProps> = |ctx, props| {
ctx.render(rsx!{ ctx.render(rsx!{
div { div {
h1 {"hello {props.name}"} h1 {"hello {props.name}"}
} }
}) })
}; };
impl Properties for ExampleProps {
type Builder = ExamplePropsBuilder<((),)>;
type StaticOutput = ExampleProps;
fn builder() -> Self::Builder {
ExampleProps::builder()
}
unsafe fn into_static(self) -> Self::StaticOutput {
todo!()
}
}
+5 -17
View File
@@ -1,23 +1,17 @@
use bumpalo::Bump; #![allow(non_snake_case)]
use dioxus::{events::EventTrigger, prelude::*};
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus::prelude::*;
use dioxus_web::WebsysRenderer; use dioxus_web::WebsysRenderer;
// creates a proxy address that gets transpiled at compile time
// all asset ids are shuttled through the renderer
// static IMG: ImgAsset = dioxus_asset!("");
fn main() { fn main() {
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
// console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)) wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example))
} }
static Example: FC<()> = |ctx, props| { static Example: FC<()> = |ctx, _props| {
let (name, set_name) = use_state(&ctx, || "...?"); let (name, set_name) = use_state(&ctx, || "...?");
ctx.render(rsx! { ctx.render(rsx! {
div { div {
class: "py-12 px-4 text-center w-full max-w-2xl mx-auto" class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
@@ -47,9 +41,3 @@ static Example: FC<()> = |ctx, props| {
} }
}) })
}; };
// onclick: {move |_| set_name("jill")}
+8 -5
View File
@@ -2,6 +2,7 @@
//! -------------- //! --------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
use dioxus::prelude::Properties;
use fxhash::FxHashMap; use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node}; use web_sys::{window, Document, Element, Event, Node};
@@ -48,7 +49,7 @@ impl WebsysRenderer {
/// Automatically progresses the creation of the VNode tree to completion. /// Automatically progresses the creation of the VNode tree to completion.
/// ///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props<T: 'static>(root: FC<T>, root_props: T) -> Self { pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props)) Self::from_vdom(VirtualDom::new_with_props(root, root_props))
} }
@@ -59,7 +60,7 @@ impl WebsysRenderer {
} }
pub async fn run(&mut self) -> dioxus_core::error::Result<()> { pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
let (mut sender, mut receiver) = mpsc::unbounded::<EventTrigger>(); let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
let body_element = prepare_websys_dom(); let body_element = prepare_websys_dom();
@@ -75,8 +76,10 @@ impl WebsysRenderer {
// todo: initialize the event registry properly on the root // todo: initialize the event registry properly on the root
self.internal_dom.rebuild()?.iter().for_each(|edit| { let edits = self.internal_dom.rebuild()?;
// log::debug!("patching with {:?}", edit); log::debug!("Received edits: {:#?}", edits);
edits.iter().for_each(|edit| {
log::debug!("patching with {:?}", edit);
patch_machine.handle_edit(edit); patch_machine.handle_edit(edit);
}); });
// log::debug!("patch stack size {:?}", patch_machine.stack); // log::debug!("patch stack size {:?}", patch_machine.stack);
@@ -92,7 +95,7 @@ impl WebsysRenderer {
.progress_with_event(event)? .progress_with_event(event)?
.iter() .iter()
.for_each(|edit| { .for_each(|edit| {
// log::debug!("edit stream {:?}", edit); log::debug!("edit stream {:?}", edit);
patch_machine.handle_edit(edit); patch_machine.handle_edit(edit);
}); });