diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml
index f779e6ba2..827968655 100644
--- a/packages/core/Cargo.toml
+++ b/packages/core/Cargo.toml
@@ -35,6 +35,8 @@ indexmap = "1.7"
serde = { version = "1", features = ["derive"], optional = true }
anyhow = "1.0.66"
+smallbox = "0.8.1"
+
[dev-dependencies]
tokio = { version = "*", features = ["full"] }
dioxus = { path = "../dioxus" }
diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs
index cc63a0e8b..35c2db411 100644
--- a/packages/core/src/create.rs
+++ b/packages/core/src/create.rs
@@ -36,6 +36,9 @@ impl<'b> VirtualDom {
let cur_scope = self.scope_stack.last().copied().unwrap();
+ // we know that this will generate at least one mutation per node
+ self.mutations.edits.reserve(template.template.roots.len());
+
let mut on_stack = 0;
for (root_idx, root) in template.template.roots.iter().enumerate() {
// We might need to generate an ID for the root node
@@ -208,19 +211,12 @@ impl<'b> VirtualDom {
// If it's all dynamic nodes, then we don't need to register it
// Quickly run through and see if it's all just dynamic nodes
- let dynamic_roots = template
- .template
- .roots
- .iter()
- .filter(|root| {
- matches!(
- root,
- TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
- )
- })
- .count();
-
- if dynamic_roots == template.template.roots.len() {
+ if template.template.roots.iter().all(|root| {
+ matches!(
+ root,
+ TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
+ )
+ }) {
return;
}
@@ -291,7 +287,7 @@ impl<'b> VirtualDom {
}
pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
- nodes.iter().fold(0, |acc, child| acc + self.create(child))
+ nodes.iter().map(|child| self.create(child)).sum()
}
pub(super) fn create_component_node(
@@ -302,7 +298,7 @@ impl<'b> VirtualDom {
) -> usize {
let props = component
.props
- .replace(None)
+ .take()
.expect("Props to always exist when a component is being created");
let unbounded_props = unsafe { std::mem::transmute(props) };
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index 0384192e2..8abf8b4ee 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -56,7 +56,9 @@ impl<'b> VirtualDom {
fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
- if left_template.template.name != right_template.template.name {
+ if !std::ptr::eq(left_template.template.name, right_template.template.name)
+ && left_template.template.name != right_template.template.name
+ {
return self.light_diff_templates(left_template, right_template);
}
diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs
index 020b307d9..594672707 100644
--- a/packages/core/src/lazynodes.rs
+++ b/packages/core/src/lazynodes.rs
@@ -11,8 +11,9 @@
//! The logic for this was borrowed from . Unfortunately, this crate does not
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
+use smallbox::{smallbox, space::S16, SmallBox};
+
use crate::{innerlude::VNode, ScopeState};
-use std::mem;
/// A concrete type provider for closures that build [`VNode`] structures.
///
@@ -24,14 +25,7 @@ use std::mem;
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
/// ```
pub struct LazyNodes<'a, 'b> {
- inner: StackNodeStorage<'a, 'b>,
-}
-
-type StackHeapSize = [usize; 16];
-
-enum StackNodeStorage<'a, 'b> {
- Stack(LazyStack),
- Heap(Box) -> Option> + 'b>),
+ inner: SmallBox VNode<'a> + 'b, S16>,
}
impl<'a, 'b> LazyNodes<'a, 'b> {
@@ -44,114 +38,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
let mut slot = Some(val);
- let val = move |fac: Option<&'a ScopeState>| {
- fac.map(
- slot.take()
- .expect("LazyNodes closure to be called only once"),
- )
- };
-
- // miri does not know how to work with mucking directly into bytes
- // just use a heap allocated type when miri is running
- if cfg!(miri) {
- Self {
- inner: StackNodeStorage::Heap(Box::new(val)),
- }
- } else {
- unsafe { LazyNodes::new_inner(val) }
- }
- }
-
- /// Create a new [`LazyNodes`] closure, but force it onto the heap.
- pub fn new_boxed(inner: F) -> Self
- where
- F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b,
- {
- // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
- let mut slot = Some(inner);
-
Self {
- inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| {
- fac.map(
- slot.take()
- .expect("LazyNodes closure to be called only once"),
- )
- })),
- }
- }
-
- unsafe fn new_inner(val: F) -> Self
- where
- F: FnMut(Option<&'a ScopeState>) -> Option> + 'b,
- {
- let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option>;
-
- assert_eq!(
- ptr as *const u8, &val as *const _ as *const u8,
- "MISUSE: Closure returned different pointer"
- );
- assert_eq!(
- std::mem::size_of_val(&*ptr),
- std::mem::size_of::(),
- "MISUSE: Closure returned a subset pointer"
- );
-
- let words = ptr_as_slice(&mut ptr);
- assert!(
- words[0] == &val as *const _ as usize,
- "BUG: Pointer layout is not (data_ptr, info...)"
- );
-
- // - Ensure that Self is aligned same as data requires
- assert!(
- std::mem::align_of::() <= std::mem::align_of::(),
- "TODO: Enforce alignment >{} (requires {})",
- std::mem::align_of::(),
- std::mem::align_of::()
- );
-
- let info = &words[1..];
- let data = words[0] as *mut ();
- let size = mem::size_of::();
-
- let stored_size = info.len() * mem::size_of::() + size;
- let max_size = mem::size_of::();
-
- if stored_size > max_size {
- Self {
- inner: StackNodeStorage::Heap(Box::new(val)),
- }
- } else {
- let mut buf: StackHeapSize = StackHeapSize::default();
-
- assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
-
- // Place pointer information at the end of the region
- // - Allows the data to be at the start for alignment purposes
- {
- let info_ofs = buf.as_ref().len() - info.len();
- let info_dst = &mut buf.as_mut()[info_ofs..];
- for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
- *d = *v;
- }
- }
-
- let src_ptr = data as *const u8;
- let dataptr = buf.as_mut_ptr().cast::();
-
- for i in 0..size {
- *dataptr.add(i) = *src_ptr.add(i);
- }
-
- std::mem::forget(val);
-
- Self {
- inner: StackNodeStorage::Stack(LazyStack {
- _align: [],
- buf,
- dropped: false,
- }),
- }
+ inner: smallbox!(move |f| {
+ let val = slot.take().expect("cannot call LazyNodes twice");
+ val(f)
+ }),
}
}
@@ -163,88 +54,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
/// let node = f.call(cac);
/// ```
#[must_use]
- pub fn call(self, f: &'a ScopeState) -> VNode<'a> {
- match self.inner {
- StackNodeStorage::Heap(mut lazy) => {
- lazy(Some(f)).expect("Closure should not be called twice")
- }
- StackNodeStorage::Stack(mut stack) => stack.call(f),
+ pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> {
+ if self.inner.is_heap() {
+ panic!();
}
+ (self.inner)(f)
}
}
-
-struct LazyStack {
- _align: [u64; 0],
- buf: StackHeapSize,
- dropped: bool,
-}
-
-impl LazyStack {
- fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> {
- let LazyStack { buf, .. } = self;
- let data = buf.as_ref();
-
- let info_size =
- mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option>>()
- / mem::size_of::()
- - 1;
-
- let info_ofs = data.len() - info_size;
-
- let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option> =
- unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
-
- self.dropped = true;
-
- let clos = unsafe { &mut *g };
- clos(Some(f)).unwrap()
- }
-}
-impl Drop for LazyStack {
- fn drop(&mut self) {
- if !self.dropped {
- let LazyStack { buf, .. } = self;
- let data = buf.as_ref();
-
- let info_size =
- mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option>>()
- / mem::size_of::()
- - 1;
-
- let info_ofs = data.len() - info_size;
-
- let g: *mut dyn FnMut(Option<&ScopeState>) -> Option> =
- unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
-
- self.dropped = true;
-
- let clos = unsafe { &mut *g };
- clos(None);
- }
- }
-}
-
-/// Obtain mutable access to a pointer's words
-fn ptr_as_slice(ptr: &mut T) -> &mut [usize] {
- assert!(mem::size_of::() % mem::size_of::() == 0);
- let words = mem::size_of::() / mem::size_of::();
- // SAFE: Points to valid memory (a raw pointer)
- unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
-}
-
-/// Re-construct a fat pointer
-unsafe fn make_fat_ptr(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
- let mut rv = mem::MaybeUninit::<*mut T>::uninit();
- {
- let s = ptr_as_slice(&mut rv);
- s[0] = data_ptr;
- s[1..].copy_from_slice(meta_vals);
- }
- let rv = rv.assume_init();
- assert_eq!(rv as *const (), data_ptr as *const ());
- rv
-}
-
-fn round_to_words(len: usize) -> usize {
- (len + mem::size_of::() - 1) / mem::size_of::()
-}
diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs
index af36f1aa6..db328fd93 100644
--- a/packages/core/src/scope_arena.rs
+++ b/packages/core/src/scope_arena.rs
@@ -8,6 +8,7 @@ use crate::{
scopes::{ScopeId, ScopeState},
virtual_dom::VirtualDom,
};
+use bumpalo::Bump;
use futures_util::FutureExt;
use std::{
mem,
@@ -34,8 +35,8 @@ impl VirtualDom {
props: Some(props),
name,
placeholder: Default::default(),
- node_arena_1: BumpFrame::new(50),
- node_arena_2: BumpFrame::new(50),
+ node_arena_1: BumpFrame::new(0),
+ node_arena_2: BumpFrame::new(0),
spawned_tasks: Default::default(),
render_cnt: Default::default(),
hook_arena: Default::default(),
@@ -62,7 +63,13 @@ impl VirtualDom {
let mut new_nodes = unsafe {
let scope = self.scopes[scope_id.0].as_mut();
- scope.previous_frame_mut().bump.reset();
+ // if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
+ if scope.previous_frame().bump.allocated_bytes() == 0 {
+ scope.previous_frame_mut().bump =
+ Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
+ } else {
+ scope.previous_frame_mut().bump.reset();
+ }
// Make sure to reset the hook counter so we give out hooks in the right order
scope.hook_idx.set(0);
@@ -128,8 +135,8 @@ impl VirtualDom {
let frame = scope.previous_frame();
// set the new head of the bump frame
- let alloced = &*frame.bump.alloc(new_nodes);
- frame.node.set(alloced);
+ let allocated = &*frame.bump.alloc(new_nodes);
+ frame.node.set(allocated);
// And move the render generation forward by one
scope.render_cnt.set(scope.render_cnt.get() + 1);
@@ -141,6 +148,6 @@ impl VirtualDom {
});
// rebind the lifetime now that its stored internally
- unsafe { mem::transmute(alloced) }
+ unsafe { mem::transmute(allocated) }
}
}
diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs
index b1bbb42fa..c46bbd4f0 100644
--- a/packages/core/src/scopes.rs
+++ b/packages/core/src/scopes.rs
@@ -10,10 +10,10 @@ use crate::{
Attribute, AttributeValue, Element, Event, Properties, TaskId,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
+use fxhash::{FxHashMap, FxHashSet};
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
- collections::{HashMap, HashSet},
fmt::Arguments,
future::Future,
rc::Rc,
@@ -82,10 +82,10 @@ pub struct ScopeState {
pub(crate) hook_list: RefCell>,
pub(crate) hook_idx: Cell,
- pub(crate) shared_contexts: RefCell>>,
+ pub(crate) shared_contexts: RefCell>>,
pub(crate) tasks: Rc,
- pub(crate) spawned_tasks: HashSet,
+ pub(crate) spawned_tasks: FxHashSet,
pub(crate) props: Option>>,
pub(crate) placeholder: Cell