Merge branch 'master' into pr/atty303/1349

This commit is contained in:
Evan Almloff committed 2023-09-20 16:19:23 -05:00
commit 7033276c34
444 files changed
+2868 -14315

No files matched your search

+1 -1
View File
@@ -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()
}
}
+2 -2
View File
@@ -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
+6 -2
View File
@@ -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 -4
View File
@@ -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()];
+12 -15
View File
@@ -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! {
+2 -2
View File
@@ -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(),
}
}
+3 -2
View File
@@ -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,
};
}
+9 -9
View File
@@ -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()
}
}
+1
View File
@@ -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");
}
+43 -5
View File
@@ -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)
}
+1
View File
@@ -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()
};
+36 -9
View File
@@ -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
+18 -1
View File
@@ -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);
+19 -10
View File
@@ -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);
}
}