Merge branch 'master' into pr/atty303/1349
This commit is contained in:
commit
7033276c34
444 files changed
+2868
-14315
No files matched your search
@@ -28,7 +28,7 @@ slab = { workspace = true }
|
||||
futures-channel = { workspace = true }
|
||||
|
||||
smallbox = "0.8.1"
|
||||
log = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Serialize the Edits for use in Webview/Liveview instances
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = vec![1, 2, 3];
|
||||
|
||||
render! {
|
||||
unsafe_child_component {
|
||||
borrowed: &count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct Testing<'a> {
|
||||
borrowed: &'a Vec<u32>,
|
||||
}
|
||||
|
||||
fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> {
|
||||
cx.render(rsx! {
|
||||
div { "{cx.props.borrowed:?}" }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
error[E0515]: cannot return value referencing local variable `count`
|
||||
--> compile_tests/props_safety_temporary_values.rs:8:5
|
||||
|
|
||||
8 | / render! {
|
||||
9 | | unsafe_child_component {
|
||||
10 | | borrowed: &count
|
||||
| | ------ `count` is borrowed here
|
||||
11 | | }
|
||||
12 | | }
|
||||
| |_____^ returns a value referencing data owned by the current function
|
||||
|
|
||||
= note: this error originates in the macro `render` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -68,7 +68,7 @@ unsafe impl<'a, P> AnyProps<'a> for VProps<'a, P> {
|
||||
Ok(None) => RenderReturn::default(),
|
||||
Err(err) => {
|
||||
let component_name = cx.name();
|
||||
log::error!("Error while rendering component `{component_name}`: {err:?}");
|
||||
tracing::error!("Error while rendering component `{component_name}`: {err:?}");
|
||||
RenderReturn::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl ElementRef {
|
||||
Self {
|
||||
template: None,
|
||||
path: ElementPath::Root(0),
|
||||
scope: ScopeId(0),
|
||||
scope: ScopeId::ROOT,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ impl VirtualDom {
|
||||
fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
let id = entry.key();
|
||||
let scope = self.runtime.current_scope_id().unwrap_or(ScopeId(0));
|
||||
let scope = self.runtime.current_scope_id().unwrap_or(ScopeId::ROOT);
|
||||
|
||||
entry.insert(ElementRef {
|
||||
// We know this is non-null because it comes from a reference
|
||||
|
||||
@@ -89,8 +89,12 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
// Intialize the root nodes slice
|
||||
*node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()];
|
||||
// Initialize the root nodes slice
|
||||
{
|
||||
let mut nodes_mut = node.root_ids.borrow_mut();
|
||||
let len = node.template.get().roots.len();
|
||||
nodes_mut.resize(len, ElementId::default());
|
||||
};
|
||||
|
||||
// The best renderers will have templates prehydrated and registered
|
||||
// Just in case, let's create the template using instructions anyways
|
||||
|
||||
@@ -17,6 +17,7 @@ use DynamicNode::*;
|
||||
|
||||
impl<'b> VirtualDom {
|
||||
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
|
||||
self.runtime.scope_stack.borrow_mut().push(scope);
|
||||
let scope_state = &mut self.get_scope(scope).unwrap();
|
||||
unsafe {
|
||||
// Load the old and new bump arenas
|
||||
@@ -47,6 +48,7 @@ impl<'b> VirtualDom {
|
||||
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
|
||||
};
|
||||
}
|
||||
self.runtime.scope_stack.borrow_mut().pop();
|
||||
}
|
||||
|
||||
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
|
||||
@@ -163,7 +165,13 @@ impl<'b> VirtualDom {
|
||||
});
|
||||
|
||||
// Make sure the roots get transferred over while we're here
|
||||
*right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone();
|
||||
{
|
||||
let mut right = right_template.root_ids.borrow_mut();
|
||||
right.clear();
|
||||
for &element in left_template.root_ids.borrow().iter() {
|
||||
right.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
let root_ids = right_template.root_ids.borrow();
|
||||
|
||||
@@ -254,7 +262,8 @@ impl<'b> VirtualDom {
|
||||
right.scope.set(Some(scope_id));
|
||||
|
||||
// copy out the box for both
|
||||
let old = self.scopes[scope_id.0].props.as_ref();
|
||||
let old_scope = &self.scopes[scope_id.0];
|
||||
let old = old_scope.props.as_ref();
|
||||
let new: Box<dyn AnyProps> = right.props.take().unwrap();
|
||||
let new: Box<dyn AnyProps> = unsafe { std::mem::transmute(new) };
|
||||
|
||||
@@ -262,6 +271,11 @@ impl<'b> VirtualDom {
|
||||
// The target scopestate still has the reference to the old props, so there's no need to update anything
|
||||
// This also implicitly drops the new props since they're not used
|
||||
if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
|
||||
tracing::trace!(
|
||||
"Memoized props for component {:#?} ({})",
|
||||
scope_id,
|
||||
old_scope.context().name
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -635,8 +649,7 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
// 4. Compute the LIS of this list
|
||||
let mut lis_sequence = Vec::default();
|
||||
lis_sequence.reserve(new_index_to_old_index.len());
|
||||
let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len());
|
||||
|
||||
let mut predecessors = vec![0; new_index_to_old_index.len()];
|
||||
let mut starts = vec![0; new_index_to_old_index.len()];
|
||||
|
||||
@@ -49,17 +49,16 @@ impl ErrorBoundary {
|
||||
/// 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
|
||||
/// through question mark syntax. The throw method returns an option that evaluates 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.
|
||||
/// If the value is `Ok`, then throw returns the value, not aborting the rendering process.
|
||||
///
|
||||
/// 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 {
|
||||
/// #[component]
|
||||
/// fn App(cx: Scope, count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw(cx)?;
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
@@ -71,9 +70,9 @@ pub trait Throw<S = ()>: 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.
|
||||
/// Returns an option that evaluates 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.
|
||||
/// If the value is `Ok`, then throw returns the value, not aborting the rendering process.
|
||||
///
|
||||
/// The call stack is saved for this component and provided to the error boundary
|
||||
///
|
||||
@@ -83,9 +82,8 @@ pub trait Throw<S = ()>: Sized {
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, count: String) -> Element {
|
||||
/// #[component]
|
||||
/// fn App(cx: Scope, count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw(cx)?;
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
@@ -95,9 +93,9 @@ pub trait Throw<S = ()>: Sized {
|
||||
/// ```
|
||||
fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
|
||||
|
||||
/// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
|
||||
/// Returns an option that evaluates 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.
|
||||
/// If the value is `Ok`, then throw returns the value, not aborting the rendering process.
|
||||
///
|
||||
/// The call stack is saved for this component and provided to the error boundary
|
||||
///
|
||||
@@ -107,9 +105,8 @@ pub trait Throw<S = ()>: Sized {
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, count: String) -> Element {
|
||||
/// #[component]
|
||||
/// fn App(cx: Scope, count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw(cx)?;
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
|
||||
@@ -118,7 +118,7 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx!{
|
||||
/// MyComponent { onclick: move |evt| log::info!("clicked") }
|
||||
/// MyComponent { onclick: move |evt| tracing::debug!("clicked") }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Props)]
|
||||
@@ -143,7 +143,7 @@ pub struct EventHandler<'bump, T = ()> {
|
||||
impl<T> Default for EventHandler<'_, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
origin: ScopeId(0),
|
||||
origin: ScopeId::ROOT,
|
||||
callback: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::nodes::RenderReturn;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::properties::*;
|
||||
pub use crate::runtime::{Runtime, RuntimeGuard};
|
||||
pub use crate::scheduler::*;
|
||||
pub use crate::scope_context::*;
|
||||
pub use crate::scopes::*;
|
||||
@@ -89,8 +90,8 @@ pub mod prelude {
|
||||
provide_context, provide_context_to_scope, provide_root_context, push_future,
|
||||
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
|
||||
AttributeType, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue,
|
||||
LazyNodes, MountedAttribute, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
|
||||
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
||||
LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState,
|
||||
Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ pub struct VNode<'a> {
|
||||
|
||||
/// 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: RefCell<Vec<ElementId>>,
|
||||
pub root_ids: RefCell<bumpalo::collections::Vec<'a, ElementId>>,
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
@@ -65,11 +65,11 @@ pub struct VNode<'a> {
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
/// Create a template with no nodes that will be skipped over during diffing
|
||||
pub fn empty() -> Element<'a> {
|
||||
pub fn empty(cx: &'a ScopeState) -> Element<'a> {
|
||||
Some(VNode {
|
||||
key: None,
|
||||
parent: None,
|
||||
root_ids: Default::default(),
|
||||
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
|
||||
dynamic_nodes: &[],
|
||||
dynamic_attrs: &[],
|
||||
template: Cell::new(Template {
|
||||
@@ -751,14 +751,14 @@ impl<'a> IntoDynNode<'a> for &Element<'a> {
|
||||
|
||||
impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
|
||||
DynamicNode::Fragment(cx.bump().alloc([cx.render(self).unwrap()]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoDynNode<'b> for &'a str {
|
||||
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
|
||||
DynamicNode::Text(VText {
|
||||
value: bumpalo::collections::String::from_str_in(self, cx.bump()).into_bump_str(),
|
||||
value: cx.bump().alloc_str(self),
|
||||
id: Default::default(),
|
||||
})
|
||||
}
|
||||
@@ -801,16 +801,16 @@ impl<'a> IntoTemplate<'a> for VNode<'a> {
|
||||
}
|
||||
}
|
||||
impl<'a> IntoTemplate<'a> for Element<'a> {
|
||||
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
match self {
|
||||
Some(val) => val.into_template(_cx),
|
||||
_ => VNode::empty().unwrap(),
|
||||
Some(val) => val.into_template(cx),
|
||||
_ => VNode::empty(cx).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.call(cx)
|
||||
cx.render(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,4 +82,5 @@ pub fn fc_to_builder<'a, T: Properties<'a> + 'a>(
|
||||
fn unsafe_props_fail() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("compile_tests/props_safety.rs");
|
||||
t.compile_fail("compile_tests/props_safety_temporary_values.rs");
|
||||
}
|
||||
@@ -41,7 +41,8 @@ where
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub(crate) struct Runtime {
|
||||
/// A global runtime that is shared across all scopes that provides the async runtime and context API
|
||||
pub struct Runtime {
|
||||
pub(crate) scope_contexts: RefCell<Vec<Option<ScopeContext>>>,
|
||||
pub(crate) scheduler: Rc<Scheduler>,
|
||||
|
||||
@@ -63,6 +64,11 @@ impl Runtime {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current runtime
|
||||
pub fn current() -> Option<Rc<Self>> {
|
||||
RUNTIMES.with(|stack| stack.borrow().last().cloned())
|
||||
}
|
||||
|
||||
/// Create a scope context. This slab is synchronized with the scope slab.
|
||||
pub(crate) fn create_context_at(&self, id: ScopeId, context: ScopeContext) {
|
||||
let mut contexts = self.scope_contexts.borrow_mut();
|
||||
@@ -77,14 +83,14 @@ impl Runtime {
|
||||
}
|
||||
|
||||
/// Get the current scope id
|
||||
pub fn current_scope_id(&self) -> Option<ScopeId> {
|
||||
pub(crate) fn current_scope_id(&self) -> Option<ScopeId> {
|
||||
self.scope_stack.borrow().last().copied()
|
||||
}
|
||||
|
||||
/// Get the context for any scope given its ID
|
||||
///
|
||||
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
|
||||
pub fn get_context(&self, id: ScopeId) -> Option<Ref<'_, ScopeContext>> {
|
||||
pub(crate) fn get_context(&self, id: ScopeId) -> Option<Ref<'_, ScopeContext>> {
|
||||
Ref::filter_map(self.scope_contexts.borrow(), |contexts| {
|
||||
contexts.get(id.0).and_then(|f| f.as_ref())
|
||||
})
|
||||
@@ -92,10 +98,42 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RuntimeGuard(Rc<Runtime>);
|
||||
/// A gaurd for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let virtual_dom = VirtualDom::new(app);
|
||||
/// }
|
||||
///
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// render!{ Component { runtime: Runtime::current().unwrap() } }
|
||||
/// }
|
||||
///
|
||||
/// // In a dynamic library
|
||||
/// #[derive(Props)]
|
||||
/// struct ComponentProps {
|
||||
/// runtime: std::rc::Rc<Runtime>,
|
||||
/// }
|
||||
///
|
||||
/// impl PartialEq for ComponentProps {
|
||||
/// fn eq(&self, _other: &Self) -> bool {
|
||||
/// true
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn Component(cx: Scope<ComponentProps>) -> Element {
|
||||
/// cx.use_hook(|| RuntimeGuard::new(cx.props.runtime.clone()));
|
||||
///
|
||||
/// render! { div {} }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct RuntimeGuard(Rc<Runtime>);
|
||||
|
||||
impl RuntimeGuard {
|
||||
pub(crate) fn new(runtime: Rc<Runtime>) -> Self {
|
||||
/// Create a new runtime guard that sets the current Dioxus runtime. The runtime will be reset when the guard is dropped
|
||||
pub fn new(runtime: Rc<Runtime>) -> Self {
|
||||
push_runtime(runtime.clone());
|
||||
Self(runtime)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ impl VirtualDom {
|
||||
let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref();
|
||||
let props: &dyn AnyProps = std::mem::transmute(props);
|
||||
|
||||
let _span = tracing::trace_span!("render", scope = %scope.context().name);
|
||||
props.render(scope).extend_lifetime()
|
||||
};
|
||||
|
||||
|
||||
@@ -107,20 +107,30 @@ impl ScopeContext {
|
||||
///
|
||||
/// Clones the state if it exists.
|
||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
tracing::trace!(
|
||||
"looking for context {} ({:?}) in {}",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>(),
|
||||
self.name
|
||||
);
|
||||
if let Some(this_ctx) = self.has_context() {
|
||||
return Some(this_ctx);
|
||||
}
|
||||
|
||||
let mut search_parent = self.parent_id;
|
||||
with_runtime(|runtime| {
|
||||
match with_runtime(|runtime: &crate::runtime::Runtime| {
|
||||
while let Some(parent_id) = search_parent {
|
||||
let parent = runtime.get_context(parent_id).unwrap();
|
||||
if let Some(shared) = parent
|
||||
.shared_contexts
|
||||
.borrow()
|
||||
.iter()
|
||||
.find_map(|any| any.downcast_ref::<T>())
|
||||
{
|
||||
tracing::trace!(
|
||||
"looking for context {} ({:?}) in {}",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>(),
|
||||
parent.name
|
||||
);
|
||||
if let Some(shared) = parent.shared_contexts.borrow().iter().find_map(|any| {
|
||||
tracing::trace!("found context {:?}", any.type_id());
|
||||
any.downcast_ref::<T>()
|
||||
}) {
|
||||
return Some(shared.clone());
|
||||
}
|
||||
search_parent = parent.parent_id;
|
||||
@@ -128,6 +138,17 @@ impl ScopeContext {
|
||||
None
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
Some(ctx) => Some(ctx),
|
||||
None => {
|
||||
tracing::trace!(
|
||||
"context {} ({:?}) not found",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
|
||||
@@ -152,6 +173,12 @@ impl ScopeContext {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
tracing::trace!(
|
||||
"providing context {} ({:?}) in {}",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>(),
|
||||
self.name
|
||||
);
|
||||
let mut contexts = self.shared_contexts.borrow_mut();
|
||||
|
||||
// If the context exists, swap it out for the new value
|
||||
@@ -179,7 +206,7 @@ impl ScopeContext {
|
||||
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
||||
with_runtime(|runtime| {
|
||||
runtime
|
||||
.get_context(ScopeId(0))
|
||||
.get_context(ScopeId::ROOT)
|
||||
.unwrap()
|
||||
.provide_context(context)
|
||||
})
|
||||
@@ -203,7 +230,7 @@ impl ScopeContext {
|
||||
/// This is good for tasks that need to be run after the component has been dropped.
|
||||
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||
// The root scope will never be unmounted so we can just add the task at the top of the app
|
||||
let id = self.tasks.spawn(ScopeId(0), fut);
|
||||
let id = self.tasks.spawn(ScopeId::ROOT, fut);
|
||||
|
||||
// wake up the scheduler if it is sleeping
|
||||
self.tasks
|
||||
|
||||
@@ -64,6 +64,21 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct ScopeId(pub usize);
|
||||
|
||||
impl ScopeId {
|
||||
/// The root ScopeId.
|
||||
///
|
||||
/// This scope will last for the entire duration of your app, making it convenient for long-lived state
|
||||
/// that is created dynamically somewhere down the component tree.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// use dioxus_signals::*;
|
||||
/// let my_persistent_state = Signal::new_in_scope(ScopeId::ROOT, String::new());
|
||||
/// ```
|
||||
pub const ROOT: ScopeId = ScopeId(0);
|
||||
}
|
||||
|
||||
/// A component's state separate from its props.
|
||||
///
|
||||
/// This struct exists to provide a common interface for all scopes without relying on generics.
|
||||
@@ -423,7 +438,9 @@ impl<'src> ScopeState {
|
||||
fn_name: &'static str,
|
||||
) -> DynamicNode<'src>
|
||||
where
|
||||
P: Properties<'child> + 'child,
|
||||
// The properties must be valid until the next bump frame
|
||||
P: Properties<'src> + 'src,
|
||||
// The current bump allocator frame must outlive the child's borrowed props
|
||||
'src: 'child,
|
||||
{
|
||||
let vcomp = VProps::new(component, P::memoize, props);
|
||||
|
||||
@@ -52,6 +52,7 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
||||
///
|
||||
/// static ROUTES: &str = "";
|
||||
///
|
||||
/// #[component]
|
||||
/// fn App(cx: Scope<AppProps>) -> Element {
|
||||
/// cx.render(rsx!(
|
||||
/// NavBar { routes: ROUTES }
|
||||
@@ -60,18 +61,19 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
||||
/// ))
|
||||
/// }
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// #[component]
|
||||
/// fn NavBar(cx: Scope, routes: &'static str) -> Element {
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Routes: {routes}" }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Footer(cx: Scope) -> Element {
|
||||
/// cx.render(rsx! { div { "Footer" } })
|
||||
/// }
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// #[component]
|
||||
/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
/// cx.render(rsx! {
|
||||
/// div { id: "title", children }
|
||||
@@ -122,13 +124,14 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
||||
///
|
||||
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
||||
/// ```rust, ignore
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// #[component]
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Hello World" }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(app);
|
||||
/// let dom = VirtualDom::new(App);
|
||||
///
|
||||
/// real_dom.apply(dom.rebuild());
|
||||
///
|
||||
@@ -270,7 +273,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());
|
||||
@@ -289,7 +292,7 @@ impl VirtualDom {
|
||||
///
|
||||
/// 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
|
||||
@@ -306,6 +309,7 @@ impl VirtualDom {
|
||||
pub fn mark_dirty(&mut self, id: ScopeId) {
|
||||
if let Some(scope) = self.get_scope(id) {
|
||||
let height = scope.height();
|
||||
tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.context().name);
|
||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||
}
|
||||
}
|
||||
@@ -602,10 +606,10 @@ impl VirtualDom {
|
||||
/// ```
|
||||
pub fn rebuild(&mut self) -> Mutations {
|
||||
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
|
||||
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,
|
||||
@@ -613,7 +617,7 @@ impl VirtualDom {
|
||||
}
|
||||
// If an error occurs, we should try to render the default error component and context where the error occured
|
||||
RenderReturn::Aborted(placeholder) => {
|
||||
log::info!("Ran into suspended or aborted scope during rebuild");
|
||||
tracing::debug!("Ran into suspended or aborted scope during rebuild");
|
||||
let id = self.next_null();
|
||||
placeholder.id.set(Some(id));
|
||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||
@@ -713,11 +717,16 @@ impl VirtualDom {
|
||||
fn finalize(&mut self) -> Mutations {
|
||||
std::mem::take(&mut self.mutations)
|
||||
}
|
||||
|
||||
/// Get the current runtime
|
||||
pub fn runtime(&self) -> Rc<Runtime> {
|
||||
self.runtime.clone()
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
[
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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::<i32>().unwrap(), 1);
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
_ = dom.render_immediate();
|
||||
assert_eq!(dom.base_scope().consume_context::<i32>().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!(
|
||||
|
||||
@@ -5,11 +5,12 @@ use dioxus_core::ElementId;
|
||||
/// Should push the text node onto the stack and modify it
|
||||
#[test]
|
||||
fn nested_passthru_creates() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
pass_thru {
|
||||
pass_thru {
|
||||
pass_thru {
|
||||
PassThru {
|
||||
PassThru {
|
||||
PassThru {
|
||||
div { "hi" }
|
||||
}
|
||||
}
|
||||
@@ -17,12 +18,12 @@ fn nested_passthru_creates() {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn pass_thru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
#[component]
|
||||
fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
cx.render(rsx!(children))
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let edits = dom.rebuild().santize();
|
||||
|
||||
assert_eq!(
|
||||
@@ -39,13 +40,14 @@ fn nested_passthru_creates() {
|
||||
/// Take note on how we don't spit out the template for child_comp since it's entirely dynamic
|
||||
#[test]
|
||||
fn nested_passthru_creates_add() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
child_comp {
|
||||
ChildComp {
|
||||
"1"
|
||||
child_comp {
|
||||
ChildComp {
|
||||
"2"
|
||||
child_comp {
|
||||
ChildComp {
|
||||
"3"
|
||||
div {
|
||||
"hi"
|
||||
@@ -56,12 +58,12 @@ fn nested_passthru_creates_add() {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn child_comp<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
#[component]
|
||||
fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
cx.render(rsx! { children })
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild().santize().edits,
|
||||
@@ -82,13 +84,14 @@ fn nested_passthru_creates_add() {
|
||||
/// note that the template is all dynamic roots - so it doesn't actually get cached as a template
|
||||
#[test]
|
||||
fn dynamic_node_as_root() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
let a = 123;
|
||||
let b = 456;
|
||||
cx.render(rsx! { "{a}" "{b}" })
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let edits = dom.rebuild().santize();
|
||||
|
||||
// Since the roots were all dynamic, they should not cause any template muations
|
||||
|
||||
@@ -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,
|
||||
[
|
||||
|
||||
@@ -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,
|
||||
[
|
||||
|
||||
@@ -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,
|
||||
[
|
||||
|
||||
@@ -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,
|
||||
[
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
@@ -277,7 +277,7 @@ fn create_random_element(cx: Scope<DepthProps>) -> 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()
|
||||
|
||||
@@ -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] },
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn miri_rollover() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
_ = dom.rebuild();
|
||||
|
||||
@@ -15,7 +15,8 @@ fn miri_rollover() {
|
||||
}
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
let mut idx = use_state(cx, || 0);
|
||||
let onhover = |_| println!("go!");
|
||||
|
||||
@@ -31,15 +32,15 @@ fn app(cx: Scope) -> Element {
|
||||
button { onclick: move |_| idx -= 1, "-" }
|
||||
ul {
|
||||
(0..**idx).map(|i| rsx! {
|
||||
child_example { i: i, onhover: onhover }
|
||||
ChildExample { i: i, onhover: onhover }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn child_example<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
|
||||
#[component]
|
||||
fn ChildExample<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
|
||||
cx.render(rsx! {
|
||||
li {
|
||||
onmouseover: move |e| onhover.call(e),
|
||||
|
||||
@@ -2,22 +2,24 @@ use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn app_drops() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {}
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
_ = dom.rebuild();
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
_ = dom.render_immediate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hooks_drop() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.use_hook(|| String::from("asd"));
|
||||
cx.use_hook(|| String::from("asd"));
|
||||
cx.use_hook(|| String::from("asd"));
|
||||
@@ -28,26 +30,28 @@ fn hooks_drop() {
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
_ = dom.rebuild();
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
_ = dom.render_immediate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contexts_drop() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.provide_context(String::from("asd"));
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
child_comp {}
|
||||
ChildComp {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn child_comp(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn ChildComp(cx: Scope) -> Element {
|
||||
let el = cx.consume_context::<String>().unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
@@ -55,16 +59,17 @@ fn contexts_drop() {
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
_ = dom.rebuild();
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
_ = dom.render_immediate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tasks_drop() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.spawn(async {
|
||||
// tokio::time::sleep(std::time::Duration::from_millis(100000)).await;
|
||||
});
|
||||
@@ -74,10 +79,10 @@ fn tasks_drop() {
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
_ = dom.rebuild();
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
_ = dom.render_immediate();
|
||||
}
|
||||
|
||||
@@ -91,37 +96,38 @@ fn root_props_drop() {
|
||||
);
|
||||
|
||||
_ = dom.rebuild();
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
_ = dom.render_immediate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diffing_drops_old() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
match cx.generation() % 2 {
|
||||
0 => rsx!( child_comp1 { name: "asdasd".to_string() }),
|
||||
1 => rsx!( child_comp2 { name: "asdasd".to_string() }),
|
||||
0 => rsx!( ChildComp1 { name: "asdasd".to_string() }),
|
||||
1 => rsx!( ChildComp2 { name: "asdasd".to_string() }),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn child_comp1(cx: Scope, name: String) -> Element {
|
||||
#[component]
|
||||
fn ChildComp1(cx: Scope, name: String) -> Element {
|
||||
cx.render(rsx! { "Hello {name}" })
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn child_comp2(cx: Scope, name: String) -> Element {
|
||||
#[component]
|
||||
fn ChildComp2(cx: Scope, name: String) -> Element {
|
||||
cx.render(rsx! { "Goodbye {name}" })
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
_ = dom.rebuild();
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
dom.mark_dirty(ScopeId::ROOT);
|
||||
|
||||
_ = dom.render_immediate();
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user