feat: a new vnode type for anchors

This commit is contained in:
Jonathan Kelley committed 2021-07-29 18:04:09 -04:00
1 parent 63a85e4865
commit d618092e9d
15 files changed
+715 -441

No files matched your search

+7 -2
View File
@@ -5,7 +5,9 @@ use std::{cell::UnsafeCell, rc::Rc};
use crate::heuristics::*;
use crate::innerlude::*;
use futures_util::stream::FuturesUnordered;
use fxhash::{FxHashMap, FxHashSet};
use slab::Slab;
use smallvec::SmallVec;
// slotmap::new_key_type! {
// // A dedicated key type for the all the scopes
@@ -47,7 +49,7 @@ pub struct SharedResources {
/// We use a SlotSet to keep track of the keys that are currently being used.
/// However, we don't store any specific data since the "mirror"
pub raw_elements: Shared<Slab<()>>,
pub raw_elements: Rc<RefCell<Slab<()>>>,
pub task_setter: Rc<dyn Fn(ScopeId)>,
}
@@ -133,7 +135,10 @@ impl SharedResources {
}
/// return the id, freeing the space of the original node
pub fn collect_garbage(&self, id: ElementId) {}
pub fn collect_garbage(&self, id: ElementId) {
let mut r: RefMut<Slab<()>> = self.raw_elements.borrow_mut();
r.remove(id.0);
}
pub fn borrow_queue(&self) -> RefMut<Vec<HeightMarker>> {
self.event_queue.borrow_mut()
+5 -5
View File
@@ -138,8 +138,8 @@ impl<'src, P> Context<'src, P> {
self,
lazy_nodes: LazyNodes<'src, F>,
) -> DomTree<'src> {
let scope_ref = self.scope;
Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref }))
let bump = &self.scope.frames.wip_frame().bump;
Some(lazy_nodes.into_vnode(NodeFactory { bump }))
}
/// `submit_task` will submit the future to be polled.
@@ -178,7 +178,7 @@ impl<'src, P> Context<'src, P> {
///
pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
let mut scope = Some(self.scope);
let mut par = None;
let mut parent = None;
let ty = TypeId::of::<T>();
while let Some(inner) = scope {
@@ -202,7 +202,7 @@ impl<'src, P> Context<'src, P> {
.clone()
.downcast::<T>()
.expect("Should not fail, already validated the type from the hashmap");
par = Some(rc);
parent = Some(rc);
break;
} else {
match inner.parent_idx {
@@ -213,7 +213,7 @@ impl<'src, P> Context<'src, P> {
}
}
}
par
parent
}
/// Store a value between renders
+408 -356
View File
File diff suppressed because it is too large. Load diff
+21
View File
@@ -64,3 +64,24 @@ pub enum DomEdit<'bump> {
name: &'static str,
},
}
impl DomEdit<'_> {
pub fn is(&self, id: &'static str) -> bool {
match self {
DomEdit::PushRoot { .. } => id == "PushRoot",
DomEdit::PopRoot => id == "PopRoot",
DomEdit::AppendChildren { .. } => id == "AppendChildren",
DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
DomEdit::Remove => id == "Remove",
DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
DomEdit::CreateElement { .. } => id == "CreateElement",
DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
DomEdit::NewEventListener { .. } => id == "NewEventListener",
DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
DomEdit::SetText { .. } => id == "SetText",
DomEdit::SetAttribute { .. } => id == "SetAttribute",
DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
}
}
}
+12 -7
View File
@@ -9,7 +9,10 @@ use std::{
rc::Rc,
};
use crate::innerlude::{ElementId, HeightMarker, ScopeId};
use crate::{
innerlude::{ElementId, HeightMarker, ScopeId},
VNode,
};
#[derive(Debug)]
pub struct EventTrigger {
@@ -46,15 +49,16 @@ impl EventTrigger {
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
#[derive(Debug)]
pub enum EventPriority {
/// "Immediate" work will interrupt whatever work is currently being done and force its way through. This type of work
/// is typically reserved for small changes to single elements.
/// Garbage collection is a type of work than can be scheduled around other work, but must be completed in a specific
/// order. The GC must be run for a component before any other future work for that component is run. Otherwise,
/// we will leak slots in our slab.
///
/// The primary user of the "Immediate" priority is the `Signal` API which performs surgical mutations to the DOM.
Immediate,
/// Garbage collection mixes with the safety aspects of the virtualdom so it's very important to get it done before
/// other work.
GarbageCollection,
/// "High Priority" work will not interrupt other high priority work, but will interrupt long medium and low priority work.
///
///
/// This is typically reserved for things like user interaction.
High,
@@ -106,6 +110,8 @@ pub enum VirtualEvent {
MouseEvent(on::MouseEvent),
PointerEvent(on::PointerEvent),
GarbageCollection,
// image event has conflicting method types
// ImageEvent(event_data::ImageEvent),
SetStateEvent {
@@ -198,7 +204,6 @@ pub mod on {
Listener {
event: shortname,
mounted_node: Cell::new(None),
scope: c.scope.our_arena_idx,
callback: RefCell::new(Some(callback)),
}
}
+4 -4
View File
@@ -190,11 +190,10 @@ where
None => {
//
Some(VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Suspended {
kind: VNodeKind::Suspended(VSuspended {
node: domnode.clone(),
},
}),
})
}
}
@@ -250,7 +249,8 @@ impl<'src> SuspendedContext<'src> {
lazy_nodes: LazyNodes<'src, F>,
) -> DomTree<'src> {
let scope_ref = self.inner.scope;
let bump = &self.inner.scope.frames.wip_frame().bump;
Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref }))
Some(lazy_nodes.into_vnode(NodeFactory { bump }))
}
}
+1
View File
@@ -1,4 +1,5 @@
#![allow(non_snake_case)]
#![doc = include_str!("../README.md")]
//! Dioxus Core
//! ----------
//!
+65 -36
View File
@@ -7,7 +7,7 @@ use crate::{
events::VirtualEvent,
innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC},
};
use bumpalo::boxed::Box as BumpBox;
use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{
cell::{Cell, RefCell},
fmt::{Arguments, Debug, Formatter},
@@ -20,16 +20,24 @@ pub struct VNode<'src> {
pub kind: VNodeKind<'src>,
pub(crate) key: Option<&'src str>,
/// ElementId supports NonZero32 and Cell is zero cost, so the size of this field is unaffected
pub(crate) dom_id: Cell<Option<ElementId>>,
}
impl VNode<'_> {
pub fn key(&self) -> Option<&str> {
impl<'src> VNode<'src> {
pub fn key(&self) -> Option<&'src str> {
self.key
}
pub fn element_id(&self) -> Option<ElementId> {
self.dom_id.get()
pub fn direct_id(&self) -> ElementId {
self.try_direct_id().unwrap()
}
pub fn try_direct_id(&self) -> Option<ElementId> {
match &self.kind {
VNodeKind::Text(el) => el.dom_id.get(),
VNodeKind::Element(el) => el.dom_id.get(),
VNodeKind::Anchor(el) => el.dom_id.get(),
VNodeKind::Fragment(_) => None,
VNodeKind::Component(_) => None,
VNodeKind::Suspended(_) => None,
}
}
}
@@ -47,11 +55,18 @@ pub enum VNodeKind<'src> {
Component(&'src VComponent<'src>),
Suspended { node: Rc<Cell<Option<ElementId>>> },
Suspended(VSuspended),
Anchor(VAnchor),
}
pub struct VAnchor {
pub dom_id: Cell<Option<ElementId>>,
}
pub struct VText<'src> {
pub text: &'src str,
pub dom_id: Cell<Option<ElementId>>,
pub is_static: bool,
}
@@ -77,6 +92,7 @@ pub struct VElement<'a> {
// tag is always static
pub tag_name: &'static str,
pub namespace: Option<&'static str>,
pub dom_id: Cell<Option<ElementId>>,
pub static_listeners: bool,
pub listeners: &'a [Listener<'a>],
@@ -110,8 +126,6 @@ pub struct Listener<'bump> {
/// The type of event to listen for.
pub(crate) event: &'static str,
pub scope: ScopeId,
pub mounted_node: Cell<Option<ElementId>>,
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(VirtualEvent) + 'bump>>>,
@@ -150,27 +164,42 @@ pub struct VComponent<'src> {
pub(crate) user_fc: *const (),
}
pub struct VSuspended {
pub node: Rc<Cell<Option<ElementId>>>,
}
/// This struct provides an ergonomic API to quickly build VNodes.
///
/// NodeFactory is used to build VNodes in the component's memory space.
/// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Copy, Clone)]
pub struct NodeFactory<'a> {
pub scope: &'a Scope,
pub(crate) bump: &'a Bump,
}
impl<'a> NodeFactory<'a> {
pub fn new(bump: &'a Bump) -> NodeFactory<'a> {
NodeFactory { bump }
}
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
&self.scope.frames.wip_frame().bump
&self.bump
}
pub fn render_directly<F>(&self, lazy_nodes: LazyNodes<'a, F>) -> DomTree<'a>
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
{
Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump }))
}
pub fn unstable_place_holder() -> VNode<'static> {
VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Text(VText {
text: "",
dom_id: empty_cell(),
is_static: true,
}),
}
@@ -179,9 +208,9 @@ impl<'a> NodeFactory<'a> {
/// Used in a place or two to make it easier to build vnodes from dummy text
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Text(VText {
dom_id: empty_cell(),
text,
is_static: true,
}),
@@ -208,9 +237,12 @@ impl<'a> NodeFactory<'a> {
pub fn text(&self, args: Arguments) -> VNode<'a> {
let (text, is_static) = self.raw_text(args);
VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Text(VText { text, is_static }),
kind: VNodeKind::Text(VText {
text,
is_static,
dom_id: empty_cell(),
}),
}
}
@@ -260,19 +292,7 @@ impl<'a> NodeFactory<'a> {
let children: &'a V = self.bump().alloc(children);
let children = children.as_ref();
// We take the references directly from the bump arena
//
// TODO: this code shouldn't necessarily be here of all places
// It would make more sense to do this in diffing
let mut queue = self.scope.listeners.borrow_mut();
for listener in listeners.iter() {
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
queue.push(long_listener as *const _)
}
VNode {
dom_id: empty_cell(),
key,
kind: VNodeKind::Element(self.bump().alloc(VElement {
tag_name: tag,
@@ -280,6 +300,7 @@ impl<'a> NodeFactory<'a> {
listeners,
attributes,
children,
dom_id: empty_cell(),
// todo: wire up more constization
static_listeners: false,
@@ -291,11 +312,10 @@ impl<'a> NodeFactory<'a> {
pub fn suspended() -> VNode<'static> {
VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Suspended {
kind: VNodeKind::Suspended(VSuspended {
node: Rc::new(empty_cell()),
},
}),
}
}
@@ -390,7 +410,6 @@ impl<'a> NodeFactory<'a> {
VNode {
key,
dom_id: empty_cell(),
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
user_fc,
comparator,
@@ -430,7 +449,6 @@ impl<'a> NodeFactory<'a> {
let children = node_iter.into_vnode_list(self);
VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Fragment(VFragment {
children,
@@ -491,6 +509,15 @@ where
}
}
if nodes.len() == 0 {
nodes.push(VNode {
kind: VNodeKind::Anchor(VAnchor {
dom_id: empty_cell(),
}),
key: None,
});
}
nodes.into_bump_slice()
}
}
@@ -625,8 +652,10 @@ impl Debug for NodeFactory<'_> {
impl Debug for VNode<'_> {
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match &self.kind {
VNodeKind::Element(el) => write!(s, "element, {}", el.tag_name),
VNodeKind::Text(t) => write!(s, "text, {}", t.text),
VNodeKind::Element(el) => write!(s, "VElement {{ name: {} }}", el.tag_name),
VNodeKind::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
VNodeKind::Anchor(a) => write!(s, "VAnchor"),
VNodeKind::Fragment(_) => write!(s, "fragment"),
VNodeKind::Suspended { .. } => write!(s, "suspended"),
VNodeKind::Component(_) => write!(s, "component"),
+35 -10
View File
@@ -1,5 +1,6 @@
use crate::innerlude::*;
use bumpalo::boxed::Box as BumpBox;
use fxhash::FxHashSet;
use std::{
any::{Any, TypeId},
borrow::BorrowMut,
@@ -24,13 +25,15 @@ pub struct Scope {
pub(crate) parent_idx: Option<ScopeId>,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
pub(crate) descendents: RefCell<FxHashSet<ScopeId>>,
// Nodes
// an internal, highly efficient storage of vnodes
// lots of safety condsiderations
pub(crate) frames: ActiveFrame,
pub(crate) caller: Rc<WrappedCaller>,
pub(crate) child_nodes: ScopeChildren<'static>,
pub(crate) pending_garbage: RefCell<Vec<*const VNode<'static>>>,
// Listeners
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
@@ -67,11 +70,18 @@ impl Scope {
height: u32,
child_nodes: ScopeChildren,
// child_nodes: &'creator_node [VNode<'creator_node>],
vdom: SharedResources,
) -> Self {
let child_nodes = unsafe { child_nodes.extend_lifetime() };
// insert ourself as a descendent of the parent
// when the parent is removed, this map will be traversed, and we will also be cleaned up.
if let Some(parent) = &parent {
let parent = unsafe { vdom.get_scope(*parent) }.unwrap();
parent.descendents.borrow_mut().insert(arena_idx);
}
Self {
child_nodes,
caller,
@@ -80,10 +90,12 @@ impl Scope {
height,
vdom,
frames: ActiveFrame::new(),
hooks: Default::default(),
shared_contexts: Default::default(),
listeners: Default::default(),
descendents: Default::default(),
pending_garbage: Default::default(),
}
}
@@ -102,6 +114,9 @@ impl Scope {
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
if !self.pending_garbage.borrow().is_empty() {
panic!("cannot run scope while garbage is pending! Please clean up your mess first");
}
log::debug!("reset okay");
@@ -189,11 +204,11 @@ impl Scope {
if let Some(raw_listener) = raw_listener {
let listener = unsafe { &**raw_listener };
log::info!(
"calling listener {:?}, {:?}",
listener.event,
listener.scope
);
// log::info!(
// "calling listener {:?}, {:?}",
// listener.event,
// // listener.scope
// );
let mut cb = listener.callback.borrow_mut();
if let Some(cb) = cb.as_mut() {
(cb)(event);
@@ -209,10 +224,20 @@ impl Scope {
self.frames.fin_head()
}
// #[inline]
pub fn child_nodes<'a>(&'a self) -> ScopeChildren {
// self.child_nodes
unsafe { self.child_nodes.unextend_lfetime() }
// unsafe { std::mem::transmute(self.child_nodes) }
}
pub fn consume_garbage(&self) -> Vec<&VNode> {
let mut garbage = self.pending_garbage.borrow_mut();
garbage
.drain(..)
.map(|node| {
// safety: scopes cannot cycle without their garbage being collected. these nodes are safe
let node: &VNode<'static> = unsafe { &*node };
let node: &VNode = unsafe { std::mem::transmute(node) };
node
})
.collect::<Vec<_>>()
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::innerlude::*;
// create a cell with a "none" value
#[inline]
pub fn empty_cell() -> Cell<Option<ElementId>> {
Cell::new(None as Option<ElementId>)
Cell::new(None)
}
/// A helper type that lets scopes be ordered by their height
+49 -8
View File
@@ -25,7 +25,7 @@ use crate::{arena::SharedResources, innerlude::*};
use std::any::Any;
use std::any::TypeId;
use std::cell::RefCell;
use std::cell::{Ref, RefCell, RefMut};
use std::pin::Pin;
/// An integrated virtual node system that progresses events and diffs UI trees.
@@ -193,8 +193,8 @@ impl VirtualDom {
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope().is_ok() {
let meta = diff_machine.create(cur_component.frames.fin_head());
diff_machine.append_children(meta.added_to_stack);
let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
diff_machine.edit_append_children(meta.added_to_stack);
} else {
// todo: should this be a hard error?
log::warn!(
@@ -269,6 +269,47 @@ impl VirtualDom {
let mut diff_machine = DiffMachine::new(edits, realdom, trigger.originator, &self.shared);
match &trigger.event {
// When a scope gets destroyed during a diff, it gets its own garbage collection event
// However, an old scope might be attached
VirtualEvent::GarbageCollection => {
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
let mut garbage_list = scope.consume_garbage();
while let Some(node) = garbage_list.pop() {
match &node.kind {
VNodeKind::Text(_) => {
//
self.shared.collect_garbage(node.direct_id())
}
VNodeKind::Anchor(anchor) => {
//
}
VNodeKind::Element(el) => {
self.shared.collect_garbage(node.direct_id());
for child in el.children {
garbage_list.push(child);
}
}
VNodeKind::Fragment(frag) => {
for child in frag.children {
garbage_list.push(child);
}
}
VNodeKind::Component(comp) => {
// run the destructors
todo!();
}
VNodeKind::Suspended(node) => {
// make sure the task goes away
todo!();
}
}
}
}
// Nothing yet
VirtualEvent::AsyncEvent { .. } => {}
@@ -295,13 +336,13 @@ impl VirtualDom {
// push the old node's root onto the stack
let real_id = domnode.get().ok_or(Error::NotMounted)?;
diff_machine.push_root(real_id);
diff_machine.edit_push_root(real_id);
// push these new nodes onto the diff machines stack
let meta = diff_machine.create(&*nodes);
let meta = diff_machine.create_vnode(&*nodes);
// replace the placeholder with the new nodes we just pushed on the stack
diff_machine.replace_with(meta.added_to_stack);
diff_machine.edit_replace_with(meta.added_to_stack);
}
}
}
@@ -330,7 +371,7 @@ impl VirtualDom {
// Make sure this isn't a node we've already seen, we don't want to double-render anything
// If we double-renderer something, this would cause memory safety issues
if diff_machine.seen_nodes.contains(&update.idx) {
if diff_machine.seen_scopes.contains(&update.idx) {
log::debug!("Skipping update for: {:#?}", update);
continue;
}
@@ -342,7 +383,7 @@ impl VirtualDom {
.expect("Failed to find scope or borrow would be aliasing");
// Now, all the "seen nodes" are nodes that got notified by running this listener
diff_machine.seen_nodes.insert(update.idx.clone());
diff_machine.seen_scopes.insert(update.idx.clone());
if cur_component.run_scope().is_ok() {
let (old, new) = (