Merge pull request #671 from DioxusLabs/jk/suspense-rollover

feat: complete the rollover of aborted and async render states
This commit is contained in:
Jon Kelley authored and GitHub committed 2022-12-25 23:25:16 -05:00
commit 641488e825
27 files changed
+506 -143

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
button { "hello, desktop!" }
})
}
+14 -7
View File
@@ -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<P> = 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<P> = 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(),
}
}
}
+2 -2
View File
@@ -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)
}
}
+29 -29
View File
@@ -433,9 +433,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(),
}
}
@@ -487,40 +487,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<dyn AnyProps> = 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<dyn AnyProps> = 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,
@@ -575,15 +573,17 @@ impl<'b> VirtualDom {
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 {
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
+59 -26
View File
@@ -30,29 +30,44 @@ 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
@@ -129,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."),
};
@@ -208,8 +223,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
@@ -691,7 +710,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!(),
}
}
@@ -719,7 +739,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<Item = &'b VNode<'b>>,
) {
let m = self.create_children(r);
let id = l.id.get().unwrap();
self.mutations.push(Mutation::ReplaceWith { id, m });
@@ -816,8 +840,11 @@ impl<'b> VirtualDom {
.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(..=1) = path_len {
continue;
if let Some(len) = path_len {
if (..=1).contains(&len) {
continue;
}
}
let next_id = attr.mounted_element.get();
@@ -877,14 +904,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();
@@ -910,7 +943,7 @@ impl<'b> VirtualDom {
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"),
}
}
@@ -926,7 +959,7 @@ impl<'b> VirtualDom {
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"),
}
}
+191 -7
View File
@@ -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<Option<ScopeId>>,
id: ScopeId,
error: RefCell<Option<CapturedError>>,
_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<dyn Debug + 'static>,
/// The scope that threw the error
pub scope: ScopeId,
}
impl CapturedError {
/// Downcast the error type into a concrete error type
pub fn downcast<T: 'static>(&self) -> Option<&T> {
if TypeId::of::<T>() == 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<dyn Debug + 'static>) {
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<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.
///
/// 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<Self::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_with<D: Debug + 'static>(
self,
cx: &ScopeState,
e: impl FnOnce() -> D,
) -> Option<Self::Out>;
}
/// We call clone on any errors that can be owned out of a reference
impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
type Out = &'a T;
fn throw(self, cx: &ScopeState) -> Option<Self::Out> {
match self {
Ok(t) => Some(t),
Err(e) => {
cx.throw(e.to_owned());
None
}
}
}
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
err: impl FnOnce() -> D,
) -> Option<Self::Out> {
match self {
Ok(t) => Some(t),
Err(_e) => {
cx.throw(err());
None
}
}
}
}
/// Or just throw errors we know about
impl<T, E: Debug + 'static> Throw for Result<T, E> {
type Out = T;
fn throw(self, cx: &ScopeState) -> Option<T> {
match self {
Ok(t) => Some(t),
Err(e) => {
cx.throw(e);
None
}
}
}
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
error: impl FnOnce() -> D,
) -> Option<Self::Out> {
self.ok().or_else(|| {
cx.throw(error());
None
})
}
}
/// Or just throw errors we know about
impl<T> Throw for Option<T> {
type Out = T;
fn throw(self, cx: &ScopeState) -> Option<T> {
self.or_else(|| {
cx.throw("None error.");
None
})
}
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
error: impl FnOnce() -> D,
) -> Option<Self::Out> {
self.or_else(|| {
cx.throw(error());
None
})
}
}
+5 -5
View File
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
fc_to_builder, Attribute, AttributeValue, 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,
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
@@ -83,7 +83,7 @@ pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
VNode, VirtualDom,
Throw, VNode, VirtualDom,
};
}
+19 -4
View File
@@ -21,10 +21,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<Output = Element<'a>> + 'a>),
Pending(BumpBox<'a, dyn Future<Output = Element<'a>> + '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
@@ -574,7 +586,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(),
}
}
}
@@ -586,7 +601,7 @@ where
{
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
RenderReturn::Pending(unsafe { BumpBox::from_raw(f) })
}
}
+4 -4
View File
@@ -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<SchedulerMsg>,
/// Tasks created with cx.spawn
pub tasks: RefCell<Slab<Rc<LocalTask>>>,
pub tasks: RefCell<Slab<Arc<LocalTask>>>,
/// Async components
pub leaves: RefCell<Slab<Rc<SuspenseLeaf>>>,
pub leaves: RefCell<Slab<Arc<SuspenseLeaf>>>,
}
impl Scheduler {
+4 -4
View File
@@ -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<Output = Element<'static>>,
}
impl RcWake for SuspenseLeaf {
fn wake_by_ref(arc_self: &Rc<Self>) {
impl ArcWake for SuspenseLeaf {
fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.notified.set(true);
_ = arc_self
.tx
+6 -5
View File
@@ -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<Self>) {
impl ArcWake for LocalTask {
fn wake_by_ref(arc_self: &Arc<Self>) {
_ = arc_self
.tx
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
+7 -4
View File
@@ -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) };
+13 -12
View File
@@ -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<Self>) -> Waker {
unsafe fn rc_vtable<T: RcWake>() -> &'static RawWakerVTable {
fn waker(self: &Arc<Self>) -> Waker {
unsafe fn rc_vtable<T: ArcWake>() -> &'static RawWakerVTable {
&RawWakerVTable::new(
|data| {
let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
let arc = mem::ManuallyDrop::new(Arc::<T>::from_raw(data.cast::<T>()));
let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
RawWaker::new(data, rc_vtable::<T>())
},
|data| Rc::from_raw(data.cast::<T>()).wake(),
|data| Arc::from_raw(data.cast::<T>()).wake(),
|data| {
let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
RcWake::wake_by_ref(&arc);
let arc = mem::ManuallyDrop::new(Arc::<T>::from_raw(data.cast::<T>()));
ArcWake::wake_by_ref(&arc);
},
|data| drop(Rc::<T>::from_raw(data.cast::<T>())),
|data| drop(Arc::<T>::from_raw(data.cast::<T>())),
)
}
unsafe {
Waker::from_raw(RawWaker::new(
Rc::into_raw(self.clone()).cast(),
Arc::into_raw(self.clone()).cast(),
rc_vtable::<Self>(),
))
}
}
fn wake_by_ref(arc_self: &Rc<Self>);
fn wake_by_ref(arc_self: &Arc<Self>);
fn wake(self: Rc<Self>) {
fn wake(self: Arc<Self>) {
Self::wake_by_ref(&self)
}
}
+11 -6
View File
@@ -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() }
}
}
+15 -2
View File
@@ -4,7 +4,7 @@ use crate::{
arena::ElementId,
bump_frame::BumpFrame,
innerlude::{DynamicNode, EventHandler, VComponent, VText},
innerlude::{Scheduler, SchedulerMsg},
innerlude::{ErrorBoundary, 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,
@@ -509,6 +509,19 @@ impl<'src> ScopeState {
AttributeValue::Listener(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::<Rc<ErrorBoundary>>() {
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`.
+4 -4
View File
@@ -466,7 +466,7 @@ impl VirtualDom {
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::Sync(Some(sync))) = scope.try_root_node() {
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
{
@@ -503,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),
@@ -511,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()
+58
View File
@@ -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
}
+8 -1
View File
@@ -55,6 +55,9 @@ pub struct LinkProps<'a> {
/// Pass children into the `<a>` element
pub children: Element<'a>,
/// The onclick event handler.
pub onclick: Option<EventHandler<'a, MouseEvent>>,
}
/// A component that renders a link to a route.
@@ -119,7 +122,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
title: format_args!("{}", title.unwrap_or("")),
prevent_default: "{prevent_default}",
target: format_args!("{}", if * new_tab { "_blank" } else { "" }),
onclick: move |_| {
onclick: move |evt| {
log::trace!("Clicked link to {}", to);
if !outerlink {
@@ -138,6 +141,10 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
);
}
}
if let Some(onclick) = cx.props.onclick.as_ref() {
onclick.call(evt);
}
},
children
}
+1 -1
View File
@@ -47,5 +47,5 @@ pub fn Redirect<'a>(cx: Scope<'a, RedirectProps<'a>>) -> Element {
router.replace_route(cx.props.to, None, None);
}
None
cx.render(rsx!(()))
}
+1 -1
View File
@@ -52,6 +52,6 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
cx.render(rsx!(&cx.props.children))
} else {
log::debug!("Route should *not* render: {:?}", cx.scope_id());
None
cx.render(rsx!(()))
}
}
+17
View File
@@ -107,6 +107,23 @@ impl UseRoute {
{
self.segment(name).map(|value| value.parse::<T>())
}
/// Get the named parameter from the path, as defined in your router. The
/// value will be parsed into the type specified by `T` by calling
/// `value.parse::<T>()`. This method returns `None` if the named
/// parameter does not exist in the current path.
pub fn parse_segment_or_404<T>(&self, name: &str) -> Option<T>
where
T: FromStr,
{
match self.parse_segment(name) {
Some(Ok(val)) => Some(val),
_ => {
// todo: throw a 404
None
}
}
}
}
// The entire purpose of this struct is to unubscribe this component when it is unmounted.
+9
View File
@@ -30,3 +30,12 @@ mod service;
pub use routecontext::*;
pub use service::*;
/// An error specific to the Router
#[derive(Debug)]
pub enum Error {
/// The route was not found while trying to navigate to it.
///
/// This will force the router to redirect to the 404 page.
NotFound,
}
+2 -2
View File
@@ -51,7 +51,7 @@ impl Renderer {
) -> std::fmt::Result {
// We should never ever run into async or errored nodes in SSR
// Error boundaries and suspense boundaries will convert these to sync
if let RenderReturn::Sync(Some(node)) = dom.get_scope(scope).unwrap().root_node() {
if let RenderReturn::Ready(node) = dom.get_scope(scope).unwrap().root_node() {
self.render_template(buf, dom, node)?
};
@@ -89,7 +89,7 @@ impl Renderer {
let scope = dom.get_scope(id).unwrap();
let node = scope.root_node();
match node {
RenderReturn::Sync(Some(node)) => {
RenderReturn::Ready(node) => {
self.render_template(buf, dom, node)?
}
_ => todo!(
+1 -1
View File
@@ -17,7 +17,7 @@ fn app(cx: Scope) -> Element {
width: "100%",
background_color: "hsl({hue}, 70%, {brightness}%)",
onmousemove: move |evt| {
if let RenderReturn::Sync(Some(node)) = cx.root_node() {
if let RenderReturn::Ready(node) = cx.root_node() {
if let Some(id) = node.root_ids.get(0){
let node = tui_query.get(id);
let Size{width, height} = node.size().unwrap();
+1 -1
View File
@@ -10,7 +10,7 @@ use dioxus_core::{ElementId, RenderReturn, Scope};
pub use input::*;
pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<ElementId> {
if let RenderReturn::Sync(Some(sync)) = cx.root_node() {
if let RenderReturn::Ready(sync) = cx.root_node() {
sync.root_ids.get(0)
} else {
None
+4 -6
View File
@@ -121,12 +121,10 @@ impl WebsysDom {
} = attr
{
match namespace {
Some(ns) if *ns == "style" => el
.dyn_ref::<HtmlElement>()
.unwrap()
.style()
.set_property(name, value)
.unwrap(),
Some(ns) if *ns == "style" => {
el.dyn_ref::<HtmlElement>()
.map(|f| f.style().set_property(name, value));
}
Some(ns) => el.set_attribute_ns(Some(ns), name, value).unwrap(),
None => el.set_attribute(name, value).unwrap(),
}
+10 -9
View File
@@ -18,15 +18,15 @@ pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
#[cfg(debug_assertions)]
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
use core::panic;
use std::convert::TryInto;
use serde::Deserialize;
let window = web_sys::window().unwrap();
let protocol = if window.location().protocol().unwrap() == "https:" {
"wss:"
} else {
"ws:"
let protocol = match window.location().protocol().unwrap() {
prot if prot == "https:" => "wss:",
_ => "ws:",
};
let url = format!(
@@ -43,10 +43,11 @@ pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
let text: Result<String, _> = text.try_into();
if let Ok(string) = text {
match serde_json::from_str(Box::leak(string.into_boxed_str())) {
Ok(template) => _ = tx.unbounded_send(template),
Err(e) => panic!("Failed to parse template: {}", e),
}
let val = serde_json::from_str::<serde_json::Value>(&string).unwrap();
// leak the value
let val: &'static serde_json::Value = Box::leak(Box::new(val));
let template: Template<'_> = Template::deserialize(val).unwrap();
tx.unbounded_send(template).unwrap();
}
}
}) as Box<dyn FnMut(MessageEvent)>);