>) -> Element;
/// ```
- pub fn component(
+ pub fn component<'child, P>(
&'src self,
- component: fn(Scope<'src, P>) -> Element<'src>,
+ component: fn(Scope<'child, P>) -> Element<'child>,
props: P,
fn_name: &'static str,
) -> DynamicNode<'src>
where
+ // The properties must be valid until the next bump frame
P: Properties + 'src,
+ // The current bump allocator frame must outlive the child's borrowed props
+ 'src: 'child,
{
let vcomp = VProps::new(component, P::memoize, props);
// cast off the lifetime of the render return
- let as_dyn: Box + '_> = Box::new(vcomp);
+ let as_dyn: Box + '_> = Box::new(vcomp);
let extended: Box + 'src> = unsafe { std::mem::transmute(as_dyn) };
DynamicNode::Component(VComponent {
@@ -510,7 +464,10 @@ impl<'src> ScopeState {
let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
let callback = RefCell::new(Some(caller));
- EventHandler { callback }
+ EventHandler {
+ callback,
+ origin: self.context().id,
+ }
}
/// Create a new [`AttributeValue`] with the listener variant from a callback
@@ -564,7 +521,8 @@ impl<'src> ScopeState {
/// Mark this component as suspended and then return None
pub fn suspend(&self) -> Option {
- self.suspended.set(true);
+ let cx = self.context();
+ cx.suspend();
None
}
diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs
index dce5415f6..c2e56c776 100644
--- a/packages/core/src/virtual_dom.rs
+++ b/packages/core/src/virtual_dom.rs
@@ -9,6 +9,7 @@ use crate::{
mutations::Mutation,
nodes::RenderReturn,
nodes::{Template, TemplateId},
+ runtime::{Runtime, RuntimeGuard},
scopes::{ScopeId, ScopeState},
AttributeValue, Element, Event, Scope,
};
@@ -174,24 +175,24 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
/// }
/// ```
pub struct VirtualDom {
+ pub(crate) scopes: Slab>,
+
+ pub(crate) dirty_scopes: BTreeSet,
+
// Maps a template path to a map of byteindexes to templates
pub(crate) templates: FxHashMap>>,
- pub(crate) scopes: Slab>,
- pub(crate) dirty_scopes: BTreeSet,
- pub(crate) scheduler: Rc,
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
pub(crate) elements: Slab,
- // While diffing we need some sort of way of breaking off a stream of suspended mutations.
- pub(crate) scope_stack: Vec,
+ pub(crate) mutations: Mutations<'static>,
+
+ pub(crate) runtime: Rc,
// Currently suspended scopes
pub(crate) suspended_scopes: FxHashSet,
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver,
-
- pub(crate) mutations: Mutations<'static>,
}
impl VirtualDom {
@@ -251,16 +252,16 @@ impl VirtualDom {
/// ```
pub fn new_with_props(root: fn(Scope) -> Element, root_props: P) -> Self {
let (tx, rx) = futures_channel::mpsc::unbounded();
+ let scheduler = Scheduler::new(tx);
let mut dom = Self {
rx,
- scheduler: Scheduler::new(tx),
- templates: Default::default(),
+ runtime: Runtime::new(scheduler),
scopes: Default::default(),
+ dirty_scopes: Default::default(),
+ templates: Default::default(),
elements: Default::default(),
- scope_stack: Vec::new(),
- dirty_scopes: BTreeSet::new(),
- suspended_scopes: FxHashSet::default(),
mutations: Mutations::default(),
+ suspended_scopes: Default::default(),
};
let root = dom.new_scope(
@@ -269,7 +270,7 @@ impl VirtualDom {
);
// Unlike react, we provide a default error boundary that just renders the error as a string
- root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
+ root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
// the root element is always given element ID 0 since it's the container for the entire tree
dom.elements.insert(ElementRef::none());
@@ -281,14 +282,14 @@ impl VirtualDom {
///
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
- self.scopes.get(id.0).map(|f| f.as_ref())
+ self.scopes.get(id.0).map(|s| &**s)
}
/// Get the single scope at the top of the VirtualDom tree that will always be around
///
/// This scope has a ScopeId of 0 and is the root of the tree
pub fn base_scope(&self) -> &ScopeState {
- self.get_scope(ScopeId(0)).unwrap()
+ self.get_scope(ScopeId::ROOT).unwrap()
}
/// Build the virtualdom with a global context inserted into the base scope
@@ -301,10 +302,10 @@ impl VirtualDom {
/// Manually mark a scope as requiring a re-render
///
- /// Whenever the VirtualDom "works", it will re-render this scope
+ /// Whenever the Runtime "works", it will re-render this scope
pub fn mark_dirty(&mut self, id: ScopeId) {
if let Some(scope) = self.get_scope(id) {
- let height = scope.height;
+ let height = scope.height();
self.dirty_scopes.insert(DirtyScope { height, id });
}
}
@@ -325,6 +326,8 @@ impl VirtualDom {
element: ElementId,
bubbles: bool,
) {
+ let _runtime = RuntimeGuard::new(self.runtime.clone());
+
/*
------------------------
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
@@ -387,9 +390,14 @@ impl VirtualDom {
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
+ let origin = el_ref.scope;
+ self.runtime.scope_stack.borrow_mut().push(origin);
+ self.runtime.rendering.set(false);
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
+ self.runtime.scope_stack.borrow_mut().pop();
+ self.runtime.rendering.set(true);
if !uievent.propagates.get() {
return;
@@ -418,9 +426,14 @@ impl VirtualDom {
// Only call the listener if this is the exact target element.
if attr.name.trim_start_matches("on") == name && target_path == this_path {
if let AttributeValue::Listener(listener) = &attr.value {
+ let origin = el_ref.scope;
+ self.runtime.scope_stack.borrow_mut().push(origin);
+ self.runtime.rendering.set(false);
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
+ self.runtime.scope_stack.borrow_mut().pop();
+ self.runtime.rendering.set(true);
break;
}
@@ -501,10 +514,11 @@ impl VirtualDom {
if sync.template.get().name.rsplit_once(':').unwrap().0
== template.name.rsplit_once(':').unwrap().0
{
- let height = scope.height;
+ let context = scope.context();
+ let height = context.height;
self.dirty_scopes.insert(DirtyScope {
height,
- id: scope.id,
+ id: context.id,
});
}
}
@@ -532,10 +546,11 @@ impl VirtualDom {
/// apply_edits(edits);
/// ```
pub fn rebuild(&mut self) -> Mutations {
- match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
+ let _runtime = RuntimeGuard::new(self.runtime.clone());
+ match unsafe { self.run_scope(ScopeId::ROOT).extend_lifetime_ref() } {
// Rebuilding implies we append the created elements to the root
RenderReturn::Ready(node) => {
- let m = self.create_scope(ScopeId(0), node);
+ let m = self.create_scope(ScopeId::ROOT, node);
self.mutations.edits.push(Mutation::AppendChildren {
id: ElementId(0),
m,
@@ -610,9 +625,12 @@ impl VirtualDom {
continue;
}
- // Run the scope and get the mutations
- self.run_scope(dirty.id);
- self.diff_scope(dirty.id);
+ {
+ let _runtime = RuntimeGuard::new(self.runtime.clone());
+ // Run the scope and get the mutations
+ self.run_scope(dirty.id);
+ self.diff_scope(dirty.id);
+ }
}
// If there's more work, then just continue, plenty of work to do
@@ -645,6 +663,6 @@ impl VirtualDom {
impl Drop for VirtualDom {
fn drop(&mut self) {
// Simply drop this scope which drops all of its children
- self.drop_scope(ScopeId(0), true);
+ self.drop_scope(ScopeId::ROOT, true);
}
}
diff --git a/packages/core/tests/attr_cleanup.rs b/packages/core/tests/attr_cleanup.rs
index 683f02260..539a0b399 100644
--- a/packages/core/tests/attr_cleanup.rs
+++ b/packages/core/tests/attr_cleanup.rs
@@ -34,7 +34,7 @@ fn attrs_cycle() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -56,7 +56,7 @@ fn attrs_cycle() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -65,7 +65,7 @@ fn attrs_cycle() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -88,7 +88,7 @@ fn attrs_cycle() {
);
// we take the node taken by attributes since we reused it
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
diff --git a/packages/core/tests/bubble_error.rs b/packages/core/tests/bubble_error.rs
index 7d6966c46..af2a34219 100644
--- a/packages/core/tests/bubble_error.rs
+++ b/packages/core/tests/bubble_error.rs
@@ -24,7 +24,7 @@ fn bubbles_error() {
let _edits = dom.rebuild().santize();
}
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs
index 696810cb0..05c66a41e 100644
--- a/packages/core/tests/context_api.rs
+++ b/packages/core/tests/context_api.rs
@@ -27,11 +27,11 @@ fn state_shares() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
assert_eq!(dom.base_scope().consume_context::().unwrap(), 1);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
assert_eq!(dom.base_scope().consume_context::().unwrap(), 2);
@@ -41,7 +41,7 @@ fn state_shares() {
[SetText { value: "Value is 2", id: ElementId(1,) },]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
dom.mark_dirty(ScopeId(2));
let edits = dom.render_immediate();
assert_eq!(
diff --git a/packages/core/tests/cycle.rs b/packages/core/tests/cycle.rs
index 10498f5cc..20d1c9343 100644
--- a/packages/core/tests/cycle.rs
+++ b/packages/core/tests/cycle.rs
@@ -23,7 +23,7 @@ fn cycling_elements() {
);
}
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -33,7 +33,7 @@ fn cycling_elements() {
);
// notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -42,7 +42,7 @@ fn cycling_elements() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
diff --git a/packages/core/tests/diff_component.rs b/packages/core/tests/diff_component.rs
index 69ab6de6d..4766ad378 100644
--- a/packages/core/tests/diff_component.rs
+++ b/packages/core/tests/diff_component.rs
@@ -75,7 +75,7 @@ fn component_swap() {
);
}
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -84,7 +84,7 @@ fn component_swap() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -93,7 +93,7 @@ fn component_swap() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
diff --git a/packages/core/tests/diff_element.rs b/packages/core/tests/diff_element.rs
index 3ab9b284a..a0b9bfa84 100644
--- a/packages/core/tests/diff_element.rs
+++ b/packages/core/tests/diff_element.rs
@@ -12,19 +12,19 @@ fn text_diff() {
let mut vdom = VirtualDom::new(app);
_ = vdom.rebuild();
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().edits,
[SetText { value: "hello 1", id: ElementId(2) }]
);
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().edits,
[SetText { value: "hello 2", id: ElementId(2) }]
);
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().edits,
[SetText { value: "hello 3", id: ElementId(2) }]
@@ -46,7 +46,7 @@ fn element_swap() {
let mut vdom = VirtualDom::new(app);
_ = vdom.rebuild();
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
@@ -55,7 +55,7 @@ fn element_swap() {
]
);
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
@@ -64,7 +64,7 @@ fn element_swap() {
]
);
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
@@ -73,7 +73,7 @@ fn element_swap() {
]
);
- vdom.mark_dirty(ScopeId(0));
+ vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs
index be26e00c0..ed14c2d95 100644
--- a/packages/core/tests/diff_keyed_list.rs
+++ b/packages/core/tests/diff_keyed_list.rs
@@ -39,7 +39,7 @@ fn keyed_diffing_out_of_order() {
);
}
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().edits,
[
@@ -64,7 +64,7 @@ fn keyed_diffing_out_of_order_adds() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().edits,
[
@@ -90,7 +90,7 @@ fn keyed_diffing_out_of_order_adds_3() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().edits,
[
@@ -116,7 +116,7 @@ fn keyed_diffing_out_of_order_adds_4() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().edits,
[
@@ -142,7 +142,7 @@ fn keyed_diffing_out_of_order_adds_5() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().edits,
[
@@ -167,7 +167,7 @@ fn keyed_diffing_additions() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -192,7 +192,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -222,7 +222,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
_ = dom.rebuild();
// LIS: 4, 5, 6
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -256,7 +256,7 @@ fn controlled_keyed_diffing_out_of_order() {
_ = dom.rebuild();
// LIS: 5, 6
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -289,7 +289,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -318,7 +318,7 @@ fn remove_list() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -343,7 +343,7 @@ fn no_common_keys() {
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs
index 4934f6080..b5be161ef 100644
--- a/packages/core/tests/diff_unkeyed_list.rs
+++ b/packages/core/tests/diff_unkeyed_list.rs
@@ -27,7 +27,7 @@ fn list_creates_one_by_one() {
);
// Rendering the first item should replace the placeholder with an element
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -38,7 +38,7 @@ fn list_creates_one_by_one() {
);
// Rendering the next item should insert after the previous
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -49,7 +49,7 @@ fn list_creates_one_by_one() {
);
// ... and again!
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -60,7 +60,7 @@ fn list_creates_one_by_one() {
);
// once more
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -107,14 +107,14 @@ fn removes_one_by_one() {
// Remove div(3)
// Rendering the first item should replace the placeholder with an element
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[Remove { id: ElementId(6) }]
);
// Remove div(2)
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[Remove { id: ElementId(4) }]
@@ -122,7 +122,7 @@ fn removes_one_by_one() {
// Remove div(1) and replace with a placeholder
// todo: this should just be a remove with no placeholder
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -133,7 +133,7 @@ fn removes_one_by_one() {
// load the 3 and replace the placeholder
// todo: this should actually be append to, but replace placeholder is fine for now
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -170,7 +170,7 @@ fn list_shrink_multiroot() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -182,7 +182,7 @@ fn list_shrink_multiroot() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -194,7 +194,7 @@ fn list_shrink_multiroot() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -249,19 +249,19 @@ fn removes_one_by_one_multiroot() {
]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
assert_eq!(
dom.render_immediate().santize().edits,
[
@@ -328,7 +328,7 @@ fn remove_many() {
}
{
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
@@ -341,7 +341,7 @@ fn remove_many() {
}
{
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
@@ -360,7 +360,7 @@ fn remove_many() {
}
{
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
@@ -376,7 +376,7 @@ fn remove_many() {
}
{
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs
index 40bb5dcbf..1523c4e22 100644
--- a/packages/core/tests/fuzzing.rs
+++ b/packages/core/tests/fuzzing.rs
@@ -179,7 +179,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
node_paths: &[&[0]],
attr_paths: &[],
}),
- root_ids: Default::default(),
+ root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
dynamic_nodes: cx.bump().alloc([cx.component(
create_random_element,
DepthProps { depth, root: false },
@@ -276,7 +276,7 @@ fn create_random_element(cx: Scope) -> Element {
key: None,
parent: None,
template: Cell::new(template),
- root_ids: Default::default(),
+ root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
dynamic_nodes: {
let dynamic_nodes: Vec<_> = dynamic_node_types
.iter()
diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs
index 04f6bc3cf..08b342631 100644
--- a/packages/core/tests/lifecycle.rs
+++ b/packages/core/tests/lifecycle.rs
@@ -58,7 +58,7 @@ fn events_generate() {
dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true);
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
let edits = dom.render_immediate();
assert_eq!(
@@ -107,7 +107,7 @@ fn events_generate() {
// );
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreateElement { root: Some(2), tag: "div", children: 0 },
// ReplaceWith { root: Some(1), nodes: vec![2] }
@@ -115,7 +115,7 @@ fn events_generate() {
// );
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreateTextNode { root: Some(1), text: "Text2" },
// ReplaceWith { root: Some(2), nodes: vec![1] }
@@ -124,7 +124,7 @@ fn events_generate() {
// // child {}
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreateElement { root: Some(2), tag: "h1", children: 0 },
// ReplaceWith { root: Some(1), nodes: vec![2] }
@@ -133,7 +133,7 @@ fn events_generate() {
// // placeholder
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreatePlaceholder { root: Some(1) },
// ReplaceWith { root: Some(2), nodes: vec![1] }
@@ -141,7 +141,7 @@ fn events_generate() {
// );
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreateTextNode { root: Some(2), text: "text 3" },
// ReplaceWith { root: Some(1), nodes: vec![2] }
@@ -149,7 +149,7 @@ fn events_generate() {
// );
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreateTextNode { text: "text 0", root: Some(1) },
// CreateTextNode { text: "text 1", root: Some(3) },
@@ -158,7 +158,7 @@ fn events_generate() {
// );
// assert_eq!(
-// dom.hard_diff(ScopeId(0)).edits,
+// dom.hard_diff(ScopeId::ROOT).edits,
// [
// CreateElement { tag: "h1", root: Some(2), children: 0 },
// ReplaceWith { root: Some(1), nodes: vec![2] },
diff --git a/packages/core/tests/miri_simple.rs b/packages/core/tests/miri_simple.rs
index 6da2c4bf4..2282cb4e2 100644
--- a/packages/core/tests/miri_simple.rs
+++ b/packages/core/tests/miri_simple.rs
@@ -11,7 +11,7 @@ fn app_drops() {
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
@@ -31,7 +31,7 @@ fn hooks_drop() {
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
@@ -58,7 +58,7 @@ fn contexts_drop() {
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
@@ -77,7 +77,7 @@ fn tasks_drop() {
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
@@ -91,7 +91,7 @@ fn root_props_drop() {
);
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
@@ -121,7 +121,7 @@ fn diffing_drops_old() {
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs
index 15b7ebf41..ff8d69788 100644
--- a/packages/core/tests/miri_stress.rs
+++ b/packages/core/tests/miri_stress.rs
@@ -62,7 +62,7 @@ fn test_memory_leak() {
_ = dom.rebuild();
for _ in 0..5 {
- dom.mark_dirty(ScopeId(0));
+ dom.mark_dirty(ScopeId::ROOT);
_ = dom.render_immediate();
}
}
@@ -97,13 +97,13 @@ fn memo_works_properly() {
_ = dom.rebuild();
// todo!()
- // dom.hard_diff(ScopeId(0));
- // dom.hard_diff(ScopeId(0));
- // dom.hard_diff(ScopeId(0));
- // dom.hard_diff(ScopeId(0));
- // dom.hard_diff(ScopeId(0));
- // dom.hard_diff(ScopeId(0));
- // dom.hard_diff(ScopeId(0));
+ // dom.hard_diff(ScopeId::ROOT);
+ // dom.hard_diff(ScopeId::ROOT);
+ // dom.hard_diff(ScopeId::ROOT);
+ // dom.hard_diff(ScopeId::ROOT);
+ // dom.hard_diff(ScopeId::ROOT);
+ // dom.hard_diff(ScopeId::ROOT);
+ // dom.hard_diff(ScopeId::ROOT);
}
#[test]
diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml
index a85c2e045..4b2b6df9f 100644
--- a/packages/desktop/Cargo.toml
+++ b/packages/desktop/Cargo.toml
@@ -55,7 +55,8 @@ default = ["tokio_runtime", "hot-reload"]
tokio_runtime = ["tokio"]
fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"]
-tray = ["wry/tray"]
+devtools = ["wry/devtools"]
+dox = ["wry/dox"]
hot-reload = ["dioxus-hot-reload"]
[dev-dependencies]
diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs
index 89ce50a48..53652f82f 100644
--- a/packages/desktop/src/protocol.rs
+++ b/packages/desktop/src/protocol.rs
@@ -158,8 +158,13 @@ fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
}
let res = match infer::get_from_path(trimmed)?.map(|f| f.mime_type()) {
- Some(t) if t == "text/plain" => get_mime_by_ext(trimmed),
- Some(f) => f,
+ Some(f) => {
+ if f == "text/plain" {
+ get_mime_by_ext(trimmed)
+ } else {
+ f
+ }
+ }
None => get_mime_by_ext(trimmed),
};
diff --git a/packages/dioxus-tui/examples/hover.rs b/packages/dioxus-tui/examples/hover.rs
index 77770b46d..3af2d1616 100644
--- a/packages/dioxus-tui/examples/hover.rs
+++ b/packages/dioxus-tui/examples/hover.rs
@@ -1,6 +1,7 @@
use dioxus::{events::MouseData, prelude::*};
use dioxus_core::Event;
use std::convert::TryInto;
+use std::fmt::Write;
use std::rc::Rc;
fn main() {
@@ -9,7 +10,12 @@ fn main() {
fn app(cx: Scope) -> Element {
fn to_str(c: &[i32; 3]) -> String {
- "#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::()
+ let mut result = String::new();
+ result += "#";
+ for c in c.iter() {
+ write!(result, "{c:02X?}").unwrap();
+ }
+ result
}
fn get_brightness(m: &Rc) -> i32 {
diff --git a/packages/fullstack/src/adapters/mod.rs b/packages/fullstack/src/adapters/mod.rs
index 9133f8a08..98d190919 100644
--- a/packages/fullstack/src/adapters/mod.rs
+++ b/packages/fullstack/src/adapters/mod.rs
@@ -112,7 +112,7 @@ impl Service for ServerFnHandler {
let mut res = http::Response::builder();
// Set the headers from the server context
- let parts = server_context.parts.read().unwrap();
+ let parts = server_context.response_parts().unwrap();
*res.headers_mut().expect("empty headers should be valid") = parts.headers.clone();
let serialized = result?;
diff --git a/packages/fullstack/src/launch.rs b/packages/fullstack/src/launch.rs
index 8d125680b..c982a1c90 100644
--- a/packages/fullstack/src/launch.rs
+++ b/packages/fullstack/src/launch.rs
@@ -119,21 +119,22 @@ impl>,
+ invalid_keys: &mut Vec>,
+ path: &mut Vec,
+ ) {
+ let branch_cutoff = 5;
+ let children = if path.len() < branch_cutoff {
+ rand::random::() % 4
+ } else {
+ rand::random::() % 2
+ };
+
+ for i in 0..children {
+ let owner = store.owner();
+ let key = owner.insert(format!("hello world {path:?}"));
+ valid_keys.push(key);
+ path.push(i);
+ // read all keys
+ println!("{:?}", path);
+ for key in valid_keys.iter() {
+ let value = key.read();
+ println!("{:?}", value);
+ assert!(value.starts_with("hello world"));
+ }
+ #[cfg(any(debug_assertions, feature = "check_generation"))]
+ for key in invalid_keys.iter() {
+ assert!(!key.validate());
+ }
+ maybe_owner_scope(store, valid_keys, invalid_keys, path);
+ invalid_keys.push(valid_keys.pop().unwrap());
+ path.pop();
+ }
+ }
+
+ for _ in 0..10 {
+ let store = Store::default();
+ maybe_owner_scope(&store, &mut Vec::new(), &mut Vec::new(), &mut Vec::new());
+ }
+}
+
+/// The core Copy state type. The generational box will be dropped when the [Owner] is dropped.
+pub struct GenerationalBox {
+ raw: MemoryLocation,
+ #[cfg(any(debug_assertions, feature = "check_generation"))]
+ generation: u32,
+ _marker: PhantomData,
+}
+
+impl Debug for GenerationalBox {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ #[cfg(any(debug_assertions, feature = "check_generation"))]
+ f.write_fmt(format_args!(
+ "{:?}@{:?}",
+ self.raw.data.as_ptr(),
+ self.generation
+ ))?;
+ #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+ f.write_fmt(format_args!("{:?}", self.raw.data.as_ptr()))?;
+ Ok(())
+ }
+}
+
+impl GenerationalBox {
+ #[inline(always)]
+ fn validate(&self) -> bool {
+ #[cfg(any(debug_assertions, feature = "check_generation"))]
+ {
+ self.raw.generation.get() == self.generation
+ }
+ #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+ {
+ true
+ }
+ }
+
+ /// Try to read the value. Returns None if the value is no longer valid.
+ pub fn try_read(&self) -> Option[> {
+ self.validate()
+ .then(|| {
+ Ref::filter_map(self.raw.data.borrow(), |any| {
+ any.as_ref()?.downcast_ref::()
+ })
+ .ok()
+ })
+ .flatten()
+ }
+
+ /// Read the value. Panics if the value is no longer valid.
+ pub fn read(&self) -> Ref<'_, T> {
+ self.try_read().unwrap()
+ }
+
+ /// Try to write the value. Returns None if the value is no longer valid.
+ pub fn try_write(&self) -> Option> {
+ self.validate()
+ .then(|| {
+ RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
+ any.as_mut()?.downcast_mut::()
+ })
+ .ok()
+ })
+ .flatten()
+ }
+
+ /// Write the value. Panics if the value is no longer valid.
+ pub fn write(&self) -> RefMut<'_, T> {
+ self.try_write().unwrap()
+ }
+
+ /// Set the value. Panics if the value is no longer valid.
+ pub fn set(&self, value: T) {
+ self.validate().then(|| {
+ *self.raw.data.borrow_mut() = Some(Box::new(value));
+ });
+ }
+
+ /// Returns true if the pointer is equal to the other pointer.
+ pub fn ptr_eq(&self, other: &Self) -> bool {
+ #[cfg(any(debug_assertions, feature = "check_generation"))]
+ {
+ self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
+ }
+ #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+ {
+ self.raw.data.as_ptr() == other.raw.data.as_ptr()
+ }
+ }
+}
+
+impl Copy for GenerationalBox {}
+
+impl Clone for GenerationalBox {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+#[derive(Clone, Copy)]
+struct MemoryLocation {
+ data: &'static RefCell]