work on optimizing web implementation
This commit is contained in:
1 parent
a61daf220d
commit
6102902387
17 files changed
+352
-298
No files matched your search
@@ -69,7 +69,6 @@ impl VirtualDom {
|
||||
)
|
||||
}
|
||||
|
||||
println!("reclaiming {:?}", el);
|
||||
self.elements.try_remove(el.0)
|
||||
}
|
||||
|
||||
@@ -95,9 +94,7 @@ impl VirtualDom {
|
||||
// Drop all the hooks once the children are dropped
|
||||
// this means we'll drop hooks bottom-up
|
||||
for hook in scope.hook_list.get_mut().drain(..) {
|
||||
println!("dropping hook !");
|
||||
drop(unsafe { BumpBox::from_raw(hook) });
|
||||
println!("hook dropped !");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -388,11 +388,6 @@ impl<'b> VirtualDom {
|
||||
// Set the placeholder of the scope
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
|
||||
println!(
|
||||
"assigning id {:?} to path {:?}, template: {:?}",
|
||||
new_id, &template.template.node_paths, template.template
|
||||
);
|
||||
|
||||
// Since the placeholder is already in the DOM, we don't create any new nodes
|
||||
self.mutations.push(AssignId {
|
||||
id: new_id,
|
||||
|
||||
@@ -61,17 +61,10 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
println!(
|
||||
"diffing node {:#?},\n\n{:#?}",
|
||||
left_template.template.name, right_template.template.name
|
||||
);
|
||||
|
||||
if left_template.template.name != right_template.template.name {
|
||||
return self.light_diff_templates(left_template, right_template);
|
||||
}
|
||||
|
||||
println!("diffing attributs...");
|
||||
|
||||
for (left_attr, right_attr) in left_template
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
@@ -106,8 +99,6 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
println!("diffing dyn nodes...");
|
||||
|
||||
for (idx, (left_node, right_node)) in left_template
|
||||
.dynamic_nodes
|
||||
.iter()
|
||||
@@ -133,8 +124,6 @@ impl<'b> VirtualDom {
|
||||
};
|
||||
}
|
||||
|
||||
println!("applying roots...");
|
||||
|
||||
// Make sure the roots get transferred over
|
||||
for (left, right) in left_template
|
||||
.root_ids
|
||||
@@ -178,7 +167,6 @@ impl<'b> VirtualDom {
|
||||
) {
|
||||
// Replace components that have different render fns
|
||||
if left.render_fn != right.render_fn {
|
||||
dbg!(&left, &right);
|
||||
let created = self.create_component_node(right_template, right, idx);
|
||||
let head = unsafe {
|
||||
self.scopes[left.scope.get().unwrap().0]
|
||||
@@ -396,8 +384,6 @@ impl<'b> VirtualDom {
|
||||
"all siblings must be keyed or all siblings must be non-keyed"
|
||||
);
|
||||
|
||||
println!("Diffing fragment {:?}, {:?}", old.len(), new.len());
|
||||
|
||||
if new_is_keyed && old_is_keyed {
|
||||
self.diff_keyed_children(old, new);
|
||||
} else {
|
||||
@@ -420,9 +406,7 @@ impl<'b> VirtualDom {
|
||||
debug_assert!(!new.is_empty());
|
||||
debug_assert!(!old.is_empty());
|
||||
|
||||
println!("Diffing non keyed children");
|
||||
|
||||
match dbg!(old.len().cmp(&new.len())) {
|
||||
match old.len().cmp(&new.len()) {
|
||||
Ordering::Greater => self.remove_nodes(&old[new.len()..]),
|
||||
Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
|
||||
Ordering::Equal => {}
|
||||
|
||||
+15
-198
@@ -11,8 +11,13 @@
|
||||
//! 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::{S1, S16, S4, S8},
|
||||
SmallBox,
|
||||
};
|
||||
|
||||
use crate::{innerlude::VNode, ScopeState};
|
||||
use std::mem;
|
||||
|
||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||
///
|
||||
@@ -24,14 +29,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 +42,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 +58,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>()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use fxhash::FxHashSet;
|
||||
|
||||
use crate::{arena::ElementId, ScopeId, Template};
|
||||
use crate::{arena::ElementId, ScopeId, ScopeState, Template};
|
||||
|
||||
/// A container for all the relevant steps to modify the Real DOM
|
||||
///
|
||||
@@ -257,3 +257,9 @@ pub enum Mutation<'a> {
|
||||
id: ElementId,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sizes() {
|
||||
println!("mutations: {}", std::mem::size_of::<Mutation>());
|
||||
println!("scope: {}", std::mem::size_of::<ScopeState>());
|
||||
}
|
||||
@@ -40,8 +40,6 @@ impl VirtualDom {
|
||||
}
|
||||
|
||||
pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
|
||||
println!("suspense notified");
|
||||
|
||||
let leaf = self
|
||||
.scheduler
|
||||
.leaves
|
||||
@@ -67,13 +65,6 @@ impl VirtualDom {
|
||||
// safety: we're not going to modify the suspense context but we don't want to make a clone of it
|
||||
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
|
||||
|
||||
println!("ready pool");
|
||||
|
||||
println!(
|
||||
"Existing mutations {:?}, scope {:?}",
|
||||
fiber.mutations, fiber.id
|
||||
);
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let arena = scope.current_frame();
|
||||
|
||||
@@ -106,16 +97,9 @@ impl VirtualDom {
|
||||
std::mem::swap(&mut self.mutations, mutations);
|
||||
|
||||
if fiber.waiting_on.borrow().is_empty() {
|
||||
println!("fiber is finished!");
|
||||
self.finished_fibers.push(fiber.id);
|
||||
} else {
|
||||
println!("fiber is not finished {:?}", fiber.waiting_on);
|
||||
}
|
||||
} else {
|
||||
println!("nodes arent right");
|
||||
}
|
||||
} else {
|
||||
println!("not ready");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,8 @@ impl VirtualDom {
|
||||
height,
|
||||
props: Some(props),
|
||||
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(),
|
||||
|
||||
@@ -10,6 +10,7 @@ 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},
|
||||
@@ -81,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>>,
|
||||
@@ -383,9 +384,35 @@ impl<'src> ScopeState {
|
||||
/// Uses the currently active [`Bump`] allocator
|
||||
pub fn raw_text(&'src self, args: Arguments) -> &'src str {
|
||||
args.as_str().unwrap_or_else(|| {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut str_buf = bumpalo::collections::String::new_in(self.bump());
|
||||
str_buf.write_fmt(args).unwrap();
|
||||
struct Wrapper<'a, 'b>(&'a mut bumpalo::collections::Vec<'b, u8>);
|
||||
|
||||
impl<'a, 'b> std::fmt::Write for Wrapper<'a, 'b> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
unsafe {
|
||||
copy(s, self.0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn write_char(&mut self, c: char) -> std::fmt::Result {
|
||||
self.0.push(c as u8);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn copy<'a>(s: &str, buf: &mut bumpalo::collections::Vec<'a, u8>) {
|
||||
let old_len = buf.len();
|
||||
buf.reserve(s.len());
|
||||
let ptr = buf.as_mut_ptr().add(old_len);
|
||||
let bytes = s.as_bytes();
|
||||
let str_ptr = bytes.as_ptr();
|
||||
for o in 0..s.len() {
|
||||
*ptr.add(o) = *str_ptr.add(o);
|
||||
}
|
||||
buf.set_len(old_len + s.len());
|
||||
}
|
||||
|
||||
let _ = unsafe { std::fmt::write(&mut Wrapper(str_buf.as_mut_vec()), args) };
|
||||
str_buf.into_bump_str()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::{
|
||||
AttributeValue, Element, Event, Scope, SuspenseContext,
|
||||
};
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use slab::Slab;
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -148,7 +149,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>,
|
||||
@@ -362,8 +363,6 @@ impl VirtualDom {
|
||||
let target_path = el_ref.path;
|
||||
|
||||
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||
println!("{:?} \n {:?} \n {:?}", attr, name, element);
|
||||
|
||||
let this_path = template.template.attr_paths[idx];
|
||||
|
||||
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
||||
@@ -385,8 +384,6 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
println!("calling listeners: {:?}", listeners);
|
||||
|
||||
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
||||
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
||||
for listener in listeners.drain(..).rev() {
|
||||
@@ -403,8 +400,6 @@ impl VirtualDom {
|
||||
|
||||
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
|
||||
}
|
||||
|
||||
println!("all listeners exhausted");
|
||||
}
|
||||
|
||||
/// Wait for the scheduler to have any work.
|
||||
@@ -527,7 +522,6 @@ impl VirtualDom {
|
||||
///
|
||||
/// If no suspense trees are present
|
||||
pub async fn render_with_deadline(&mut self, deadline: impl Future<Output = ()>) -> Mutations {
|
||||
println!("render with deadline");
|
||||
pin_mut!(deadline);
|
||||
|
||||
loop {
|
||||
@@ -538,11 +532,11 @@ impl VirtualDom {
|
||||
|
||||
self.mutations
|
||||
.templates
|
||||
.extend(context.mutations.borrow_mut().templates.drain(..));
|
||||
.append(&mut context.mutations.borrow_mut().templates);
|
||||
|
||||
self.mutations
|
||||
.edits
|
||||
.extend(context.mutations.borrow_mut().edits.drain(..));
|
||||
.append(&mut context.mutations.borrow_mut().edits);
|
||||
|
||||
// TODO: count how many nodes are on the stack?
|
||||
self.mutations.push(Mutation::ReplaceWith {
|
||||
|
||||
Reference in new issue
Block a user