Merge branch 'upstream' into fix-non-str-attributes

This commit is contained in:
Evan Almloff committed 2022-12-09 16:24:02 -06:00
commit 0c0158f62d
20 files changed
+542 -312

No files matched your search

+11 -15
View File
@@ -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
@@ -199,19 +202,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;
}
@@ -282,7 +278,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(
@@ -293,7 +289,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) };
+3 -1
View File
@@ -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);
}
+11 -198
View File
@@ -11,8 +11,9 @@
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. 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<dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b>),
inner: SmallBox<dyn FnMut(&'a ScopeState) -> 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<F>(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<F>(val: F) -> Self
where
F: FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b,
{
let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>;
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::<F>(),
"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::<F>() <= std::mem::align_of::<Self>(),
"TODO: Enforce alignment >{} (requires {})",
std::mem::align_of::<Self>(),
std::mem::align_of::<F>()
);
let info = &words[1..];
let data = words[0] as *mut ();
let size = mem::size_of::<F>();
let stored_size = info.len() * mem::size_of::<usize>() + size;
let max_size = mem::size_of::<StackHeapSize>();
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::<u8>();
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<VNode<'a>>>()
/ mem::size_of::<usize>()
- 1;
let info_ofs = data.len() - info_size;
let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> =
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<VNode<'_>>>()
/ mem::size_of::<usize>()
- 1;
let info_ofs = data.len() - info_size;
let g: *mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>> =
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<T>(ptr: &mut T) -> &mut [usize] {
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
let words = mem::size_of::<T>() / mem::size_of::<usize>();
// 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<T: ?Sized>(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::<usize>() - 1) / mem::size_of::<usize>()
}
+13 -6
View File
@@ -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) }
}
}
+3 -3
View File
@@ -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<Vec<*mut dyn Any>>,
pub(crate) hook_idx: Cell<usize>,
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: HashSet<TaskId>,
pub(crate) spawned_tasks: FxHashSet<TaskId>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
pub(crate) placeholder: Cell<Option<ElementId>>,
+3 -9
View File
@@ -14,15 +14,9 @@ use crate::{
AttributeValue, Element, Event, Scope, SuspenseContext,
};
use futures_util::{pin_mut, StreamExt};
use fxhash::FxHashMap;
use slab::Slab;
use std::{
any::Any,
borrow::BorrowMut,
cell::Cell,
collections::{BTreeSet, HashMap},
future::Future,
rc::Rc,
};
use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
/// A virtual node system that progresses user events and diffs UI trees.
///
@@ -148,7 +142,7 @@ use std::{
/// }
/// ```
pub struct VirtualDom {
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
pub(crate) scopes: Slab<Box<ScopeState>>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>,