wip: bubbling in progress

This commit is contained in:
Jonathan Kelley committed 2021-11-11 21:34:20 -05:00
1 parent 8acdd2ea83
commit a21020ea57
15 files changed
+268 -291

No files matched your search

+2
View File
@@ -202,6 +202,8 @@ pub fn rsx(s: TokenStream) -> TokenStream {
/// Home,
/// #[at("/secure")]
/// Secure,
/// #[at("/profile/{id}")]
/// Profile(u32),
/// #[at("/404")]
/// NotFound,
/// }
+25 -24
View File
@@ -200,11 +200,11 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream {
);
quote! {
::std::thread_local! {
#[doc(hidden)]
#[allow(non_upper_case_globals)]
static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
}
// ::std::thread_local! {
// #[doc(hidden)]
// #[allow(non_upper_case_globals)]
// static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
// }
#[automatically_derived]
impl ::dioxus::router::Routable for #ident {
@@ -219,29 +219,30 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream {
#not_found_route
}
fn current_route() -> ::std::option::Option<Self> {
#cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
}
// fn current_route() -> ::std::option::Option<Self> {
// #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
// }
fn recognize(pathname: &str) -> ::std::option::Option<Self> {
::std::thread_local! {
static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
}
let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
{
let route = ::std::clone::Clone::clone(&route);
#cache_thread_local_ident.with(move |val| {
*val.borrow_mut() = route;
});
}
route
todo!()
// ::std::thread_local! {
// static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
// }
// let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
// {
// let route = ::std::clone::Clone::clone(&route);
// #cache_thread_local_ident.with(move |val| {
// *val.borrow_mut() = route;
// });
// }
// route
}
fn cleanup() {
#cache_thread_local_ident.with(move |val| {
*val.borrow_mut() = ::std::option::Option::None;
});
}
// fn cleanup() {
// #cache_thread_local_ident.with(move |val| {
// *val.borrow_mut() = ::std::option::Option::None;
// });
// }
}
}
}
+4 -4
View File
@@ -36,7 +36,7 @@ impl<const A: bool> FragmentBuilder<A> {
///
/// ## Example
///
/// ```rust
/// ```rust, ignore
/// fn App(cx: Context, props: &()) -> Element {
/// cx.render(rsx!{
/// CustomCard {
@@ -82,7 +82,7 @@ impl Properties for FragmentProps {
///
/// ## Example
///
/// ```rust
/// ```rust, ignore
/// rsx!{
/// Fragment { key: "abc" }
/// }
@@ -118,7 +118,7 @@ pub fn Fragment(cx: Context, props: &FragmentProps) -> Element {
/// ## Example
///
/// For props that are 'static:
/// ```rust ignore
/// ```rust, ignore ignore
/// #[derive(Props, PartialEq)]
/// struct MyProps {
/// data: String
@@ -127,7 +127,7 @@ pub fn Fragment(cx: Context, props: &FragmentProps) -> Element {
///
/// For props that borrow:
///
/// ```rust ignore
/// ```rust, ignore ignore
/// #[derive(Props)]
/// struct MyProps<'a >{
/// data: &'a str
+47 -29
View File
@@ -54,7 +54,7 @@
//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
//! ```rust
//! ```rust, ignore
//! rsx!( div { class: "hello world", "this node is entirely static" } )
//! ```
//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
@@ -147,6 +147,7 @@ pub(crate) enum DiffInstruction<'a> {
},
PopScope,
PopElement,
}
#[derive(Debug, Clone, Copy)]
@@ -162,30 +163,32 @@ pub(crate) struct DiffStack<'bump> {
pub(crate) instructions: Vec<DiffInstruction<'bump>>,
nodes_created_stack: SmallVec<[usize; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>,
pub element_stack: SmallVec<[ElementId; 10]>,
}
impl<'bump> DiffStack<'bump> {
pub fn new() -> Self {
fn new() -> Self {
Self {
instructions: Vec::with_capacity(1000),
nodes_created_stack: smallvec![],
scope_stack: smallvec![],
element_stack: smallvec![],
}
}
pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
self.instructions.pop()
}
pub fn pop_off_scope(&mut self) {
fn pop_off_scope(&mut self) {
self.scope_stack.pop();
}
pub fn push(&mut self, instruction: DiffInstruction<'bump>) {
pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) {
self.instructions.push(instruction)
}
pub fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount { and });
@@ -195,36 +198,36 @@ impl<'bump> DiffStack<'bump> {
}
}
pub fn push_subtree(&mut self) {
fn push_subtree(&mut self) {
self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount {
and: MountType::Append,
});
}
pub fn push_nodes_created(&mut self, count: usize) {
fn push_nodes_created(&mut self, count: usize) {
self.nodes_created_stack.push(count);
}
pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
pub(crate) fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount { and });
self.instructions.push(DiffInstruction::Create { node });
}
pub fn add_child_count(&mut self, count: usize) {
fn add_child_count(&mut self, count: usize) {
*self.nodes_created_stack.last_mut().unwrap() += count;
}
pub fn pop_nodes_created(&mut self) -> usize {
fn pop_nodes_created(&mut self) -> usize {
self.nodes_created_stack.pop().unwrap()
}
pub fn current_scope(&self) -> Option<ScopeId> {
fn current_scope(&self) -> Option<ScopeId> {
self.scope_stack.last().copied()
}
pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
// Push the new scope onto the stack
self.scope_stack.push(idx);
@@ -263,10 +266,13 @@ impl<'bump> DiffState<'bump> {
self.stack.add_child_count(num_on_stack);
}
DiffInstruction::PopScope => self.stack.pop_off_scope(),
DiffInstruction::PopElement => {
self.stack.element_stack.pop();
}
};
if deadline_expired() {
log::debug!("Deadline expired before we could finished!");
log::debug!("Deadline expired before we could finish!");
return false;
}
}
@@ -390,11 +396,18 @@ impl<'bump> DiffState<'bump> {
children,
namespace,
dom_id,
parent_id,
..
} = element;
let real_id = self.scopes.reserve_node(node);
// set the parent ID for event bubbling
self.stack.instructions.push(DiffInstruction::PopElement);
let parent = self.stack.element_stack.last().unwrap();
parent_id.set(Some(*parent));
// set the id of the element
let real_id = self.scopes.reserve_node(node);
self.stack.element_stack.push(real_id);
dom_id.set(Some(real_id));
self.mutations.create_element(tag_name, *namespace, real_id);
@@ -438,9 +451,11 @@ impl<'bump> DiffState<'bump> {
let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) };
let fc_ptr = vcomponent.user_fc;
let new_idx = self
.scopes
.new_with_key(fc_ptr, caller, parent_scope, height, subtree);
let container = *self.stack.element_stack.last().unwrap();
let new_idx =
self.scopes
.new_with_key(fc_ptr, caller, parent_scope, container, height, subtree);
// Actually initialize the caller's slot with the right address
vcomponent.associated_scope.set(Some(new_idx));
@@ -650,20 +665,18 @@ impl<'bump> DiffState<'bump> {
let scope = unsafe {
self.scopes
.get_scope_mut(&scope_addr)
.expect(&format!("could not find {:?}", scope_addr))
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr))
};
scope.caller = unsafe { std::mem::transmute(new.caller) };
// React doesn't automatically memoize, but we do.
let props_are_the_same = old.comparator.unwrap();
if self.force_diff || !props_are_the_same(new) {
if self.scopes.run_scope(&scope_addr) {
self.diff_node(
self.scopes.wip_head(&scope_addr),
self.scopes.fin_head(&scope_addr),
);
}
if (self.force_diff || !props_are_the_same(new)) && self.scopes.run_scope(&scope_addr) {
self.diff_node(
self.scopes.wip_head(&scope_addr),
self.scopes.fin_head(&scope_addr),
);
}
self.stack.scope_stack.pop();
@@ -1206,13 +1219,19 @@ impl<'bump> DiffState<'bump> {
fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) {
match old {
VNode::Element(el) => {
let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
let id = old
.try_mounted_id()
.unwrap_or_else(|| panic!("broke on {:?}", old));
self.mutations.replace_with(id, nodes_created as u32);
self.remove_nodes(el.children, false);
}
VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
let id = old
.try_mounted_id()
.unwrap_or_else(|| panic!("broke on {:?}", old));
self.mutations.replace_with(id, nodes_created as u32);
}
@@ -1226,7 +1245,6 @@ impl<'bump> DiffState<'bump> {
self.replace_node(node, nodes_created);
let scope_id = c.associated_scope.get().unwrap();
println!("replacing c {:?} ", scope_id);
log::debug!("Destroying scope {:?}", scope_id);
self.scopes.try_remove(&scope_id).unwrap();
}
+18 -12
View File
@@ -20,7 +20,7 @@ use std::mem;
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
///
///
/// ```rust
/// ```rust, ignore
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
/// ```
pub struct LazyNodes<'a, 'b> {
@@ -47,7 +47,14 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
fac.map(inner)
};
unsafe { LazyNodes::new_inner(val) }
// miri does not know how to work with mucking directly into bytes
if cfg!(miri) {
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
}
} else {
unsafe { LazyNodes::new_inner(val) }
}
}
unsafe fn new_inner<F>(val: F) -> Self
@@ -212,6 +219,7 @@ fn round_to_words(len: usize) -> usize {
fn it_works() {
let bump = bumpalo::Bump::new();
#[cfg(not(miri))]
simple_logger::init().unwrap();
let factory = NodeFactory { bump: &bump };
@@ -232,9 +240,10 @@ fn it_drops() {
use std::rc::Rc;
let bump = bumpalo::Bump::new();
#[cfg(not(miri))]
simple_logger::init().unwrap();
// let factory = NodeFactory { scope: &bump };
let factory = NodeFactory { bump: &bump };
struct DropInner {
id: i32,
@@ -246,7 +255,7 @@ fn it_drops() {
}
let val = Rc::new(10);
{
let caller = {
let it = (0..10)
.map(|i| {
let val = val.clone();
@@ -259,16 +268,13 @@ fn it_drops() {
})
.collect::<Vec<_>>();
let caller = NodeFactory::annotate_lazy(|f| {
NodeFactory::annotate_lazy(|f| {
log::debug!("main closure");
f.fragment_from_iter(it)
})
.unwrap();
}
.unwrap()
};
// let nodes = caller.call(factory);
// dbg!(nodes);
dbg!(Rc::strong_count(&val));
let _ = caller.call(factory);
assert_eq!(Rc::strong_count(&val), 1);
}
+20 -32
View File
@@ -25,7 +25,7 @@ pub enum VNode<'src> {
///
/// # Example
///
/// ```
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
/// let node = vdom.render_vnode(rsx!( "hello" ));
///
@@ -41,7 +41,7 @@ pub enum VNode<'src> {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
///
/// let node = vdom.render_vnode(rsx!{
@@ -66,7 +66,7 @@ pub enum VNode<'src> {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// rsx!{
/// a {}
/// link {}
@@ -81,7 +81,7 @@ pub enum VNode<'src> {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// fn Example(cx: Context, props: &()) -> Element {
/// todo!()
/// }
@@ -100,7 +100,7 @@ pub enum VNode<'src> {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
///
///
/// ```
@@ -112,7 +112,7 @@ pub enum VNode<'src> {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
///
/// let node = vdom.render_vnode(rsx!( Fragment {} ));
@@ -133,7 +133,7 @@ pub enum VNode<'src> {
/// an `rsx!` call.
///
/// # Example
/// ```rust
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
///
/// let node: NodeLink = vdom.render_vnode(rsx!( "hello" ));
@@ -202,7 +202,7 @@ impl<'src> VNode<'src> {
VNode::Linked(c) => VNode::Linked(NodeLink {
scope_id: c.scope_id.clone(),
link_idx: c.link_idx.clone(),
node: c.node.clone(),
node: c.node,
}),
}
}
@@ -282,6 +282,7 @@ pub struct VElement<'a> {
pub dom_id: Cell<Option<ElementId>>,
// Keep the parent id around so we can bubble events through the tree
pub parent_id: Cell<Option<ElementId>>,
pub listeners: &'a [Listener<'a>],
@@ -310,7 +311,7 @@ impl Debug for VElement<'_> {
///
/// This trait provides the ability to use custom elements in the `rsx!` macro.
///
/// ```rust
/// ```rust, ignore
/// struct my_element;
///
/// impl DioxusElement for my_element {
@@ -368,7 +369,6 @@ pub struct Listener<'bump> {
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
}
pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>;
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
pub struct VComponent<'src> {
@@ -381,7 +381,7 @@ pub struct VComponent<'src> {
pub(crate) can_memoize: bool,
pub(crate) hard_allocation: Cell<Option<*const ()>>,
pub(crate) _hard_allocation: Cell<Option<*const ()>>,
// Raw pointer into the bump arena for the props of the component
pub(crate) bump_props: *const (),
@@ -426,16 +426,6 @@ impl PartialEq for NodeLink {
self.node == other.node
}
}
impl NodeLink {
// we don't want to let users clone NodeLinks
pub(crate) fn clone_inner(&self) -> Self {
Self {
link_idx: self.link_idx.clone(),
scope_id: self.scope_id.clone(),
node: self.node.clone(),
}
}
}
/// This struct provides an ergonomic API to quickly build VNodes.
///
@@ -453,7 +443,7 @@ impl<'a> NodeFactory<'a> {
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
&self.bump
self.bump
}
/// Directly pass in text blocks without the need to use the format_args macro.
@@ -591,14 +581,13 @@ impl<'a> NodeFactory<'a> {
// - Because FC<P> is the same, then P must be the same (even with generics)
// - Non-static P are autoderived to memoize as false
// - This comparator is only called on a corresponding set of bumpframes
let props_memoized = unsafe {
let real_other: &P = &*(other.bump_props as *const _ as *const P);
props.memoize(real_other)
};
//
// It's only okay to memoize if there are no children and the props can be memoized
// Implementing memoize is unsafe and done automatically with the props trait
props_memoized
unsafe {
let real_other: &P = &*(other.bump_props as *const _ as *const P);
props.memoize(real_other)
}
} else {
false
}
@@ -633,8 +622,7 @@ impl<'a> NodeFactory<'a> {
let caller: &'a mut dyn Fn(&'a Scope) -> Element =
bump.alloc(move |scope: &Scope| -> Element {
let props: &'_ P = unsafe { &*(bump_props as *const P) };
let res = component(scope, props);
res
component(scope, props)
});
let can_memoize = P::IS_STATIC;
@@ -648,7 +636,7 @@ impl<'a> NodeFactory<'a> {
can_memoize,
drop_props,
associated_scope: Cell::new(None),
hard_allocation: Cell::new(None),
_hard_allocation: Cell::new(None),
}))
}
@@ -782,7 +770,7 @@ impl Debug for NodeFactory<'_> {
///
/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
/// ```
/// ```rust, ignore
/// impl IntoIterator<Item = impl IntoVNode<'a>>
/// ```
///
+26 -32
View File
@@ -43,12 +43,8 @@ pub type Context<'a> = &'a Scope;
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
/// use case they might have.
pub struct Scope {
// safety:
//
// pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
// this is just a bit of a hack to not need an Rc to the ScopeArena.
// todo: replace this will ScopeId and provide a connection to scope arena directly
pub(crate) parent_scope: Option<*mut Scope>,
pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId,
@@ -60,23 +56,14 @@ pub struct Scope {
pub(crate) generation: Cell<u32>,
// The double-buffering situation that we will use
pub(crate) frames: [BumpFrame; 2],
pub(crate) caller: *const dyn Fn(&Scope) -> Element,
/*
we care about:
- listeners (and how to call them when an event is triggered)
- borrowed props (and how to drop them when the parent is dropped)
- suspended nodes (and how to call their callback when their associated tasks are complete)
*/
pub(crate) items: RefCell<SelfReferentialItems<'static>>,
// State
pub(crate) hooks: HookList,
// todo: move this into a centralized place - is more memory efficient
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
pub(crate) sender: UnboundedSender<SchedulerMsg>,
@@ -108,7 +95,7 @@ impl Scope {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
/// dom.rebuild();
///
@@ -126,7 +113,7 @@ impl Scope {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
/// dom.rebuild();
///
@@ -146,7 +133,7 @@ impl Scope {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
/// dom.rebuild();
///
@@ -155,10 +142,7 @@ impl Scope {
/// assert_eq!(base.parent(), None);
/// ```
pub fn parent(&self) -> Option<ScopeId> {
match self.parent_scope {
Some(p) => Some(unsafe { &*p }.our_arena_idx),
None => None,
}
self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx)
}
/// Get the ID of this Scope within this Dioxus VirtualDOM.
@@ -167,7 +151,7 @@ impl Scope {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
/// dom.rebuild();
/// let base = dom.base_scope();
@@ -303,7 +287,7 @@ impl Scope {
///
/// # Example
///
/// ```
/// ```rust, ignore
/// struct SharedState(&'static str);
///
/// static App: FC<()> = |cx, props|{
@@ -311,7 +295,7 @@ impl Scope {
/// rsx!(cx, Child {})
/// }
///
/// static Child: FC<()> = |cx, props|{
/// static Child: FC<()> = |cx, props| {
/// let state = cx.consume_state::<SharedState>();
/// rsx!(cx, div { "hello {state.0}" })
/// }
@@ -351,7 +335,7 @@ impl Scope {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// fn App(cx: Context, props: &()) -> Element {
/// todo!();
/// rsx!(cx, div { "Subtree {id}"})
@@ -375,7 +359,7 @@ impl Scope {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
/// fn App(cx: Context, props: &()) -> Element {
/// let id = cx.get_current_subtree();
/// rsx!(cx, div { "Subtree {id}"})
@@ -437,6 +421,13 @@ impl Scope {
}
}
pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 0 {
true => &mut self.frames[0],
false => &mut self.frames[1],
}
}
pub(crate) fn fin_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 {
true => &self.frames[0],
@@ -444,14 +435,17 @@ impl Scope {
}
}
pub unsafe fn reset_wip_frame(&self) {
/// Reset this component's frame
///
/// # Safety:
/// This method breaks every reference of VNodes in the current frame.
pub(crate) unsafe fn reset_wip_frame(&mut self) {
// todo: unsafecell or something
let bump = self.wip_frame() as *const _ as *mut Bump;
let g = &mut *bump;
g.reset();
let bump = self.wip_frame_mut();
bump.bump.reset();
}
pub fn cycle_frame(&self) {
pub(crate) fn cycle_frame(&self) {
self.generation.set(self.generation.get() + 1);
}
@@ -485,7 +479,7 @@ impl Scope {
let mut nodes = &mut self.items.get_mut().suspended_nodes;
if let Some(suspended) = nodes.remove(&task_id) {
let sus: &'a VSuspended<'static> = unsafe { &*suspended };
let sus: &'a VSuspended<'static> = suspended;
let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
let mut boxed = sus.callback.borrow_mut().take().unwrap();
let new_node: Element = boxed();
+51 -27
View File
@@ -28,45 +28,65 @@ pub(crate) struct ScopeArena {
}
impl ScopeArena {
pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
let bump = Bump::new();
// allocate a container for the root element
// this will *never* show up in the diffing process
let el = bump.alloc(VElement {
tag_name: "root",
namespace: None,
key: None,
dom_id: Cell::new(Some(ElementId(0))),
parent_id: Default::default(),
listeners: &[],
attributes: &[],
children: &[],
});
let node = bump.alloc(VNode::Element(el));
let mut nodes = Slab::new();
let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
debug_assert_eq!(root_id, 0);
Self {
scope_counter: Cell::new(0),
bump: Bump::new(),
bump,
scopes: RefCell::new(FxHashMap::default()),
heuristics: RefCell::new(FxHashMap::default()),
free_scopes: RefCell::new(Vec::new()),
nodes: RefCell::new(Slab::new()),
nodes: RefCell::new(nodes),
sender,
}
}
pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
/// Safety:
/// - Obtaining a mutable refernece to any Scope is unsafe
/// - Scopes use interior mutability when sharing data into components
pub(crate) fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
unsafe { self.scopes.borrow().get(id).map(|f| &**f) }
}
// this is unsafe
pub unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> {
self.scopes.borrow().get(id).map(|f| *f)
pub(crate) unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> {
self.scopes.borrow().get(id).copied()
}
// this is unsafe
pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> {
pub(crate) unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> {
self.scopes.borrow().get(id).map(|s| &mut **s)
}
pub fn new_with_key(
pub(crate) fn new_with_key(
&self,
fc_ptr: *const (),
caller: *const dyn Fn(&Scope) -> Element,
parent_scope: Option<*mut Scope>,
container: ElementId,
height: u32,
subtree: u32,
) -> ScopeId {
let new_scope_id = ScopeId(self.scope_counter.get());
self.scope_counter.set(self.scope_counter.get() + 1);
//
//
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
let scope = unsafe { &mut *old_scope };
log::debug!(
@@ -80,6 +100,7 @@ impl ScopeArena {
scope.height = height;
scope.subtree = Cell::new(subtree);
scope.our_arena_idx = new_scope_id;
scope.container = container;
scope.frames[0].nodes.get_mut().push({
let vnode = scope.frames[0]
@@ -103,9 +124,8 @@ impl ScopeArena {
unsafe { std::mem::transmute(vnode as *mut VNode) }
});
let r = self.scopes.borrow_mut().insert(new_scope_id, scope);
assert!(r.is_none());
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
debug_assert!(any_item.is_none());
new_scope_id
} else {
@@ -144,6 +164,7 @@ impl ScopeArena {
let scope = self.bump.alloc(Scope {
sender: self.sender.clone(),
container,
our_arena_idx: new_scope_id,
parent_scope,
height,
@@ -166,12 +187,8 @@ impl ScopeArena {
}),
});
dbg!(self.scopes.borrow());
let r = self.scopes.borrow_mut().insert(new_scope_id, scope);
assert!(r.is_none());
// .expect(&format!("scope shouldnt exist, {:?}", new_scope_id));
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
debug_assert!(any_item.is_none());
new_scope_id
}
@@ -181,9 +198,11 @@ impl ScopeArena {
self.ensure_drop_safety(id);
log::debug!("removing scope {:?}", id);
println!("removing scope {:?}", id);
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
// Safety:
// - ensure_drop_safety ensures that no references to this scope are in use
// - this raw pointer is removed from the map
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(id).unwrap() };
// we're just reusing scopes so we need to clear it out
scope.hooks.clear();
@@ -229,6 +248,11 @@ impl ScopeArena {
id
}
pub fn update_reservation(&self, node: &VNode, id: ElementId) {
let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) };
*self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
}
pub fn collect_garbage(&self, id: ElementId) {
self.nodes.borrow_mut().remove(id.0);
}
@@ -275,15 +299,15 @@ impl ScopeArena {
}
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
log::debug!("found scope, about to run: {:?}", id);
// 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
self.ensure_drop_safety(id);
let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
log::debug!("found scope, about to run: {:?}", id);
// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
// - All children nodes that rely on &mut T are replaced with a new reference
+24 -22
View File
@@ -18,7 +18,7 @@ use std::{any::Any, collections::VecDeque};
///
/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].
///
/// ```rust
/// ```rust, ignore
/// #[derive(Props, PartialEq)]
/// struct AppProps {
/// title: String
@@ -33,7 +33,7 @@ use std::{any::Any, collections::VecDeque};
///
/// Components may be composed to make complex apps.
///
/// ```rust
/// ```rust, ignore
/// fn App(cx: Context, props: &AppProps) -> Element {
/// cx.render(rsx!(
/// NavBar { routes: ROUTES }
@@ -46,14 +46,14 @@ use std::{any::Any, collections::VecDeque};
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
/// draw the UI.
///
/// ```rust
/// ```rust, ignore
/// let mut vdom = VirtualDom::new(App);
/// let edits = vdom.rebuild();
/// ```
///
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
///
/// ```rust
/// ```rust, ignore
/// let channel = vdom.get_scheduler_channel();
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
/// // ...
@@ -62,7 +62,7 @@ use std::{any::Any, collections::VecDeque};
///
/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
///
/// ```rust
/// ```rust, ignore
/// vdom.wait_for_work().await;
/// ```
///
@@ -70,7 +70,7 @@ use std::{any::Any, collections::VecDeque};
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
/// handled by the renderer.
///
/// ```rust
/// ```rust, ignore
/// let mutations = vdom.work_with_deadline(|| false);
/// for edit in mutations {
/// apply(edit);
@@ -81,7 +81,7 @@ use std::{any::Any, collections::VecDeque};
///
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
///
/// ```rust
/// ```rust, ignore
/// fn App(cx: Context, props: &()) -> Element {
/// cx.render(rsx!{
/// div { "Hello World" }
@@ -106,7 +106,7 @@ use std::{any::Any, collections::VecDeque};
pub struct VirtualDom {
base_scope: ScopeId,
_root_caller: *mut dyn Fn(&Scope) -> Element,
_root_props: Box<dyn Any>,
scopes: Box<ScopeArena>,
@@ -134,7 +134,7 @@ impl VirtualDom {
///
///
/// # Example
/// ```
/// ```rust, ignore
/// fn Example(cx: Context, props: &()) -> Element {
/// cx.render(rsx!( div { "hello world" } ))
/// }
@@ -158,7 +158,7 @@ impl VirtualDom {
///
///
/// # Example
/// ```
/// ```rust, ignore
/// #[derive(PartialEq, Props)]
/// struct SomeProps {
/// name: &'static str
@@ -173,7 +173,7 @@ impl VirtualDom {
///
/// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
///
/// ```rust
/// ```rust, ignore
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild();
/// ```
@@ -194,9 +194,9 @@ impl VirtualDom {
) -> Self {
let scopes = ScopeArena::new(sender.clone());
let caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
let mut caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
let caller_ref: *mut dyn Fn(&Scope) -> Element = caller.as_mut() as *mut _;
let base_scope = scopes.new_with_key(root as _, caller_ref, None, ElementId(0), 0, 0);
let pending_messages = VecDeque::new();
let mut dirty_scopes = IndexSet::new();
@@ -206,8 +206,7 @@ impl VirtualDom {
scopes: Box::new(scopes),
base_scope,
receiver,
// todo: clean this up manually?
_root_caller: caller_ref,
_root_props: caller,
pending_messages,
pending_futures: Default::default(),
dirty_scopes,
@@ -239,7 +238,7 @@ impl VirtualDom {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
///
///
/// ```
@@ -251,7 +250,7 @@ impl VirtualDom {
///
/// # Example
///
/// ```rust
/// ```rust, ignore
///
///
/// ```
@@ -359,7 +358,7 @@ impl VirtualDom {
///
/// # Example
///
/// ```no_run
/// ```rust, ignore
/// fn App(cx: Context, props: &()) -> Element {
/// cx.render(rsx!( div {"hello"} ))
/// }
@@ -394,6 +393,7 @@ impl VirtualDom {
while let Some(msg) = self.pending_messages.pop_back() {
match msg {
// TODO: Suspsense
SchedulerMsg::Immediate(id) => {
self.dirty_scopes.insert(id);
}
@@ -447,6 +447,8 @@ impl VirtualDom {
self.scopes.fin_head(&scopeid),
);
diff_state.stack.scope_stack.push(scopeid);
let scope = scopes.get_scope(&scopeid).unwrap();
diff_state.stack.element_stack.push(scope.container);
diff_state.stack.push(DiffInstruction::Diff { new, old });
}
}
@@ -458,7 +460,6 @@ impl VirtualDom {
let DiffState {
mutations,
seen_scopes,
stack,
..
} = diff_state;
@@ -490,7 +491,7 @@ impl VirtualDom {
/// All state stored in components will be completely wiped away.
///
/// # Example
/// ```
/// ```rust, ignore
/// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" });
/// let mut dom = VirtualDom::new();
/// let edits = dom.rebuild();
@@ -506,6 +507,7 @@ impl VirtualDom {
.stack
.create_node(self.scopes.fin_head(&scope_id), MountType::Append);
diff_state.stack.element_stack.push(ElementId(0));
diff_state.stack.scope_stack.push(scope_id);
diff_state.work(|| false);
@@ -523,7 +525,7 @@ impl VirtualDom {
///
///
/// # Example
/// ```rust
/// ```rust, ignore
/// #[derive(PartialEq, Props)]
/// struct AppProps {
/// value: Shared<&'static str>,
+47 -47
View File
@@ -36,16 +36,16 @@ fn test_original_diff() {
assert_eq!(
mutations.edits,
[
CreateElement {
root: 0,
tag: "div"
},
CreateElement {
root: 1,
tag: "div"
},
CreateTextNode {
CreateElement {
root: 2,
tag: "div"
},
CreateTextNode {
root: 3,
text: "Hello, world!"
},
AppendChildren { many: 1 },
@@ -81,32 +81,32 @@ fn create() {
assert_eq!(
mutations.edits,
[
CreateElement {
root: 0,
tag: "div"
},
CreateElement {
root: 1,
tag: "div"
},
CreateTextNode {
root: 2,
text: "Hello, world!"
},
CreateElement {
root: 3,
root: 2,
tag: "div"
},
CreateTextNode {
root: 3,
text: "Hello, world!"
},
CreateElement {
root: 4,
tag: "div"
},
CreateTextNode {
CreateElement {
root: 5,
text: "hello"
tag: "div"
},
CreateTextNode {
root: 6,
text: "hello"
},
CreateTextNode {
root: 7,
text: "world"
},
AppendChildren { many: 2 },
@@ -136,29 +136,29 @@ fn create_list() {
mutations.edits,
[
CreateElement {
root: 0,
tag: "div"
},
CreateTextNode {
root: 1,
text: "hello"
tag: "div"
},
AppendChildren { many: 1 },
CreateElement {
CreateTextNode {
root: 2,
tag: "div"
},
CreateTextNode {
root: 3,
text: "hello"
},
AppendChildren { many: 1 },
CreateElement {
root: 4,
root: 3,
tag: "div"
},
CreateTextNode {
root: 4,
text: "hello"
},
AppendChildren { many: 1 },
CreateElement {
root: 5,
tag: "div"
},
CreateTextNode {
root: 6,
text: "hello"
},
AppendChildren { many: 1 },
@@ -185,10 +185,6 @@ fn create_simple() {
assert_eq!(
mutations.edits,
[
CreateElement {
root: 0,
tag: "div"
},
CreateElement {
root: 1,
tag: "div"
@@ -201,6 +197,10 @@ fn create_simple() {
root: 3,
tag: "div"
},
CreateElement {
root: 4,
tag: "div"
},
AppendChildren { many: 4 },
]
);
@@ -234,39 +234,39 @@ fn create_components() {
assert_eq!(
mutations.edits,
[
CreateElement { root: 0, tag: "h1" },
CreateElement { root: 1, tag: "h1" },
CreateElement {
root: 1,
root: 2,
tag: "div"
},
CreateTextNode {
root: 2,
root: 3,
text: "abc1"
},
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "p" },
CreateElement { root: 4, tag: "h1" },
CreateElement { root: 4, tag: "p" },
CreateElement { root: 5, tag: "h1" },
CreateElement {
root: 5,
root: 6,
tag: "div"
},
CreateTextNode {
root: 6,
root: 7,
text: "abc2"
},
AppendChildren { many: 1 },
CreateElement { root: 7, tag: "p" },
CreateElement { root: 8, tag: "h1" },
CreateElement { root: 8, tag: "p" },
CreateElement { root: 9, tag: "h1" },
CreateElement {
root: 9,
root: 10,
tag: "div"
},
CreateTextNode {
root: 10,
root: 11,
text: "abc3"
},
AppendChildren { many: 1 },
CreateElement { root: 11, tag: "p" },
CreateElement { root: 12, tag: "p" },
AppendChildren { many: 9 },
]
);
@@ -286,15 +286,15 @@ fn anchors() {
mutations.edits,
[
CreateElement {
root: 0,
root: 1,
tag: "div"
},
CreateTextNode {
root: 1,
root: 2,
text: "hello"
},
AppendChildren { many: 1 },
CreatePlaceholder { root: 2 },
CreatePlaceholder { root: 3 },
AppendChildren { many: 2 },
]
);
+3 -4
View File
@@ -18,8 +18,6 @@ type Shared<T> = Arc<Mutex<T>>;
#[test]
fn manual_diffing() {
test_logging::set_up_logging(IS_LOGGING_ENABLED);
struct AppProps {
value: Shared<&'static str>,
}
@@ -73,9 +71,9 @@ fn events_generate() {
let mut channel = dom.get_scheduler_channel();
assert!(dom.has_any_work());
let edits = dom.work_with_deadline(|| false);
let edits = dom.rebuild();
assert_eq!(
edits[0].edits,
edits.edits,
[
CreateElement {
tag: "div",
@@ -100,6 +98,7 @@ fn events_generate() {
root: 3,
},
AppendChildren { many: 2 },
AppendChildren { many: 1 },
]
)
}
-41
View File
@@ -1,41 +0,0 @@
use futures_util::StreamExt;
/*
furtures_channel provides us some batching simply due to how Rust's async works.
Any hook that uses schedule_update is simply deferring to unbounded_send. Multiple
unbounded_sends can be linked together in succession provided there isn't an "await"
between them. Our internal batching mechanism simply waits for the "schedule_update"
to fire and then pulls any messages off the unbounded_send queue.
Additionally, due to how our "time slicing" works we'll always come back and check
in for new work if the deadline hasn't expired. On average, our deadline should be
about 10ms, which is way more than enough for diffing/creating to happen.
*/
#[async_std::test]
async fn batch() {
let (sender, mut recver) = futures_channel::mpsc::unbounded::<i32>();
let _handle = async_std::task::spawn(async move {
let _msg = recver.next().await;
while let Ok(msg) = recver.try_next() {
println!("{:#?}", msg);
}
let _msg = recver.next().await;
while let Ok(msg) = recver.try_next() {
println!("{:#?}", msg);
}
});
sender.unbounded_send(1).unwrap();
sender.unbounded_send(2).unwrap();
sender.unbounded_send(3).unwrap();
sender.unbounded_send(4).unwrap();
async_std::task::sleep(std::time::Duration::from_millis(100)).await;
sender.unbounded_send(5).unwrap();
sender.unbounded_send(6).unwrap();
sender.unbounded_send(7).unwrap();
sender.unbounded_send(8).unwrap();
}
-14
View File
@@ -88,17 +88,3 @@ fn child_components() {
let edits = vdom.rebuild();
dbg!(edits);
}
#[test]
fn suspended_works() {
todo!()
// static App: FC<()> = |cx, props| {
// let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
// // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
// cx.render(rsx!("hello" { title }))
// };
// let mut vdom = VirtualDom::new(App);
// let edits = vdom.rebuild();
// dbg!(edits);
}
-2
View File
@@ -17,8 +17,6 @@ pub(crate) struct ProvidedStateInner<T> {
impl<T> ProvidedStateInner<T> {
pub(crate) fn notify_consumers(&mut self) {
for consumer in self.consumers.iter() {
println!("notifiying {:?}", consumer);
// log::debug("notifiying {:?}", consumer);
(self.notify_any)(*consumer);
}
}
+1 -1
View File
@@ -193,7 +193,7 @@ pub mod debug {}
pub mod prelude {
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
pub use dioxus_core::prelude::*;
pub use dioxus_core_macro::{format_args_f, rsx, Props};
pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable};
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
pub use dioxus_hooks::*;
pub use dioxus_html as dioxus_elements;