diff --git a/examples/button.rs b/examples/button.rs
new file mode 100644
index 000000000..9d32819e8
--- /dev/null
+++ b/examples/button.rs
@@ -0,0 +1,11 @@
+use dioxus::prelude::*;
+
+fn main() {
+ dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+ cx.render(rsx! {
+ button { "hello, desktop!" }
+ })
+}
diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs
index c3ea4dc2c..7014a2771 100644
--- a/packages/core/src/any_props.rs
+++ b/packages/core/src/any_props.rs
@@ -1,4 +1,4 @@
-use std::marker::PhantomData;
+use std::{marker::PhantomData, panic::AssertUnwindSafe};
use crate::{
innerlude::Scoped,
@@ -62,12 +62,19 @@ where
}
fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
- let scope: &mut Scoped
= cx.bump().alloc(Scoped {
- props: &self.props,
- scope: cx,
- });
+ let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
+ // Call the render function directly
+ let scope: &mut Scoped
= cx.bump().alloc(Scoped {
+ props: &self.props,
+ scope: cx,
+ });
- // Call the render function directly
- (self.render_fn)(scope).into_return(cx)
+ (self.render_fn)(scope).into_return(cx)
+ }));
+
+ match res {
+ Ok(e) => e,
+ Err(_) => RenderReturn::default(),
+ }
}
}
diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs
index a7d030162..aef8e8b2d 100644
--- a/packages/core/src/arena.rs
+++ b/packages/core/src/arena.rs
@@ -81,12 +81,12 @@ impl VirtualDom {
self.ensure_drop_safety(id);
if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
- if let RenderReturn::Sync(Some(node)) = unsafe { root.extend_lifetime_ref() } {
+ if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
self.drop_scope_inner(node)
}
}
if let Some(root) = unsafe { self.scopes[id.0].as_ref().previous_frame().try_load_node() } {
- if let RenderReturn::Sync(Some(node)) = unsafe { root.extend_lifetime_ref() } {
+ if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
self.drop_scope_inner(node)
}
}
@@ -126,11 +126,9 @@ impl VirtualDom {
}
});
- for root in node.root_ids {
- if let Some(id) = root.get() {
- if id.0 != 0 {
- self.try_reclaim(id);
- }
+ for id in &node.root_ids {
+ if id.0 != 0 {
+ self.try_reclaim(id);
}
}
}
diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs
index 423c923db..4e2d8f0a2 100644
--- a/packages/core/src/create.rs
+++ b/packages/core/src/create.rs
@@ -5,13 +5,50 @@ use crate::mutations::Mutation::*;
use crate::nodes::VNode;
use crate::nodes::{DynamicNode, TemplateNode};
use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
+use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext, Template};
use std::cell::Cell;
-use std::iter::{Enumerate, Peekable};
+use std::iter::Peekable;
use std::rc::Rc;
-use std::slice;
use TemplateNode::*;
+fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
+ let mut with_indecies = paths.iter().copied().enumerate().collect::>();
+ with_indecies.sort_unstable_by(|(_, a), (_, b)| {
+ let mut a = a.iter();
+ let mut b = b.iter();
+ loop {
+ match (a.next(), b.next()) {
+ (Some(a), Some(b)) => {
+ if a != b {
+ return a.cmp(b);
+ }
+ }
+ // The shorter path goes first
+ (Some(_), None) => return std::cmp::Ordering::Less,
+ (None, Some(_)) => return std::cmp::Ordering::Greater,
+ (None, None) => return std::cmp::Ordering::Equal,
+ }
+ }
+ });
+ with_indecies
+}
+
+#[test]
+fn sorting() {
+ let r: [(usize, &[u8]); 5] = [
+ (0, &[0, 1]),
+ (1, &[0, 2]),
+ (2, &[1, 0]),
+ (4, &[1, 1]),
+ (3, &[1, 2]),
+ ];
+ assert_eq!(
+ sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 2,], &[1, 1,],]),
+ r
+ );
+ assert!(matches!(&[0], &[_, ..]))
+}
+
impl<'b> VirtualDom {
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
///
@@ -25,21 +62,79 @@ impl<'b> VirtualDom {
/// Create this template and write its mutations
pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize {
+ // check for a overriden template
+ #[cfg(debug_assertions)]
+ {
+ let (path, byte_index) = node.template.get().name.rsplit_once(':').unwrap();
+ if let Some(template) = self
+ .templates
+ .get(path)
+ .and_then(|map| map.get(&byte_index.parse().unwrap()))
+ {
+ node.template.set(*template);
+ }
+ }
+
+ // Intialize the root nodes slice
+ node.root_ids
+ .intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice());
+
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
- if !self.templates.contains_key(&node.template.name) {
- self.register_template(node);
+ if !self.templates.contains_key(&node.template.get().name) {
+ self.register_template(node.template.get());
}
// we know that this will generate at least one mutation per node
- self.mutations.edits.reserve(node.template.roots.len());
+ self.mutations
+ .edits
+ .reserve(node.template.get().roots.len());
// Walk the roots, creating nodes and assigning IDs
+ // nodes in an iterator of ((dynamic_node_index, sorted_index), path)
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
- let mut attrs = node.template.attr_paths.iter().enumerate().peekable();
- let mut nodes = node.template.node_paths.iter().enumerate().peekable();
+ #[cfg(not(debug_assertions))]
+ let (mut attrs, mut nodes) = (
+ node.template
+ .get()
+ .attr_paths
+ .iter()
+ .copied()
+ .enumerate()
+ .peekable(),
+ node.template
+ .get()
+ .node_paths
+ .iter()
+ .copied()
+ .enumerate()
+ .map(|(i, path)| ((i, i), path))
+ .peekable(),
+ );
+ // If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
+
+ #[cfg(debug_assertions)]
+ let (attrs_sorted, nodes_sorted) = {
+ (
+ sort_bfs(node.template.get().attr_paths),
+ sort_bfs(node.template.get().node_paths),
+ )
+ };
+ #[cfg(debug_assertions)]
+ let (mut attrs, mut nodes) = {
+ (
+ attrs_sorted.into_iter().peekable(),
+ nodes_sorted
+ .iter()
+ .copied()
+ .enumerate()
+ .map(|(i, (id, path))| ((id, i), path))
+ .peekable(),
+ )
+ };
node.template
+ .get()
.roots
.iter()
.enumerate()
@@ -48,7 +143,14 @@ impl<'b> VirtualDom {
nodes.next().unwrap();
self.write_dynamic_root(node, *id)
}
- Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes),
+ Element { .. } => {
+ #[cfg(not(debug_assertions))]
+ let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[]);
+ #[cfg(debug_assertions)]
+ let id =
+ self.write_element_root(node, idx, &mut attrs, &mut nodes, &nodes_sorted);
+ id
+ }
Text { .. } => self.write_static_text_root(node, idx),
})
.sum()
@@ -65,8 +167,9 @@ impl<'b> VirtualDom {
fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize {
use DynamicNode::*;
match &template.dynamic_nodes[idx] {
- node @ Fragment(_) => self.create_dynamic_node(template, node, idx),
- node @ Component { .. } => self.create_dynamic_node(template, node, idx),
+ node @ Component { .. } | node @ Fragment(_) => {
+ self.create_dynamic_node(template, node, idx)
+ }
Placeholder(VPlaceholder { id }) => {
let id = self.set_slot(template, id, idx);
self.mutations.push(CreatePlaceholder { id });
@@ -98,17 +201,18 @@ impl<'b> VirtualDom {
&mut self,
template: &'b VNode<'b>,
root_idx: usize,
- dynamic_attrs: &mut Peekable>>,
- dynamic_nodes: &mut Peekable>>,
+ dynamic_attrs: &mut Peekable>,
+ dynamic_nodes_iter: &mut Peekable>,
+ dynamic_nodes: &[(usize, &'static [u8])],
) -> usize {
// Load the template root and get the ID for the node on the stack
let root_on_stack = self.load_template_root(template, root_idx);
// Write all the attributes below this root
- self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template);
+ self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template);
// Load in all of the placeholder or dynamic content under this root too
- self.load_placeholders(dynamic_nodes, root_idx, template);
+ self.load_placeholders(dynamic_nodes_iter, dynamic_nodes, root_idx as u8, template);
1
}
@@ -126,22 +230,32 @@ impl<'b> VirtualDom {
/// }
/// }
/// ```
+ #[allow(unused)]
fn load_placeholders(
&mut self,
- dynamic_nodes: &mut Peekable>>,
- root_idx: usize,
+ dynamic_nodes_iter: &mut Peekable>,
+ dynamic_nodes: &[(usize, &'static [u8])],
+ root_idx: u8,
template: &'b VNode<'b>,
) {
- let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) {
+ let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {
Some((a, b)) => (a, b),
None => return,
};
- for idx in (start..=end).rev() {
+ // If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index
+ #[cfg(not(debug_assertions))]
+ let reversed_iter = (start..=end).rev();
+ #[cfg(debug_assertions)]
+ let reversed_iter = (start..=end)
+ .rev()
+ .map(|sorted_index| dynamic_nodes[sorted_index].0);
+
+ for idx in reversed_iter {
let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
if m > 0 {
// The path is one shorter because the top node is the root
- let path = &template.template.node_paths[idx][1..];
+ let path = &template.template.get().node_paths[idx][1..];
self.mutations.push(ReplacePlaceholder { m, path });
}
}
@@ -149,12 +263,14 @@ impl<'b> VirtualDom {
fn write_attrs_on_root(
&mut self,
- attrs: &mut Peekable>>,
- root_idx: usize,
+ attrs: &mut Peekable>,
+ root_idx: u8,
root: ElementId,
node: &VNode,
) {
- while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
+ while let Some((mut attr_id, path)) =
+ attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
+ {
let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
loop {
@@ -236,10 +352,10 @@ impl<'b> VirtualDom {
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
// Get an ID for this root since it's a real root
let this_id = self.next_root(template, root_idx);
- template.root_ids[root_idx].set(Some(this_id));
+ template.root_ids.set(root_idx, this_id);
self.mutations.push(LoadTemplate {
- name: template.template.name,
+ name: template.template.get().name,
index: root_idx,
id: this_id,
});
@@ -267,7 +383,7 @@ impl<'b> VirtualDom {
// if attribute is on a root node, then we've already created the element
// Else, it's deep in the template and we should create a new id for it
- let id = self.next_element(template, template.template.attr_paths[attr_id]);
+ let id = self.next_element(template, template.template.get().attr_paths[attr_id]);
self.mutations.push(Mutation::AssignId {
path: &path[1..],
@@ -278,14 +394,59 @@ impl<'b> VirtualDom {
}
/// Insert a new template into the VirtualDom's template registry
- fn register_template(&mut self, template: &'b VNode<'b>) {
+ pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template<'static>) {
// First, make sure we mark the template as seen, regardless if we process it
- self.templates
- .insert(template.template.name, template.template);
+ let (path, _) = template.name.rsplit_once(':').unwrap();
+ if let Some((_, old_template)) = self
+ .templates
+ .entry(path)
+ .or_default()
+ .iter_mut()
+ .min_by_key(|(byte_index, _)| **byte_index)
+ {
+ // the byte index of the hot reloaded template could be different
+ template.name = old_template.name;
+ *old_template = template;
+ } else {
+ // This is a template without any current instances
+ self.templates
+ .entry(path)
+ .or_default()
+ .insert(usize::MAX, template);
+ }
// If it's all dynamic nodes, then we don't need to register it
- if !template.template.is_completely_dynamic() {
- self.mutations.templates.push(template.template);
+ if !template.is_completely_dynamic() {
+ self.mutations.templates.push(template);
+ }
+ }
+
+ /// Insert a new template into the VirtualDom's template registry
+ pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
+ // First, make sure we mark the template as seen, regardless if we process it
+ let (path, byte_index) = template.name.rsplit_once(':').unwrap();
+ let byte_index = byte_index.parse::().unwrap();
+
+ // if hot reloading is enabled, then we need to check for a template that has overriten this one
+ #[cfg(debug_assertions)]
+ if let Some(mut new_template) = self
+ .templates
+ .get_mut(path)
+ .and_then(|map| map.remove(&usize::MAX))
+ {
+ // the byte index of the hot reloaded template could be different
+ new_template.name = template.name;
+ template = new_template;
+ }
+
+ self.templates
+ .entry(path)
+ .or_default()
+ .insert(byte_index, template);
+
+ // If it's all dynamic nodes, then we don't need to register it
+ if !template.is_completely_dynamic() {
+ self.mutations.templates.push(template);
}
}
@@ -298,9 +459,9 @@ impl<'b> VirtualDom {
use DynamicNode::*;
match node {
Text(text) => self.create_dynamic_text(template, text, idx),
- Fragment(frag) => self.create_fragment(frag),
- Placeholder(frag) => self.create_placeholder(frag, template, idx),
+ Placeholder(place) => self.create_placeholder(place, template, idx),
Component(component) => self.create_component_node(template, component, idx),
+ Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
}
}
@@ -311,7 +472,7 @@ impl<'b> VirtualDom {
idx: usize,
) -> usize {
// Allocate a dynamic element reference for this text node
- let new_id = self.next_element(template, template.template.node_paths[idx]);
+ let new_id = self.next_element(template, template.template.get().node_paths[idx]);
// Make sure the text node is assigned to the correct element
text.id.set(Some(new_id));
@@ -322,7 +483,7 @@ impl<'b> VirtualDom {
// Add the mutation to the list
self.mutations.push(HydrateText {
id: new_id,
- path: &template.template.node_paths[idx][1..],
+ path: &template.template.get().node_paths[idx][1..],
value,
});
@@ -337,14 +498,14 @@ impl<'b> VirtualDom {
idx: usize,
) -> usize {
// Allocate a dynamic element reference for this text node
- let id = self.next_element(template, template.template.node_paths[idx]);
+ let id = self.next_element(template, template.template.get().node_paths[idx]);
// Make sure the text node is assigned to the correct element
placeholder.id.set(Some(id));
// Assign the ID to the existing node in the template
self.mutations.push(AssignId {
- path: &template.template.node_paths[idx][1..],
+ path: &template.template.get().node_paths[idx][1..],
id,
});
@@ -352,40 +513,38 @@ impl<'b> VirtualDom {
0
}
- pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
- nodes.iter().map(|child| self.create(child)).sum()
- }
-
pub(super) fn create_component_node(
&mut self,
template: &'b VNode<'b>,
component: &'b VComponent<'b>,
idx: usize,
) -> usize {
- let scope = match component.props.take() {
- Some(props) => {
- let unbounded_props: Box = unsafe { std::mem::transmute(props) };
- let scope = self.new_scope(unbounded_props, component.name);
- scope.id
- }
+ use RenderReturn::*;
- // Component is coming back, it probably still exists, right?
- None => component.scope.get().unwrap(),
- };
+ // Load up a ScopeId for this vcomponent
+ let scope = self.load_scope_from_vcomponent(component);
component.scope.set(Some(scope));
- let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
-
- use RenderReturn::*;
-
- match return_nodes {
- Sync(Some(t)) => self.mount_component(scope, template, t, idx),
- Sync(None) => todo!("Propogate error upwards"),
- Async(_) => self.mount_component_placeholder(template, idx, scope),
+ match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
+ Ready(t) => self.mount_component(scope, template, t, idx),
+ Aborted(t) => self.mount_aborted(template, t),
+ Pending(_) => self.mount_async(template, idx, scope),
}
}
+ /// Load a scope from a vcomponent. If the props don't exist, that means the component is currently "live"
+ fn load_scope_from_vcomponent(&mut self, component: &VComponent) -> ScopeId {
+ component
+ .props
+ .take()
+ .map(|props| {
+ let unbounded_props: Box = unsafe { std::mem::transmute(props) };
+ self.new_scope(unbounded_props, component.name).id
+ })
+ .unwrap_or_else(|| component.scope.get().unwrap())
+ }
+
fn mount_component(
&mut self,
scope: ScopeId,
@@ -412,7 +571,7 @@ impl<'b> VirtualDom {
};
// Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
- let new_id = self.next_element(new, parent.template.node_paths[idx]);
+ let new_id = self.next_element(new, parent.template.get().node_paths[idx]);
// Now connect everything to the boundary
self.scopes[scope.0].placeholder.set(Some(new_id));
@@ -434,22 +593,24 @@ impl<'b> VirtualDom {
// Now assign the placeholder in the DOM
self.mutations.push(AssignId {
id: new_id,
- path: &parent.template.node_paths[idx][1..],
+ path: &parent.template.get().node_paths[idx][1..],
});
0
}
+ fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
+ let id = self.next_element(parent, &[]);
+ self.mutations.push(Mutation::CreatePlaceholder { id });
+ placeholder.id.set(Some(id));
+ 1
+ }
+
/// Take the rendered nodes from a component and handle them if they were async
///
/// IE simply assign an ID to the placeholder
- fn mount_component_placeholder(
- &mut self,
- template: &VNode,
- idx: usize,
- scope: ScopeId,
- ) -> usize {
- let new_id = self.next_element(template, template.template.node_paths[idx]);
+ fn mount_async(&mut self, template: &VNode, idx: usize, scope: ScopeId) -> usize {
+ let new_id = self.next_element(template, template.template.get().node_paths[idx]);
// Set the placeholder of the scope
self.scopes[scope.0].placeholder.set(Some(new_id));
@@ -457,7 +618,7 @@ impl<'b> VirtualDom {
// Since the placeholder is already in the DOM, we don't create any new nodes
self.mutations.push(AssignId {
id: new_id,
- path: &template.template.node_paths[idx][1..],
+ path: &template.template.get().node_paths[idx][1..],
});
0
@@ -469,24 +630,26 @@ impl<'b> VirtualDom {
slot: &'b Cell>,
id: usize,
) -> ElementId {
- let id = self.next_element(template, template.template.node_paths[id]);
+ let id = self.next_element(template, template.template.get().node_paths[id]);
slot.set(Some(id));
id
}
}
fn collect_dyn_node_range(
- dynamic_nodes: &mut Peekable>>,
- root_idx: usize,
+ dynamic_nodes: &mut Peekable>,
+ root_idx: u8,
) -> Option<(usize, usize)> {
let start = match dynamic_nodes.peek() {
- Some((idx, p)) if p[0] == root_idx as u8 => *idx,
+ Some(((_, idx), [first, ..])) if *first == root_idx => *idx,
_ => return None,
};
let mut end = start;
- while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
+ while let Some(((_, idx), p)) =
+ dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))
+ {
if p.len() == 1 {
continue;
}
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index af262948e..f055fb650 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -30,31 +30,61 @@ impl<'b> VirtualDom {
.try_load_node()
.expect("Call rebuild before diffing");
- use RenderReturn::{Async, Sync};
+ use RenderReturn::{Aborted, Pending, Ready};
match (old, new) {
- (Sync(Some(l)), Sync(Some(r))) => self.diff_node(l, r),
+ // Normal pathway
+ (Ready(l), Ready(r)) => self.diff_node(l, r),
- // Err cases
- (Sync(Some(l)), Sync(None)) => self.diff_ok_to_err(l),
- (Sync(None), Sync(Some(r))) => self.diff_err_to_ok(r),
- (Sync(None), Sync(None)) => { /* nothing */ }
+ // Unwind the mutations if need be
+ (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
- // Async
- (Sync(Some(_l)), Async(_)) => todo!(),
- (Sync(None), Async(_)) => todo!(),
- (Async(_), Sync(Some(_r))) => todo!(),
- (Async(_), Sync(None)) => { /* nothing */ }
- (Async(_), Async(_)) => { /* nothing */ }
+ // Just move over the placeholder
+ (Aborted(l), Aborted(r)) => r.id.set(l.id.get()),
+
+ // Becomes async, do nothing while we ait
+ (Ready(_nodes), Pending(_fut)) => self.diff_ok_to_async(_nodes, scope),
+
+ // Placeholder becomes something
+ // We should also clear the error now
+ (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
+
+ (Aborted(_), Pending(_)) => todo!("async should not resolve here"),
+ (Pending(_), Ready(_)) => todo!("async should not resolve here"),
+ (Pending(_), Aborted(_)) => todo!("async should not resolve here"),
+ (Pending(_), Pending(_)) => {
+ // All suspense should resolve before we diff it again
+ panic!("Should not roll from suspense to suspense.");
+ }
};
}
self.scope_stack.pop();
}
- fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>) {}
- fn diff_err_to_ok(&mut self, _l: &'b VNode<'b>) {}
+ fn diff_ok_to_async(&mut self, _new: &'b VNode<'b>, _scope: ScopeId) {
+ //
+ }
+
+ fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _p: &'b VPlaceholder) {
+ todo!()
+ }
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
+ // If hot reloading is enabled, we need to make sure we're using the latest template
+ #[cfg(debug_assertions)]
+ {
+ let (path, byte_index) = right_template.template.get().name.rsplit_once(':').unwrap();
+ if let Some(map) = self.templates.get(path) {
+ let byte_index = byte_index.parse::().unwrap();
+ if let Some(&template) = map.get(&byte_index) {
+ right_template.template.set(template);
+ if template != left_template.template.get() {
+ return self.replace(left_template, [right_template]);
+ }
+ }
+ }
+ }
+
// If the templates are the same, we don't need to do anything, nor do we want to
if templates_are_the_same(left_template, right_template) {
return;
@@ -99,11 +129,7 @@ impl<'b> VirtualDom {
});
// Make sure the roots get transferred over while we're here
- left_template
- .root_ids
- .iter()
- .zip(right_template.root_ids.iter())
- .for_each(|(left, right)| right.set(left.get()));
+ right_template.root_ids.transfer(&left_template.root_ids);
}
fn diff_dynamic_node(
@@ -118,7 +144,7 @@ impl<'b> VirtualDom {
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
(Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
(Component(left), Component(right)) => self.diff_vcomponent(left, right, node, idx),
- (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right),
+ (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
};
@@ -191,8 +217,12 @@ impl<'b> VirtualDom {
) {
let m = self.create_component_node(right_template, right, idx);
+ let pre_edits = self.mutations.edits.len();
+
self.remove_component_node(left, true);
+ assert!(self.mutations.edits.len() > pre_edits);
+
// We want to optimize the replace case to use one less mutation if possible
// Since mutations are done in reverse, the last node removed will be the first in the stack
// Instead of *just* removing it, we can use the replace mutation
@@ -638,6 +668,7 @@ impl<'b> VirtualDom {
/// Push all the real nodes on the stack
fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
node.template
+ .get()
.roots
.iter()
.enumerate()
@@ -646,7 +677,7 @@ impl<'b> VirtualDom {
Some(node) => node,
None => {
self.mutations.push(Mutation::PushRoot {
- id: node.root_ids[idx].get().unwrap(),
+ id: node.root_ids.get(idx).unwrap(),
});
return 1;
}
@@ -673,7 +704,8 @@ impl<'b> VirtualDom {
Component(comp) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
- RenderReturn::Sync(Some(node)) => self.push_all_real_nodes(node),
+ RenderReturn::Ready(node) => self.push_all_real_nodes(node),
+ RenderReturn::Aborted(_node) => todo!(),
_ => todo!(),
}
}
@@ -701,7 +733,11 @@ impl<'b> VirtualDom {
}
/// Simply replace a placeholder with a list of nodes
- fn replace_placeholder(&mut self, l: &'b VPlaceholder, r: &'b [VNode<'b>]) {
+ fn replace_placeholder(
+ &mut self,
+ l: &'b VPlaceholder,
+ r: impl IntoIterator- >,
+ ) {
let m = self.create_children(r);
let id = l.id.get().unwrap();
self.mutations.push(Mutation::ReplaceWith { id, m });
@@ -773,11 +809,11 @@ impl<'b> VirtualDom {
}
fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
- for (idx, _) in node.template.roots.iter().enumerate() {
+ for (idx, _) in node.template.get().roots.iter().enumerate() {
if let Some(dy) = node.dynamic_root(idx) {
self.remove_dynamic_node(dy, gen_muts);
} else {
- let id = node.root_ids[idx].get().unwrap();
+ let id = node.root_ids.get(idx).unwrap();
if gen_muts {
self.mutations.push(Mutation::Remove { id });
}
@@ -790,8 +826,19 @@ impl<'b> VirtualDom {
let mut id = None;
for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
// We'll clean up the root nodes either way, so don't worry
- if node.template.attr_paths[idx].len() == 1 {
- continue;
+ let path_len = node
+ .template
+ .get()
+ .attr_paths
+ .get(idx)
+ .map(|path| path.len());
+ // if the path is 1 the attribute is in the root, so we don't need to clean it up
+ // if the path is 0, the attribute is a not attached at all, so we don't need to clean it up
+
+ if let Some(len) = path_len {
+ if (..=1).contains(&len) {
+ continue;
+ }
}
let next_id = attr.mounted_element.get();
@@ -808,12 +855,16 @@ impl<'b> VirtualDom {
fn remove_nested_dyn_nodes(&mut self, node: &VNode) {
for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
- // Roots are cleaned up automatically above
- if node.template.node_paths[idx].len() == 1 {
- continue;
+ let path_len = node
+ .template
+ .get()
+ .node_paths
+ .get(idx)
+ .map(|path| path.len());
+ // Roots are cleaned up automatically above and nodes with a empty path are placeholders
+ if let Some(2..) = path_len {
+ self.remove_dynamic_node(dyn_node, false)
}
-
- self.remove_dynamic_node(dyn_node, false);
}
}
@@ -847,14 +898,20 @@ impl<'b> VirtualDom {
}
fn remove_component_node(&mut self, comp: &VComponent, gen_muts: bool) {
- let scope = comp.scope.take().unwrap();
+ let scope = comp
+ .scope
+ .take()
+ .expect("VComponents to always have a scope");
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
- RenderReturn::Sync(Some(t)) => {
- println!("Removing component node sync {:?}", gen_muts);
- self.remove_node(t, gen_muts)
+ RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
+ RenderReturn::Aborted(t) => {
+ if let Some(id) = t.id.get() {
+ self.try_reclaim(id);
+ }
+ return;
}
- _ => todo!("cannot handle nonstandard nodes"),
+ _ => todo!(),
};
let props = self.scopes[scope.0].props.take();
@@ -873,14 +930,14 @@ impl<'b> VirtualDom {
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(0) {
- None => node.root_ids[0].get().unwrap(),
+ None => node.root_ids.get(0).unwrap(),
Some(Text(t)) => t.id.get().unwrap(),
Some(Fragment(t)) => self.find_first_element(&t[0]),
Some(Placeholder(t)) => t.id.get().unwrap(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
- RenderReturn::Sync(Some(t)) => self.find_first_element(t),
+ RenderReturn::Ready(t) => self.find_first_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
}
@@ -888,15 +945,15 @@ impl<'b> VirtualDom {
}
fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
- match node.dynamic_root(node.template.roots.len() - 1) {
- None => node.root_ids.last().unwrap().get().unwrap(),
+ match node.dynamic_root(node.template.get().roots.len() - 1) {
+ None => node.root_ids.last().unwrap(),
Some(Text(t)) => t.id.get().unwrap(),
Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
Some(Placeholder(t)) => t.id.get().unwrap(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
- RenderReturn::Sync(Some(t)) => self.find_last_element(t),
+ RenderReturn::Ready(t) => self.find_last_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
}
@@ -911,27 +968,30 @@ impl<'b> VirtualDom {
/// We use the pointer of the dynamic_node list in this case
fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool {
std::ptr::eq(left_template, right_template)
- || std::ptr::eq(left_template.dynamic_nodes, right_template.dynamic_nodes)
}
fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool {
- !std::ptr::eq(left_template.template.name, right_template.template.name)
- && left_template.template.name != right_template.template.name
+ let left_template_name = left_template.template.get().name;
+ let right_template_name = right_template.template.get().name;
+ // we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template
+ !std::ptr::eq(left_template_name, right_template_name)
}
fn matching_components<'a>(
left: &'a VNode<'a>,
right: &'a VNode<'a>,
) -> Option
, &'a VComponent<'a>)>> {
- if left.template.roots.len() != right.template.roots.len() {
+ let left_template = left.template.get();
+ let right_template = right.template.get();
+ if left_template.roots.len() != right_template.roots.len() {
return None;
}
// run through the components, ensuring they're the same
- left.template
+ left_template
.roots
.iter()
- .zip(right.template.roots.iter())
+ .zip(right_template.roots.iter())
.map(|(l, r)| {
let (l, r) = match (l, r) {
(TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
@@ -956,11 +1016,12 @@ fn matching_components<'a>(
/// - for appending children we can use AppendChildren
#[allow(dead_code)]
fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
- let path = node.template.node_paths[idx];
+ let template = node.template.get();
+ let path = template.node_paths[idx];
// use a loop to index every static node's children until the path has run out
// only break if the last path index is a dynamic node
- let mut static_node = &node.template.roots[path[0] as usize];
+ let mut static_node = &template.roots[path[0] as usize];
for i in 1..path.len() - 1 {
match static_node {
diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs
index fd8810f1a..071fbe48b 100644
--- a/packages/core/src/error_boundary.rs
+++ b/packages/core/src/error_boundary.rs
@@ -1,19 +1,203 @@
-use std::cell::RefCell;
-
-use crate::ScopeId;
+use crate::{ScopeId, ScopeState};
+use std::{
+ any::{Any, TypeId},
+ cell::RefCell,
+ fmt::Debug,
+};
/// A boundary that will capture any errors from child components
-#[allow(dead_code)]
pub struct ErrorBoundary {
- error: RefCell>,
- id: ScopeId,
+ error: RefCell >,
+ _id: ScopeId,
+}
+
+/// An instance of an error captured by a descendant component.
+pub struct CapturedError {
+ /// The error captured by the error boundary
+ pub error: Box,
+
+ /// The scope that threw the error
+ pub scope: ScopeId,
+}
+
+impl CapturedError {
+ /// Downcast the error type into a concrete error type
+ pub fn downcast(&self) -> Option<&T> {
+ if TypeId::of::() == self.error.type_id() {
+ let raw = self.error.as_ref() as *const _ as *const T;
+ Some(unsafe { &*raw })
+ } else {
+ None
+ }
+ }
}
impl ErrorBoundary {
pub fn new(id: ScopeId) -> Self {
Self {
error: RefCell::new(None),
- id,
+ _id: id,
+ }
+ }
+
+ /// Push an error into this Error Boundary
+ pub fn insert_error(&self, scope: ScopeId, error: Box) {
+ self.error.replace(Some(CapturedError { error, scope }));
+ }
+}
+
+/// A trait to allow results to be thrown upwards to the nearest Error Boundary
+///
+/// The canonical way of using this trait is to throw results from hooks, aborting rendering
+/// through question mark synax. The throw method returns an option that evalutes to None
+/// if there is an error, injecting the error to the nearest error boundary.
+///
+/// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
+///
+/// The call stack is saved for this component and provided to the error boundary
+///
+/// ```rust, ignore
+///
+/// #[inline_props]
+/// fn app(cx: Scope, count: String) -> Element {
+/// let id: i32 = count.parse().throw(cx)?;
+///
+/// cx.render(rsx! {
+/// div { "Count {}" }
+/// })
+/// }
+/// ```
+pub trait Throw: Sized {
+ /// The value that will be returned in if the given value is `Ok`.
+ type Out;
+
+ /// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
+ ///
+ /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
+ ///
+ /// The call stack is saved for this component and provided to the error boundary
+ ///
+ ///
+ /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
+ /// which is what this trait shells out to.
+ ///
+ ///
+ /// ```rust, ignore
+ ///
+ /// #[inline_props]
+ /// fn app(cx: Scope, count: String) -> Element {
+ /// let id: i32 = count.parse().throw(cx)?;
+ ///
+ /// cx.render(rsx! {
+ /// div { "Count {}" }
+ /// })
+ /// }
+ /// ```
+ fn throw(self, cx: &ScopeState) -> Option;
+
+ /// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
+ ///
+ /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
+ ///
+ /// The call stack is saved for this component and provided to the error boundary
+ ///
+ ///
+ /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
+ /// which is what this trait shells out to.
+ ///
+ ///
+ /// ```rust, ignore
+ ///
+ /// #[inline_props]
+ /// fn app(cx: Scope, count: String) -> Element {
+ /// let id: i32 = count.parse().throw(cx)?;
+ ///
+ /// cx.render(rsx! {
+ /// div { "Count {}" }
+ /// })
+ /// }
+ /// ```
+ fn throw_with(
+ self,
+ cx: &ScopeState,
+ e: impl FnOnce() -> D,
+ ) -> Option;
+}
+
+/// We call clone on any errors that can be owned out of a reference
+impl<'a, T, O: Debug + 'static, E: ToOwned> Throw for &'a Result {
+ type Out = &'a T;
+
+ fn throw(self, cx: &ScopeState) -> Option {
+ match self {
+ Ok(t) => Some(t),
+ Err(e) => {
+ cx.throw(e.to_owned());
+ None
+ }
+ }
+ }
+
+ fn throw_with(
+ self,
+ cx: &ScopeState,
+ err: impl FnOnce() -> D,
+ ) -> Option {
+ match self {
+ Ok(t) => Some(t),
+ Err(_e) => {
+ cx.throw(err());
+ None
+ }
}
}
}
+
+/// Or just throw errors we know about
+impl Throw for Result {
+ type Out = T;
+
+ fn throw(self, cx: &ScopeState) -> Option {
+ match self {
+ Ok(t) => Some(t),
+ Err(e) => {
+ cx.throw(e);
+ None
+ }
+ }
+ }
+
+ fn throw_with(
+ self,
+ cx: &ScopeState,
+ error: impl FnOnce() -> D,
+ ) -> Option {
+ self.ok().or_else(|| {
+ cx.throw(error());
+ None
+ })
+ }
+}
+
+/// Or just throw errors we know about
+impl Throw for Option {
+ type Out = T;
+
+ fn throw(self, cx: &ScopeState) -> Option {
+ self.or_else(|| {
+ cx.throw("None error.");
+ None
+ })
+ }
+
+ fn throw_with(
+ self,
+ cx: &ScopeState,
+ error: impl FnOnce() -> D,
+ ) -> Option {
+ self.or_else(|| {
+ cx.throw(error());
+ None
+ })
+ }
+}
diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs
index d36031556..a9e919bfa 100644
--- a/packages/core/src/fragment.rs
+++ b/packages/core/src/fragment.rs
@@ -31,8 +31,8 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
Some(VNode {
key: children.key,
parent: children.parent,
- template: children.template,
- root_ids: children.root_ids,
+ template: children.template.clone(),
+ root_ids: children.root_ids.clone(),
dynamic_nodes: children.dynamic_nodes,
dynamic_attrs: children.dynamic_attrs,
})
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index d6ae70c04..f13c20b43 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
- fc_to_builder, AnyValue, AnyValueContainer, Attribute, AttributeValue, Component, DynamicNode,
- Element, ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation,
- Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
- TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
+ fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element,
+ ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties,
+ RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template,
+ TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
- fc_to_builder, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue,
- LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
- TemplateAttribute, TemplateNode, VNode, VirtualDom,
+ fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
+ Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
+ Throw, VNode, VirtualDom,
};
}
diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs
index c0ac4efd4..030e77e8d 100644
--- a/packages/core/src/nodes.rs
+++ b/packages/core/src/nodes.rs
@@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump;
use std::{
any::{Any, TypeId},
- cell::{Cell, RefCell},
+ cell::{Cell, RefCell, UnsafeCell},
fmt::Arguments,
future::Future,
ops::Deref,
@@ -22,10 +22,22 @@ pub type TemplateId = &'static str;
/// you might need to handle the case where there's no node immediately ready.
pub enum RenderReturn<'a> {
/// A currently-available element
- Sync(Element<'a>),
+ Ready(VNode<'a>),
+
+ /// The component aborted rendering early. It might've thrown an error.
+ ///
+ /// In its place we've produced a placeholder to locate its spot in the dom when
+ /// it recovers.
+ Aborted(VPlaceholder),
/// An ongoing future that will resolve to a [`Element`]
- Async(BumpBox<'a, dyn Future> + 'a>),
+ Pending(BumpBox<'a, dyn Future> + 'a>),
+}
+
+impl<'a> Default for RenderReturn<'a> {
+ fn default() -> Self {
+ RenderReturn::Aborted(VPlaceholder::default())
+ }
}
/// A reference to a template along with any context needed to hydrate it
@@ -43,11 +55,11 @@ pub struct VNode<'a> {
pub parent: Option,
/// The static nodes and static descriptor of the template
- pub template: Template<'static>,
+ pub template: Cell>,
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
/// the actual Dom
- pub root_ids: &'a [Cell>],
+ pub root_ids: BoxedCellSlice,
/// The dynamic parts of the template
pub dynamic_nodes: &'a [DynamicNode<'a>],
@@ -56,21 +68,120 @@ pub struct VNode<'a> {
pub dynamic_attrs: &'a [Attribute<'a>],
}
+// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell)
+#[derive(Debug, Default)]
+pub struct BoxedCellSlice(UnsafeCell >>);
+
+impl Clone for BoxedCellSlice {
+ fn clone(&self) -> Self {
+ Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() }))
+ }
+}
+
+impl BoxedCellSlice {
+ pub fn last(&self) -> Option {
+ unsafe {
+ (*self.0.get())
+ .as_ref()
+ .and_then(|inner| inner.as_ref().last().copied())
+ }
+ }
+
+ pub fn get(&self, idx: usize) -> Option {
+ unsafe {
+ (*self.0.get())
+ .as_ref()
+ .and_then(|inner| inner.as_ref().get(idx).copied())
+ }
+ }
+
+ pub unsafe fn get_unchecked(&self, idx: usize) -> Option {
+ (*self.0.get())
+ .as_ref()
+ .and_then(|inner| inner.as_ref().get(idx).copied())
+ }
+
+ pub fn set(&self, idx: usize, new: ElementId) {
+ unsafe {
+ if let Some(inner) = &mut *self.0.get() {
+ inner[idx] = new;
+ }
+ }
+ }
+
+ pub fn intialize(&self, contents: Box<[ElementId]>) {
+ unsafe {
+ *self.0.get() = Some(contents);
+ }
+ }
+
+ pub fn transfer(&self, other: &Self) {
+ unsafe {
+ *self.0.get() = (*other.0.get()).clone();
+ }
+ }
+
+ pub fn take_from(&self, other: &Self) {
+ unsafe {
+ *self.0.get() = (*other.0.get()).take();
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ unsafe {
+ (*self.0.get())
+ .as_ref()
+ .map(|inner| inner.len())
+ .unwrap_or(0)
+ }
+ }
+}
+
+impl<'a> IntoIterator for &'a BoxedCellSlice {
+ type Item = ElementId;
+
+ type IntoIter = BoxedCellSliceIter<'a>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ BoxedCellSliceIter {
+ index: 0,
+ borrow: self,
+ }
+ }
+}
+
+pub struct BoxedCellSliceIter<'a> {
+ index: usize,
+ borrow: &'a BoxedCellSlice,
+}
+
+impl Iterator for BoxedCellSliceIter<'_> {
+ type Item = ElementId;
+
+ fn next(&mut self) -> Option {
+ let result = self.borrow.get(self.index);
+ if result.is_some() {
+ self.index += 1;
+ }
+ result
+ }
+}
+
impl<'a> VNode<'a> {
/// Create a template with no nodes that will be skipped over during diffing
pub fn empty() -> Element<'a> {
Some(VNode {
key: None,
parent: None,
- root_ids: &[],
+ root_ids: BoxedCellSlice::default(),
dynamic_nodes: &[],
dynamic_attrs: &[],
- template: Template {
+ template: Cell::new(Template {
name: "dioxus-empty",
roots: &[],
node_paths: &[],
attr_paths: &[],
- },
+ }),
})
}
@@ -78,7 +189,7 @@ impl<'a> VNode<'a> {
///
/// Returns [`None`] if the root is actually a static node (Element/Text)
pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
- match &self.template.roots[idx] {
+ match &self.template.get().roots[idx] {
TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
Some(&self.dynamic_nodes[*id])
@@ -103,32 +214,85 @@ impl<'a> VNode<'a> {
///
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
/// ways, with the suggested approach being the unique code location (file, line, col, etc).
-#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub struct Template<'a> {
/// The name of the template. This must be unique across your entire program for template diffing to work properly
///
/// If two templates have the same name, it's likely that Dioxus will panic when diffing.
+ #[cfg_attr(
+ feature = "serialize",
+ serde(deserialize_with = "deserialize_string_leaky")
+ )]
pub name: &'a str,
/// The list of template nodes that make up the template
///
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
+ #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
pub roots: &'a [TemplateNode<'a>],
/// The paths of each node relative to the root of the template.
///
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
/// topmost element, not the `roots` field.
+ #[cfg_attr(
+ feature = "serialize",
+ serde(deserialize_with = "deserialize_bytes_leaky")
+ )]
pub node_paths: &'a [&'a [u8]],
/// The paths of each dynamic attribute relative to the root of the template
///
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
/// topmost element, not the `roots` field.
+ #[cfg_attr(
+ feature = "serialize",
+ serde(deserialize_with = "deserialize_bytes_leaky")
+ )]
pub attr_paths: &'a [&'a [u8]],
}
+#[cfg(feature = "serialize")]
+fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ use serde::Deserialize;
+
+ let deserialized = String::deserialize(deserializer)?;
+ Ok(&*Box::leak(deserialized.into_boxed_str()))
+}
+
+#[cfg(feature = "serialize")]
+fn deserialize_bytes_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a [&'a [u8]], D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ use serde::Deserialize;
+
+ let deserialized = Vec::>::deserialize(deserializer)?;
+ let deserialized = deserialized
+ .into_iter()
+ .map(|v| &*Box::leak(v.into_boxed_slice()))
+ .collect::>();
+ Ok(&*Box::leak(deserialized.into_boxed_slice()))
+}
+
+#[cfg(feature = "serialize")]
+fn deserialize_leaky<'a, 'de, T: serde::Deserialize<'de>, D>(
+ deserializer: D,
+) -> Result<&'a [T], D::Error>
+where
+ T: serde::Deserialize<'de>,
+ D: serde::Deserializer<'de>,
+{
+ use serde::Deserialize;
+
+ let deserialized = Box::<[T]>::deserialize(deserializer)?;
+ Ok(&*Box::leak(deserialized))
+}
+
impl<'a> Template<'a> {
/// Is this template worth caching at all, since it's completely runtime?
///
@@ -145,7 +309,11 @@ impl<'a> Template<'a> {
///
/// This can be created at compile time, saving the VirtualDom time when diffing the tree
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
+#[cfg_attr(
+ feature = "serialize",
+ derive(serde::Serialize, serde::Deserialize),
+ serde(tag = "type")
+)]
pub enum TemplateNode<'a> {
/// An statically known element in the dom.
///
@@ -165,9 +333,11 @@ pub enum TemplateNode<'a> {
/// A list of possibly dynamic attribues for this element
///
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
+ #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
attrs: &'a [TemplateAttribute<'a>],
/// A list of template nodes that define another set of template nodes
+ #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
children: &'a [TemplateNode<'a>],
},
@@ -544,7 +714,10 @@ pub trait ComponentReturn<'a, A = ()> {
impl<'a> ComponentReturn<'a> for Element<'a> {
fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
- RenderReturn::Sync(self)
+ match self {
+ Some(node) => RenderReturn::Ready(node),
+ None => RenderReturn::default(),
+ }
}
}
@@ -556,7 +729,7 @@ where
{
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
let f: &mut dyn Future> = cx.bump().alloc(self);
- RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
+ RenderReturn::Pending(unsafe { BumpBox::from_raw(f) })
}
}
@@ -640,8 +813,8 @@ impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(_cx.bump().alloc([VNode {
parent: self.parent,
- template: self.template,
- root_ids: self.root_ids,
+ template: self.template.clone(),
+ root_ids: self.root_ids.clone(),
key: self.key,
dynamic_nodes: self.dynamic_nodes,
dynamic_attrs: self.dynamic_attrs,
diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs
index a6442184d..7aed417b1 100644
--- a/packages/core/src/scheduler/mod.rs
+++ b/packages/core/src/scheduler/mod.rs
@@ -8,7 +8,7 @@ mod waker;
pub use suspense::*;
pub use task::*;
-pub use waker::RcWake;
+pub use waker::ArcWake;
/// The type of message that can be sent to the scheduler.
///
@@ -25,16 +25,16 @@ pub(crate) enum SchedulerMsg {
SuspenseNotified(SuspenseId),
}
-use std::{cell::RefCell, rc::Rc};
+use std::{cell::RefCell, rc::Rc, sync::Arc};
pub(crate) struct Scheduler {
pub sender: futures_channel::mpsc::UnboundedSender,
/// Tasks created with cx.spawn
- pub tasks: RefCell>>,
+ pub tasks: RefCell>>,
/// Async components
- pub leaves: RefCell>>,
+ pub leaves: RefCell>>,
}
impl Scheduler {
diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs
index 8d7d7337e..c7cd650bb 100644
--- a/packages/core/src/scheduler/suspense.rs
+++ b/packages/core/src/scheduler/suspense.rs
@@ -1,11 +1,11 @@
-use super::{waker::RcWake, SchedulerMsg};
+use super::{waker::ArcWake, SchedulerMsg};
use crate::ElementId;
use crate::{innerlude::Mutations, Element, ScopeId};
use std::future::Future;
+use std::sync::Arc;
use std::{
cell::{Cell, RefCell},
collections::HashSet,
- rc::Rc,
};
/// An ID representing an ongoing suspended component
@@ -42,8 +42,8 @@ pub(crate) struct SuspenseLeaf {
pub(crate) task: *mut dyn Future>,
}
-impl RcWake for SuspenseLeaf {
- fn wake_by_ref(arc_self: &Rc) {
+impl ArcWake for SuspenseLeaf {
+ fn wake_by_ref(arc_self: &Arc) {
arc_self.notified.set(true);
_ = arc_self
.tx
diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs
index fb4378104..8c1d7ca76 100644
--- a/packages/core/src/scheduler/task.rs
+++ b/packages/core/src/scheduler/task.rs
@@ -1,8 +1,9 @@
-use super::{waker::RcWake, Scheduler, SchedulerMsg};
+use super::{waker::ArcWake, Scheduler, SchedulerMsg};
use crate::ScopeId;
use std::cell::RefCell;
use std::future::Future;
-use std::{pin::Pin, rc::Rc};
+use std::pin::Pin;
+use std::sync::Arc;
/// A task's unique identifier.
///
@@ -35,7 +36,7 @@ impl Scheduler {
let entry = tasks.vacant_entry();
let task_id = TaskId(entry.key());
- entry.insert(Rc::new(LocalTask {
+ entry.insert(Arc::new(LocalTask {
id: task_id,
tx: self.sender.clone(),
task: RefCell::new(Box::pin(task)),
@@ -57,8 +58,8 @@ impl Scheduler {
}
}
-impl RcWake for LocalTask {
- fn wake_by_ref(arc_self: &Rc) {
+impl ArcWake for LocalTask {
+ fn wake_by_ref(arc_self: &Arc) {
_ = arc_self
.tx
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs
index 8dff17b01..205bd8ea0 100644
--- a/packages/core/src/scheduler/wait.rs
+++ b/packages/core/src/scheduler/wait.rs
@@ -10,7 +10,7 @@ use crate::{
ScopeId, TaskId, VNode, VirtualDom,
};
-use super::{waker::RcWake, SuspenseId};
+use super::{waker::ArcWake, SuspenseId};
impl VirtualDom {
/// Handle notifications by tasks inside the scheduler
@@ -67,18 +67,21 @@ impl VirtualDom {
// we should attach them to that component and then render its children
// continue rendering the tree until we hit yet another suspended component
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
- // 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);
let scope = &mut self.scopes[scope_id.0];
let arena = scope.current_frame();
- let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
+ let ret = arena.bump.alloc(match new_nodes {
+ Some(new) => RenderReturn::Ready(new),
+ None => RenderReturn::default(),
+ });
+
arena.node.set(ret);
fiber.waiting_on.borrow_mut().remove(&id);
- if let RenderReturn::Sync(Some(template)) = ret {
+ if let RenderReturn::Ready(template) = ret {
let mutations_ref = &mut fiber.mutations.borrow_mut();
let mutations = &mut **mutations_ref;
let template: &VNode = unsafe { std::mem::transmute(template) };
diff --git a/packages/core/src/scheduler/waker.rs b/packages/core/src/scheduler/waker.rs
index 762020167..fc75fe8fd 100644
--- a/packages/core/src/scheduler/waker.rs
+++ b/packages/core/src/scheduler/waker.rs
@@ -1,36 +1,37 @@
+use std::mem;
+use std::sync::Arc;
use std::task::{RawWaker, RawWakerVTable, Waker};
-use std::{mem, rc::Rc};
-pub trait RcWake: Sized {
+pub trait ArcWake: Sized {
/// Create a waker from this self-wakening object
- fn waker(self: &Rc) -> Waker {
- unsafe fn rc_vtable() -> &'static RawWakerVTable {
+ fn waker(self: &Arc) -> Waker {
+ unsafe fn rc_vtable() -> &'static RawWakerVTable {
&RawWakerVTable::new(
|data| {
- let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::()));
+ let arc = mem::ManuallyDrop::new(Arc::::from_raw(data.cast::()));
let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
RawWaker::new(data, rc_vtable::())
},
- |data| Rc::from_raw(data.cast::()).wake(),
+ |data| Arc::from_raw(data.cast::()).wake(),
|data| {
- let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::()));
- RcWake::wake_by_ref(&arc);
+ let arc = mem::ManuallyDrop::new(Arc::::from_raw(data.cast::()));
+ ArcWake::wake_by_ref(&arc);
},
- |data| drop(Rc::::from_raw(data.cast::())),
+ |data| drop(Arc::::from_raw(data.cast::())),
)
}
unsafe {
Waker::from_raw(RawWaker::new(
- Rc::into_raw(self.clone()).cast(),
+ Arc::into_raw(self.clone()).cast(),
rc_vtable::(),
))
}
}
- fn wake_by_ref(arc_self: &Rc);
+ fn wake_by_ref(arc_self: &Arc);
- fn wake(self: Rc) {
+ fn wake(self: Arc) {
Self::wake_by_ref(&self)
}
}
diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs
index 6dd5316d3..7cbca96e4 100644
--- a/packages/core/src/scope_arena.rs
+++ b/packages/core/src/scope_arena.rs
@@ -4,7 +4,7 @@ use crate::{
innerlude::DirtyScope,
innerlude::{SuspenseId, SuspenseLeaf},
nodes::RenderReturn,
- scheduler::RcWake,
+ scheduler::ArcWake,
scopes::{ScopeId, ScopeState},
virtual_dom::VirtualDom,
};
@@ -13,7 +13,7 @@ use futures_util::FutureExt;
use std::{
mem,
pin::Pin,
- rc::Rc,
+ sync::Arc,
task::{Context, Poll},
};
@@ -79,17 +79,18 @@ impl VirtualDom {
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref();
let props: &dyn AnyProps = mem::transmute(props);
+
props.render(scope).extend_lifetime()
};
// immediately resolve futures that can be resolved
- if let RenderReturn::Async(task) = &mut new_nodes {
+ if let RenderReturn::Pending(task) = &mut new_nodes {
let mut leaves = self.scheduler.leaves.borrow_mut();
let entry = leaves.vacant_entry();
let suspense_id = SuspenseId(entry.key());
- let leaf = Rc::new(SuspenseLeaf {
+ let leaf = Arc::new(SuspenseLeaf {
scope_id,
task: task.as_mut(),
id: suspense_id,
@@ -108,7 +109,11 @@ impl VirtualDom {
match pinned.poll_unpin(&mut cx) {
// If nodes are produced, then set it and we can break
Poll::Ready(nodes) => {
- new_nodes = RenderReturn::Sync(nodes);
+ new_nodes = match nodes {
+ Some(nodes) => RenderReturn::Ready(nodes),
+ None => RenderReturn::default(),
+ };
+
break;
}
@@ -150,6 +155,6 @@ impl VirtualDom {
});
// rebind the lifetime now that its stored internally
- unsafe { mem::transmute(allocated) }
+ unsafe { allocated.extend_lifetime_ref() }
}
}
diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs
index 7520df942..20be78cce 100644
--- a/packages/core/src/scopes.rs
+++ b/packages/core/src/scopes.rs
@@ -4,7 +4,7 @@ use crate::{
arena::ElementId,
bump_frame::BumpFrame,
innerlude::{DynamicNode, EventHandler, VComponent, VText},
- innerlude::{ListenerCb, Scheduler, SchedulerMsg},
+ innerlude::{ErrorBoundary, ListenerCb, Scheduler, SchedulerMsg},
lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
Attribute, AttributeValue, Element, Event, Properties, TaskId,
@@ -14,7 +14,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
- fmt::Arguments,
+ fmt::{Arguments, Debug},
future::Future,
rc::Rc,
sync::Arc,
@@ -513,6 +513,19 @@ impl<'src> ScopeState {
AttributeValue::Listener(ListenerCb(RefCell::new(Some(boxed))))
}
+ /// Inject an error into the nearest error boundary and quit rendering
+ ///
+ /// The error doesn't need to implement Error or any specific traits since the boundary
+ /// itself will downcast the error into a trait object.
+ pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
+ if let Some(cx) = self.consume_context::>() {
+ cx.insert_error(self.scope_id(), Box::new(error));
+ }
+
+ // Always return none during a throw
+ None
+ }
+
/// Store a value between renders. The foundational hook for all other hooks.
///
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs
index 9afbe2ce9..890416cd9 100644
--- a/packages/core/src/virtual_dom.rs
+++ b/packages/core/src/virtual_dom.rs
@@ -142,7 +142,8 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
/// }
/// ```
pub struct VirtualDom {
- pub(crate) templates: FxHashMap>,
+ // 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,
@@ -354,10 +355,11 @@ impl VirtualDom {
while let Some(el_ref) = parent_path {
// safety: we maintain references of all vnodes in the element slab
let template = unsafe { &*el_ref.template };
+ let node_template = template.template.get();
let target_path = el_ref.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
- let this_path = template.template.attr_paths[idx];
+ let this_path = node_template.attr_paths[idx];
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
// we should fix this so that we look for "onclick" instead of "click"
@@ -454,6 +456,30 @@ impl VirtualDom {
}
}
+ /// Replace a template at runtime. This will re-render all components that use this template.
+ /// This is the primitive that enables hot-reloading.
+ ///
+ /// The caller must ensure that the template refrences the same dynamic attributes and nodes as the original template.
+ ///
+ /// This will only replace the the parent template, not any nested templates.
+ pub fn replace_template(&mut self, template: Template<'static>) {
+ self.register_template_first_byte_index(template);
+ // iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine
+ for (_, scope) in &self.scopes {
+ if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() {
+ if sync.template.get().name.rsplit_once(':').unwrap().0
+ == template.name.rsplit_once(':').unwrap().0
+ {
+ let height = scope.height;
+ self.dirty_scopes.insert(DirtyScope {
+ height,
+ id: scope.id,
+ });
+ }
+ }
+ }
+ }
+
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
///
/// The mutations item expects the RealDom's stack to be the root of the application.
@@ -477,7 +503,7 @@ impl VirtualDom {
pub fn rebuild(&mut self) -> Mutations {
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
// Rebuilding implies we append the created elements to the root
- RenderReturn::Sync(Some(node)) => {
+ RenderReturn::Ready(node) => {
let m = self.create_scope(ScopeId(0), node);
self.mutations.edits.push(Mutation::AppendChildren {
id: ElementId(0),
@@ -485,8 +511,8 @@ impl VirtualDom {
});
}
// If an error occurs, we should try to render the default error component and context where the error occured
- RenderReturn::Sync(None) => panic!("Cannot catch errors during rebuild"),
- RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
+ RenderReturn::Aborted(_placeholder) => panic!("Cannot catch errors during rebuild"),
+ RenderReturn::Pending(_) => unreachable!("Root scope cannot be an async component"),
}
self.finalize()
diff --git a/packages/core/tests/error_boundary.rs b/packages/core/tests/error_boundary.rs
new file mode 100644
index 000000000..7dbfca343
--- /dev/null
+++ b/packages/core/tests/error_boundary.rs
@@ -0,0 +1,58 @@
+use dioxus::prelude::*;
+use futures_util::Future;
+
+#[test]
+fn catches_panic() {
+ let mut dom = VirtualDom::new(app);
+
+ let a = dom.rebuild();
+
+ dbg!(a);
+}
+
+fn app(cx: Scope) -> Element {
+ cx.render(rsx! {
+ div {
+ h1 { "Title" }
+
+ NoneChild {}
+ }
+ })
+}
+
+fn NoneChild(cx: Scope) -> Element {
+ None
+}
+
+fn PanicChild(cx: Scope) -> Element {
+ panic!("Rendering panicked for whatever reason");
+
+ cx.render(rsx! {
+ h1 { "It works!" }
+ })
+}
+
+fn ThrowChild(cx: Scope) -> Element {
+ cx.throw(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd"))?;
+
+ let g: i32 = "123123".parse().throw(cx)?;
+
+ cx.render(rsx! {
+ div {}
+ })
+}
+
+fn custom_allocator(cx: Scope) -> Element {
+ let g = String::new();
+
+ let p = g.as_str();
+
+ let g2 = cx.use_hook(|| 123);
+ // cx.spawn(async move {
+
+ // //
+ // // println!("Thig is {p}");
+ // });
+
+ None
+}
diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs
index c732f5a8b..2d08ad79a 100644
--- a/packages/desktop/src/controller.rs
+++ b/packages/desktop/src/controller.rs
@@ -1,7 +1,7 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent};
use dioxus_core::*;
use dioxus_html::HtmlEvent;
-use futures_channel::mpsc::{unbounded, UnboundedSender};
+use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_util::StreamExt;
#[cfg(target_os = "ios")]
use objc::runtime::Object;
@@ -25,7 +25,10 @@ pub(super) struct DesktopController {
pub(super) quit_app_on_close: bool,
pub(super) is_ready: Arc,
pub(super) proxy: EventLoopProxy,
+
pub(super) event_tx: UnboundedSender,
+ #[cfg(debug_assertions)]
+ pub(super) templates_tx: UnboundedSender>,
#[cfg(target_os = "ios")]
pub(super) views: Vec<*mut Object>,
@@ -41,6 +44,7 @@ impl DesktopController {
) -> Self {
let edit_queue = Arc::new(Mutex::new(Vec::new()));
let (event_tx, mut event_rx) = unbounded();
+ let (templates_tx, mut templates_rx) = unbounded();
let proxy2 = proxy.clone();
let pending_edits = edit_queue.clone();
@@ -68,6 +72,18 @@ impl DesktopController {
loop {
tokio::select! {
+ template = {
+ #[allow(unused)]
+ fn maybe_future<'a>(templates_rx: &'a mut UnboundedReceiver>) -> impl Future> + 'a {
+ #[cfg(debug_assertions)]
+ return templates_rx.select_next_some();
+ #[cfg(not(debug_assertions))]
+ return std::future::pending();
+ }
+ maybe_future(&mut templates_rx)
+ } => {
+ dom.replace_template(template);
+ }
_ = dom.wait_for_work() => {}
Some(json_value) = event_rx.next() => {
if let Ok(value) = serde_json::from_value::(json_value) {
@@ -103,6 +119,8 @@ impl DesktopController {
quit_app_on_close: true,
proxy: proxy2,
event_tx,
+ #[cfg(debug_assertions)]
+ templates_tx,
#[cfg(target_os = "ios")]
views: vec![],
}
@@ -133,8 +151,4 @@ impl DesktopController {
}
}
}
-
- pub(crate) fn set_template(&self, _serialized_template: String) {
- todo!("hot reloading currently WIP")
- }
}
diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs
index 7b41d33e0..943fbf91e 100644
--- a/packages/desktop/src/desktop_context.rs
+++ b/packages/desktop/src/desktop_context.rs
@@ -179,11 +179,6 @@ pub enum UserWindowEvent {
DragWindow,
FocusWindow,
- /// Set a new Dioxus template for hot-reloading
- ///
- /// Is a no-op in release builds. Must fit the right format for templates
- SetTemplate(String),
-
Visible(bool),
Minimize(bool),
Maximize(bool),
@@ -232,7 +227,6 @@ impl DesktopController {
match user_event {
Initialize | EditsReady => self.try_load_ready_webviews(),
- SetTemplate(template) => self.set_template(template),
CloseWindow => *control_flow = ControlFlow::Exit,
DragWindow => {
// if the drag_window has any errors, we don't do anything
diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs
index e2628cdd4..e1383390d 100644
--- a/packages/desktop/src/hot_reload.rs
+++ b/packages/desktop/src/hot_reload.rs
@@ -1,6 +1,6 @@
#![allow(dead_code)]
-use dioxus_core::VirtualDom;
+use dioxus_core::Template;
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
use std::io::{BufRead, BufReader};
@@ -13,7 +13,7 @@ fn handle_error(connection: std::io::Result) -> Option>) {
let latest_in_connection: Arc>>> =
Arc::new(Mutex::new(None));
@@ -36,11 +36,9 @@ pub(crate) fn init(_dom: &VirtualDom) {
let mut buf = String::new();
match conn.read_line(&mut buf) {
Ok(_) => {
- todo!()
- // let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
- // channel
- // .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
- // .unwrap();
+ let msg: Template<'static> =
+ serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
+ proxy.unbounded_send(msg).unwrap();
}
Err(err) => {
if err.kind() != std::io::ErrorKind::WouldBlock {
diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs
index 0080e671b..337a9485c 100644
--- a/packages/desktop/src/lib.rs
+++ b/packages/desktop/src/lib.rs
@@ -89,7 +89,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
/// use dioxus::prelude::*;
///
/// fn main() {
-/// dioxus_desktop::launch_cfg(app, AppProps { name: "asd" }, |c| c);
+/// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, |c| c);
/// }
///
/// struct AppProps {
@@ -106,6 +106,9 @@ pub fn launch_with_props(root: Component, props: P, mut cf
let event_loop = EventLoop::with_user_event();
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
+ #[cfg(debug_assertions)]
+ hot_reload::init(desktop.templates_tx.clone());
+
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml
index 3436dcc93..de5db6645 100644
--- a/packages/html/Cargo.toml
+++ b/packages/html/Cargo.toml
@@ -12,6 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.1" }
+dioxus-rsx = { path = "../rsx", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
serde_repr = { version = "0.1", optional = true }
wasm-bindgen = { version = "0.2.79", optional = true }
@@ -47,3 +48,4 @@ serde_json = "*"
default = ["serialize"]
serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde", "dioxus-core/serialize"]
wasm-bind = ["web-sys", "wasm-bindgen"]
+hot-reload-context = ["dioxus-rsx"]
\ No newline at end of file
diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs
index efbb4d797..931bc3d60 100644
--- a/packages/html/src/elements.rs
+++ b/packages/html/src/elements.rs
@@ -1,61 +1,258 @@
#![allow(non_upper_case_globals)]
-
+#[cfg(feature = "hot-reload-context")]
+use crate::{map_global_attributes, map_svg_attributes};
use crate::{GlobalAttributes, SvgAttributes};
+#[cfg(feature = "hot-reload-context")]
+use dioxus_rsx::HotReloadingContext;
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
+macro_rules! impl_attribute {
+ (
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident (DEFAULT),
+ ) => {
+ pub const $fil: AttributeDiscription = (stringify!($fil), None, false);
+ };
+
+ (
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident ($name:literal),
+ ) => {
+ pub const $fil: AttributeDiscription = ($name, None, false);
+ };
+
+ (
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident (volatile),
+ ) => {
+ pub const $fil: AttributeDiscription = (stringify!($fil), None, true);
+ };
+
+ (
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident (in $ns:ident),
+ ) => {
+ pub const $fil: AttributeDiscription = (stringify!($fil), Some(stringify!($ns)), false)
+ };
+
+ (
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident (in $ns:ident : volatile),
+ ) => {
+ pub const $fil: AttributeDiscription = (stringify!($fil), Some(stringify!($ns)), true)
+ };
+}
+
+#[cfg(feature = "hot-reload-context")]
+macro_rules! impl_attribute_match {
+ (
+ $attr:ident $fil:ident: $vil:ident (DEFAULT),
+ ) => {
+ if $attr == stringify!($fil) {
+ return Some((stringify!($fil), None));
+ }
+ };
+
+ (
+ $attr:ident $fil:ident: $vil:ident (volatile),
+ ) => {
+ if $attr == stringify!($fil) {
+ return Some((stringify!($fil), None));
+ }
+ };
+
+ (
+ $attr:ident $fil:ident: $vil:ident ($name:literal),
+ ) => {
+ if $attr == stringify!($fil) {
+ return Some(($name, None));
+ }
+ };
+
+ (
+ $attr:ident $fil:ident: $vil:ident (in $ns:ident),
+ ) => {
+ if $attr == stringify!($fil) {
+ return Some((stringify!(fil), Some(stringify!(ns))));
+ }
+ };
+}
+
+macro_rules! impl_element {
+ (
+ $(#[$attr:meta])*
+ $name:ident None {
+ $(
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident $extra:tt,
+ )*
+ }
+ ) => {
+ #[allow(non_camel_case_types)]
+ $(#[$attr])*
+ pub struct $name;
+
+ impl $name {
+ pub const TAG_NAME: &'static str = stringify!($name);
+ pub const NAME_SPACE: Option<&'static str> = None;
+
+ $(
+ impl_attribute!(
+ $(#[$attr_method])*
+ $fil: $vil ($extra),
+ );
+ )*
+ }
+
+ impl GlobalAttributes for $name {}
+ };
+
+ (
+ $(#[$attr:meta])*
+ $name:ident $namespace:tt {
+ $(
+ $(#[$attr_method:meta])*
+ $fil:ident: $vil:ident $extra:tt,
+ )*
+ }
+ ) => {
+ #[allow(non_camel_case_types)]
+ $(#[$attr])*
+ pub struct $name;
+
+ impl SvgAttributes for $name {}
+
+ impl $name {
+ pub const TAG_NAME: &'static str = stringify!($name);
+ pub const NAME_SPACE: Option<&'static str> = Some($namespace);
+
+ $(
+ impl_attribute!(
+ $(#[$attr_method])*
+ $fil: $vil in $namespace $extra
+ );
+ )*
+ }
+ }
+}
+
+#[cfg(feature = "hot-reload-context")]
+macro_rules! impl_element_match {
+ (
+ $el:ident $name:ident None {
+ $(
+ $fil:ident: $vil:ident $extra:tt,
+ )*
+ }
+ ) => {
+ if $el == stringify!($name) {
+ return Some((stringify!($name), None));
+ }
+ };
+
+ (
+ $el:ident $name:ident $namespace:tt {
+ $(
+ $fil:ident: $vil:ident $extra:tt,
+ )*
+ }
+ ) => {
+ if $el == stringify!($name) {
+ return Some((stringify!($name), Some(stringify!($namespace))));
+ }
+ };
+}
+
+#[cfg(feature = "hot-reload-context")]
+macro_rules! impl_element_match_attributes {
+ (
+ $el:ident $attr:ident $name:ident None {
+ $(
+ $fil:ident: $vil:ident $extra:tt,
+ )*
+ }
+ ) => {
+ if $el == stringify!($name) {
+ $(
+ impl_attribute_match!(
+ $attr $fil: $vil ($extra),
+ );
+ )*
+ }
+ };
+
+ (
+ $el:ident $attr:ident $name:ident $namespace:tt {
+ $(
+ $fil:ident: $vil:ident $extra:tt,
+ )*
+ }
+ ) => {
+ if $el == stringify!($name) {
+ $(
+ impl_attribute_match!(
+ $attr $fil: $vil in $namespace $extra
+ );
+ )*
+ }
+ }
+}
+
macro_rules! builder_constructors {
(
$(
$(#[$attr:meta])*
- $name:ident {
+ $name:ident $namespace:tt {
$(
$(#[$attr_method:meta])*
- $fil:ident: $vil:ident,
+ $fil:ident: $vil:ident $extra:tt,
)*
};
)*
- ) => {
- $(
- #[allow(non_camel_case_types)]
- $(#[$attr])*
- pub struct $name;
-
-
- impl $name {
- pub const TAG_NAME: &'static str = stringify!($name);
- pub const NAME_SPACE: Option<&'static str> = None;
+ ) => {
+ #[cfg(feature = "hot-reload-context")]
+ pub struct HtmlCtx;
+ #[cfg(feature = "hot-reload-context")]
+ impl HotReloadingContext for HtmlCtx {
+ fn map_attribute(element: &str, attribute: &str) -> Option<(&'static str, Option<&'static str>)> {
$(
- pub const $fil: AttributeDiscription = (stringify!($fil), None, false);
+ impl_element_match_attributes!(
+ element attribute $name $namespace {
+ $(
+ $fil: $vil $extra,
+ )*
+ }
+ );
)*
+ map_global_attributes(attribute).or_else(|| map_svg_attributes(attribute))
}
- impl GlobalAttributes for $name {}
- )*
- };
-
- ( $(
- $(#[$attr:meta])*
- $name:ident <> $namespace:tt {
- $($fil:ident: $vil:ident,)*
- };
- )* ) => {
- $(
- #[allow(non_camel_case_types)]
- $(#[$attr])*
- pub struct $name;
-
- impl SvgAttributes for $name {}
-
- impl $name {
- pub const TAG_NAME: &'static str = stringify!($name);
- pub const NAME_SPACE: Option<&'static str> = Some($namespace);
-
+ fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> {
$(
- pub const $fil: AttributeDiscription = (stringify!($fil), Some(stringify!($namespace)), false)
+ impl_element_match!(
+ element $name $namespace {
+ $(
+ $fil: $vil $extra,
+ )*
+ }
+ );
)*
+ None
}
+ }
+
+ $(
+ impl_element!(
+ $(#[$attr])*
+ $name $namespace {
+ $(
+ $(#[$attr_method])*
+ $fil: $vil $extra,
+ )*
+ }
+ );
)*
};
}
@@ -75,63 +272,63 @@ builder_constructors! {
/// [` `](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
/// element.
///
- base {
- href: Uri,
- target: Target,
+ base None {
+ href: Uri DEFAULT,
+ target: Target DEFAULT,
};
/// Build a
/// [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
/// element.
- head {};
+ head None {};
/// Build a
/// [` `](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
/// element.
- link {
+ link None {
// as: Mime,
- crossorigin: CrossOrigin,
- href: Uri,
- hreflang: LanguageTag,
- media: String, // FIXME media query
- rel: LinkType,
- sizes: String, // FIXME
- title: String, // FIXME
- r#type: Mime,
- integrity: String,
+ crossorigin: CrossOrigin DEFAULT,
+ href: Uri DEFAULT,
+ hreflang: LanguageTag DEFAULT,
+ media: String DEFAULT, // FIXME media query
+ rel: LinkType DEFAULT,
+ sizes: String DEFAULT, // FIXME
+ title: String DEFAULT, // FIXME
+ r#type: Mime DEFAULT,
+ integrity: String DEFAULT,
};
/// Build a
/// [` `](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
/// element.
- meta {
- charset: String, // FIXME IANA standard names
- content: String,
- http_equiv: HTTPEquiv,
- name: Metadata,
+ meta None {
+ charset: String DEFAULT, // FIXME IANA standard names
+ content: String DEFAULT,
+ http_equiv: HTTPEquiv DEFAULT,
+ name: Metadata DEFAULT,
};
/// Build a
/// [`