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
+433 -346

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ members = [
"packages/dioxus",
"packages/core-macro",
"packages/core",
# "packages/web",
"packages/web",
# "packages/router",
# "packages/ssr",
# "packages/webview",
+1 -1
View File
@@ -16,7 +16,7 @@ once_cell = "1.7.2"
proc-macro-hack = "0.5.19"
proc-macro2 = "1.0.6"
quote = "1.0"
syn = { version = "1.0.11", features = ["full"] }
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
# testing
[dev-dependencies]
-1
View File
@@ -11,7 +11,6 @@ use proc_macro2::TokenStream;
use syn::parse::Error;
use syn::spanned::Spanned;
use quote::quote;
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
[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" }
# Backs some static data
once_cell = "1.5.2"
@@ -22,18 +20,12 @@ generational-arena = { version = "0.2.8", features = ["serde"] }
# Bumpalo backs the VNode creation
bumpalo = { version = "3.6.0", features = ["collections"] }
owning_ref = "0.4.1"
# all the arenas 👿
typed-arena = "2.0.1"
toolshed = "0.8.1"
id-arena = "2.2.1"
thiserror = "1.0.23"
fxhash = "0.2.1"
longest-increasing-subsequence = "0.1.0"
serde = { version = "1.0.123", features = ["derive"] }
log = "0.4.14"
pretty_env_logger = "0.4.0"
fstrings = "0.2.3"
# ouroboros = "0.8.0"
# hashbrown = { version = "0.9.1", features = ["bumpalo"] }
uuid = {version = "0.8.2", features=["serde", "v4"]}
+5 -2
View File
@@ -7,8 +7,6 @@
fn main() {}
use dioxus_core::prelude::*;
struct Props {
@@ -73,9 +71,14 @@ impl PartialEq for ChildProps<'_> {
impl<'a> Properties for ChildProps<'a> {
type Builder = ChildPropsBuilder<'a, ((), ())>;
type StaticOutput = ChildProps<'static>;
fn builder() -> <Self as Properties>::Builder {
ChildProps::builder()
}
unsafe fn into_static(self) -> Self::StaticOutput {
unsafe { std::mem::transmute(self) }
}
}
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::prelude::*;
static BLAH: FC<()> = |ctx, _props| {
let g = "asd".to_string();
ctx.render(rsx! {
@@ -30,7 +27,12 @@ fn main() {}
impl Properties for ExampleProps {
type Builder = ExamplePropsBuilder<((),)>;
type StaticOutput = ExampleProps;
fn builder() -> Self::Builder {
ExampleProps::builder()
}
unsafe fn into_static(self) -> Self {
self
}
}
-4
View File
@@ -1,12 +1,8 @@
// #[macro_use]
extern crate fstrings;
// use dioxus_core::ifmt;
// use fstrings::format_args_f;
fn main() {
let bump = bumpalo::Bump::new();
let _b = &bump;
+5
View File
@@ -27,7 +27,12 @@ static Example: FC<SomeProps> = |ctx, _props| {
// toodo: derive this
impl Properties for SomeProps {
type Builder = SomePropsBuilder<((),)>;
type StaticOutput = SomeProps;
fn builder() -> Self::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 {
type Builder;
type StaticOutput: Properties + 'static;
fn builder() -> Self::Builder;
unsafe fn into_static(self) -> Self::StaticOutput;
}
pub struct EmptyBuilder;
@@ -23,10 +25,15 @@ impl EmptyBuilder {
impl Properties for () {
type Builder = EmptyBuilder;
type StaticOutput = ();
fn builder() -> Self::Builder {
EmptyBuilder {}
}
unsafe fn into_static(self) -> Self::StaticOutput {
std::mem::transmute(self)
}
}
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:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{
innerlude::*,
};
use crate::{innerlude::*, scope::Scoped};
use bumpalo::Bump;
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
/// 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.
///
/// 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.
pub struct DiffMachine<'a> {
pub change_list: EditMachine<'a>,
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> {
pub fn new(bump: &'a Bump) -> Self {
pub fn new() -> Self {
// pub fn new(bump: &'a Bump) -> Self {
Self {
change_list: EditMachine::new(bump),
lifecycle_events: VecDeque::new(),
change_list: EditMachine::new(),
diffed: FxHashSet::default(),
need_to_diff: FxHashSet::default(),
}
}
@@ -111,18 +125,31 @@ impl<'a> DiffMachine<'a> {
self.diff_children(eold.children, enew.children);
}
(VNode::Component(_cold), VNode::Component(_cnew)) => {
// if cold.comp != cnew.comp {
// // queue an event to mount this new component
(VNode::Component(cold), VNode::Component(cnew)) => {
todo!("should not happen")
// if cold.caller_ref != cnew.caller_ref {
// // todo: create a stable addr
// self.lifecycle_events.push_back(LifeCycleEvent::Mount);
// 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)) => {
// self.lifecycle_events.push_back(LifeCycleEvent::Mount);
// we have no stable reference to work from
// push the lifecycle event onto the queue
// 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`.
//
// The listeners' node must be on top of the change list stack:
@@ -787,93 +908,6 @@ impl<'a> DiffMachine<'a> {
// 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.
//
// The change list stack must have this shape upon entry to this function:
+23 -3
View File
@@ -10,7 +10,11 @@ pub use use_state_def::use_state;
mod use_state_def {
use crate::innerlude::*;
use std::{cell::RefCell, ops::DerefMut, rc::Rc};
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
rc::Rc,
};
struct UseState<T: 'static> {
new_val: Rc<RefCell<Option<T>>>,
@@ -18,6 +22,22 @@ mod use_state_def {
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!
/// 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.
@@ -175,9 +195,9 @@ mod use_reducer_def {
// #[cfg(test)]
mod tests {
use crate::prelude::*;
enum Actions {
Incr,
Decr,
+8 -5
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::context::Context;
pub(crate) use crate::error::{Result};
pub use crate::events::EventTrigger;
pub use crate::diff::LifeCycleEvent;
pub(crate) use crate::error::Result;
pub use crate::events::{EventTrigger, VirtualEvent};
use crate::nodes;
pub(crate) use nodes::*;
pub use crate::component::ScopeIdx;
pub use crate::context::hooks::Hook;
pub use crate::nodes::VNode;
pub(crate) use nodes::*;
pub use crate::diff::DiffMachine;
pub use crate::patch::{EditList, EditMachine};
// pub use crate::patchdx;
+8 -13
View File
@@ -1,18 +1,15 @@
//! Helpers for building virtual DOM VNodes.
use std::{borrow::BorrowMut};
use std::{borrow::BorrowMut, intrinsics::transmute};
use crate::{
context::NodeCtx,
events::VirtualEvent,
innerlude::{VComponent, FC},
innerlude::{Properties, VComponent, FC},
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::VElement,
};
/// A virtual DOM element builder.
///
/// 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>,
// _props: T
pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>(
_ctx: &'b NodeCtx<'a>,
_f: FC<T>,
_p: T,
) -> VNode<'a> {
// crate::nodes::VComponent
todo!()
// VNode::Component()
pub fn virtual_child<'a, T: Properties>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
// alloc props into the bump;
// erase the lifetime
let p_static = unsafe { p.into_static() };
VNode::Component(crate::nodes::VComponent::new(f, p_static))
}
+38 -25
View File
@@ -10,12 +10,7 @@ use crate::{
use bumpalo::Bump;
use std::fmt::Debug;
use std::{
any::{Any},
cell::RefCell,
marker::PhantomData,
rc::Rc,
};
use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
/// A domtree represents the result of "Viewing" the context
/// 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
pub type StableScopeAddres = Rc<RefCell<Option<usize>>>;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct VComponent<'src> {
pub stable_addr: StableScopeAddres,
pub raw_props: Rc<dyn Any>,
pub comparator: Comparator,
pub caller: Caller,
pub caller_ref: *const (),
_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 {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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 {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
@@ -298,30 +300,41 @@ impl<'a> VComponent<'a> {
// - perform comparisons when diffing (memoization)
// TODO: lift the requirement that props need to be static
// 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);
// used for memoization
let p1 = props.clone();
let props_comparator = move |new_props: &dyn Any| -> bool {
new_props
.downcast_ref::<P>()
.map(|new_p| p1.as_ref() == new_p)
.unwrap_or_else(|| {
log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL");
false
})
let props_comparator = move |new_props: ()| -> bool {
false
// let props_comparator = move |new_props: &dyn Any| -> bool {
// new_props
// .downcast_ref::<P>()
// .map(|new_p| p1.as_ref() == new_p)
// .unwrap_or_else(|| {
// log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL");
// false
// })
};
// used for actually rendering the custom component
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 {
_p: PhantomData,
comparator: Comparator(Box::new(props_comparator)),
caller: Caller(Box::new(caller)),
stable_addr: Rc::new(RefCell::new(None)),
}
todo!()
// Self {
// caller_ref,
// raw_props: props,
// _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
TraverseToKnown {
node: ScopeIdx,
node: uuid::Uuid,
// node: ScopeIdx,
},
// Add the current top of the stack to the known nodes
MakeKnown {
node: ScopeIdx,
node: uuid::Uuid,
// node: ScopeIdx,
},
// Remove the current top of the stack from the known nodes
RemoveKnown,
@@ -126,18 +128,18 @@ pub struct EditMachine<'src> {
pub traversal: Traversal,
next_temporary: u32,
forcing_new_listeners: bool,
pub emitter: EditList<'src>,
}
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
Self {
traversal: Traversal::new(),
next_temporary: 0,
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);
}
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);
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) {
// debug_assert!(self.traversal_is_committed());
// 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::nodes::VNode;
use bumpalo::Bump;
use std::{
any::{Any},
cell::RefCell,
marker::PhantomData,
ops::Deref,
};
use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, ops::Deref};
pub trait Scoped {
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 new_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
pub myidx: ScopeIdx,
// the props
pub props: P,
//
pub children: HashSet<ScopeIdx>,
// // the props
// pub props: P,
// 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
@@ -56,43 +52,31 @@ pub struct Scope<P: Properties> {
// - is self-refenrential and therefore needs to point into the bump
// Stores references into the listeners attached to the vnodes
// 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
// todo: do the unsafety magic stuff to erase the type of p
pub fn create_scoped<P: Properties + 'static>(
caller: FC<P>,
props: P,
pub fn create_scoped(
// raw_f: FC<P>,
caller: Caller,
// props: P,
myidx: ScopeIdx,
parent: Option<ScopeIdx>,
) -> Box<dyn Scoped> {
let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new());
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 {
Box::new(Scope::<()> {
// raw_caller: raw_f,
_p: Default::default(),
caller,
myidx,
hook_arena,
hooks,
frames,
listeners,
hook_arena: typed_arena::Arena::new(),
hooks: RefCell::new(Vec::new()),
frames: ActiveFrame::new(),
children: HashSet::new(),
listeners: Default::default(),
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
// let _: DomTree = caller(ctx, props);
// let _: DomTree = P::render (ctx, &self.props);
let _: DomTree = (self.caller)(ctx, &self.props);
let _: DomTree = (self.caller.0.as_ref())(ctx);
// let _: DomTree = (self.raw_caller)(ctx, &self.props);
/*
SAFETY ALERT
@@ -145,11 +128,11 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
.expect("Viewing did not happen");
}
fn compare_props(&self, new: &dyn Any) -> bool {
new.downcast_ref::<P>()
.map(|f| &self.props == f)
.expect("Props should not be of a different type")
}
// fn compare_props(&self, new: &dyn Any) -> bool {
// new.downcast_ref::<P>()
// .map(|f| &self.props == f)
// .expect("Props should not be of a different type")
// }
// A safe wrapper around calling 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
// ==========================
// todo, do better with the active frame stuff
// somehow build this vnode with a lifetime tied to self
// This root node has "static" lifetime, but it's really not static.
@@ -212,6 +194,19 @@ pub struct BumpFrame {
}
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 {
Self {
idx: 0.into(),
+78 -73
View File
@@ -7,7 +7,7 @@ use crate::{
};
use bumpalo::Bump;
use generational_arena::Arena;
use std::{any::TypeId, cell::RefCell, rc::Rc};
/// 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.
@@ -17,8 +17,9 @@ pub struct VirtualDom {
///
/// eventually, come up with a better datastructure that reuses boxes for known P types
/// like a generational typemap bump arena
/// -> IE a cache line for each P type with 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: RefCell<Arena<Box<dyn Scoped>>>,
// pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
/// The index of the root component.
/// 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
/// to toss out the entire tree.
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();
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = Arena::new();
// 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
// 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));
todo!()
// Self {
// // components: RefCell::new(components),
// components: components,
// // components: Rc::new(RefCell::new(components)),
// base_scope,
// // event_queue: RefCell::new(VecDeque::new()),
// diff_bump: Bump::new(),
// _root_prop_type: TypeId::of::<P>(),
// }
Self {
components,
base_scope,
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.
// 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>> {
log::debug!("rebuilding...");
// Reset and then build a new diff machine
// The previous edit list cannot be around while &mut is held
// Make sure variance doesnt break this
self.diff_bump.reset();
// bump.reset();
self.components
.get_mut(self.base_scope)
.expect("Root should always exist")
.run();
// Diff from the top
let mut diff_machine = DiffMachine::new(); // partial borrow
{
let component = self
.components
.get_mut(self.base_scope)
.expect("failed to acquire base scope");
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();
diff_machine.diff_node(component.old_frame(), component.new_frame());
let edits = diff_machine.consume();
// self.diff_bump = b;
// 'render: loop {
// for event in &mut diff_machine.lifecycle_events.drain(..) {
// log::debug!("event is {:#?}", event);
// 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)
}
@@ -133,50 +168,20 @@ impl VirtualDom {
/// }
///
/// ```
pub fn progress_with_event(&mut self, _event: EventTrigger) -> Result<EditList<'_>> {
// self.components
// .borrow_mut()
// .get_mut(event.component_id)
// .map(|f| {
// f.call_listener(event);
// f
// })
// .map(|f| f.run())
// .expect("Borrowing should not fail");
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
let component = self
.components
.get_mut(event.component_id)
.expect("Borrowing should not fail");
// component.call_listener(event);
component.call_listener(event);
component.run();
// .expect("Component should exist if an event was triggered");
// Reset and then build a new diff machine
// The previous edit list cannot be around while &mut is held
// Make sure variance doesnt break this
// 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);
let mut diff_machine = DiffMachine::new();
// let mut diff_machine = DiffMachine::new(&self.diff_bump);
// 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())
// 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!()
Ok(diff_machine.consume())
}
}
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, _| {
log::info!("Ran component");
use dioxus::builder::*;
todo!()
// ctx.render(|b| {
// div(b)
// .child(text("hello"))
// .listeners([on(b, "click", |_| {
// //
// log::info!("button1 clicked!");
// })])
// .finish()
// })
// ctx.render(html! {
// <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>
// // <button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button>
// // </div>
// </div>
// })
ctx.render(rsx! {
div {
h1 {"hello"}
C1 {}
C2 {}
}
})
};
static C1: FC<()> = |ctx, props| {
ctx.render(rsx! {
button {
"numba 1"
}
})
};
static C2: FC<()> = |ctx, props| {
ctx.render(rsx! {
button {
"numba 2"
}
})
};
+21 -4
View File
@@ -6,6 +6,7 @@ fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
@@ -18,6 +19,8 @@ static Example: FC<()> = |ctx, _props| {
set_event(Some(evt));
};
log::info!("hello world");
ctx.render(rsx! {
div {
@@ -35,21 +38,35 @@ static Example: FC<()> = |ctx, _props| {
id: "json"
"{event}"
}
Example2 { name: "{event}" }
// Example2 { name: "{event}".to_string() }
}
})
};
#[derive(Debug, PartialEq)]
struct Props {
#[derive(Debug, PartialEq, Props)]
struct ExampleProps {
name: String
}
static Example2: FC<Props> = |ctx, props| {
static Example2: FC<ExampleProps> = |ctx, props| {
ctx.render(rsx!{
div {
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;
use dioxus::{events::EventTrigger, prelude::*};
#![allow(non_snake_case)]
use dioxus_core as dioxus;
use dioxus::prelude::*;
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() {
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
// console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
console_error_panic_hook::set_once();
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, || "...?");
ctx.render(rsx! {
div {
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.
use dioxus::prelude::Properties;
use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node};
@@ -48,7 +49,7 @@ impl WebsysRenderer {
/// Automatically progresses the creation of the VNode tree to completion.
///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props<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))
}
@@ -59,7 +60,7 @@ impl WebsysRenderer {
}
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();
@@ -75,8 +76,10 @@ impl WebsysRenderer {
// todo: initialize the event registry properly on the root
self.internal_dom.rebuild()?.iter().for_each(|edit| {
// log::debug!("patching with {:?}", edit);
let edits = self.internal_dom.rebuild()?;
log::debug!("Received edits: {:#?}", edits);
edits.iter().for_each(|edit| {
log::debug!("patching with {:?}", edit);
patch_machine.handle_edit(edit);
});
// log::debug!("patch stack size {:?}", patch_machine.stack);
@@ -92,7 +95,7 @@ impl WebsysRenderer {
.progress_with_event(event)?
.iter()
.for_each(|edit| {
// log::debug!("edit stream {:?}", edit);
log::debug!("edit stream {:?}", edit);
patch_machine.handle_edit(edit);
});