mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
wip: so much suspense code
This commit is contained in:
parent
d2ce57ba6e
commit
aec1b326ba
9 changed files with 305 additions and 37 deletions
|
@ -1,15 +1,15 @@
|
|||
use std::task::Context;
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures_util::task::noop_waker_ref;
|
||||
use futures_util::{pin_mut, Future};
|
||||
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::factory::{FiberLeaf, RenderReturn};
|
||||
use crate::mutations::Mutation;
|
||||
use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, TemplateNode};
|
||||
use crate::suspense::LeafLocation;
|
||||
use crate::virtualdom::VirtualDom;
|
||||
use crate::{AttributeValue, Element, ElementId, TemplateAttribute};
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use futures_util::Future;
|
||||
|
||||
impl VirtualDom {
|
||||
/// Create this template and write its mutations
|
||||
|
@ -177,7 +177,9 @@ impl VirtualDom {
|
|||
1
|
||||
}
|
||||
|
||||
DynamicNode::Component { props, .. } => {
|
||||
DynamicNode::Component {
|
||||
props, placeholder, ..
|
||||
} => {
|
||||
let id = self.new_scope(unsafe { std::mem::transmute(props.get()) });
|
||||
|
||||
let render_ret = self.run_scope(id);
|
||||
|
@ -192,39 +194,52 @@ impl VirtualDom {
|
|||
self.scope_stack.pop();
|
||||
created
|
||||
}
|
||||
RenderReturn::Sync(None) => todo!("nodes that return nothing"),
|
||||
|
||||
// whenever the future is polled later, we'll revisit it
|
||||
// For now, just set the placeholder
|
||||
RenderReturn::Sync(None) => {
|
||||
let new_id = self.next_element(template);
|
||||
placeholder.set(Some(new_id));
|
||||
mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
});
|
||||
0
|
||||
}
|
||||
RenderReturn::Async(fut) => {
|
||||
use futures_util::FutureExt;
|
||||
let new_id = self.next_element(template);
|
||||
|
||||
// Poll the suspense node once to see if we can get any nodes from it
|
||||
let mut cx = Context::from_waker(&noop_waker_ref());
|
||||
let res = fut.poll_unpin(&mut cx);
|
||||
// move up the tree looking for the first suspense boundary
|
||||
// our current component can not be a suspense boundary, so we skip it
|
||||
for scope_id in self.scope_stack.iter().rev().skip(1) {
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
if let Some(fiber) = &mut scope.suspense_boundary {
|
||||
// save the fiber leaf onto the fiber itself
|
||||
let detached: &mut FiberLeaf<'static> =
|
||||
unsafe { std::mem::transmute(fut) };
|
||||
|
||||
match res {
|
||||
std::task::Poll::Ready(Some(val)) => {
|
||||
let scope = self.get_scope(id).unwrap();
|
||||
let ready = &*scope.bump().alloc(val);
|
||||
let ready = unsafe { std::mem::transmute(ready) };
|
||||
// And save the fiber leaf using the placeholder node
|
||||
// this way, when we resume the fiber, we just need to "pick up placeholder"
|
||||
fiber.futures.insert(
|
||||
LeafLocation {
|
||||
element: new_id,
|
||||
scope: *scope_id,
|
||||
},
|
||||
detached,
|
||||
);
|
||||
|
||||
self.scope_stack.push(id);
|
||||
let created = self.create(mutations, ready);
|
||||
self.scope_stack.pop();
|
||||
created
|
||||
}
|
||||
std::task::Poll::Ready(None) => {
|
||||
todo!("Pending suspense")
|
||||
}
|
||||
std::task::Poll::Pending => {
|
||||
let new_id = self.next_element(template);
|
||||
// id.set(new_id);
|
||||
mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
});
|
||||
|
||||
0
|
||||
self.suspended_scopes.insert(*scope_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
placeholder.set(Some(new_id));
|
||||
mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
});
|
||||
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ impl ScopeState {
|
|||
name: fn_name,
|
||||
is_static: P::IS_STATIC,
|
||||
props: Cell::new(detached_dyn),
|
||||
placeholder: Cell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +136,8 @@ pub enum RenderReturn<'a> {
|
|||
Async(Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>),
|
||||
}
|
||||
|
||||
pub type FiberLeaf<'a> = Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>;
|
||||
|
||||
pub trait IntoVnode<'a, A = ()> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ mod nodes;
|
|||
mod properties;
|
||||
mod scope_arena;
|
||||
mod scopes;
|
||||
// mod subtree;
|
||||
mod suspense;
|
||||
mod virtualdom;
|
||||
|
||||
pub(crate) mod innerlude {
|
||||
|
|
|
@ -79,6 +79,7 @@ pub enum DynamicNode<'a> {
|
|||
name: &'static str,
|
||||
is_static: bool,
|
||||
props: Cell<*mut dyn AnyProps<'a>>,
|
||||
placeholder: Cell<Option<ElementId>>,
|
||||
},
|
||||
|
||||
// Comes in with string interpolation or from format_args, include_str, etc
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
use std::task::Context;
|
||||
|
||||
use futures_util::task::noop_waker_ref;
|
||||
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
|
@ -23,6 +27,7 @@ impl VirtualDom {
|
|||
height,
|
||||
props,
|
||||
tasks: self.pending_futures.clone(),
|
||||
suspense_boundary: None,
|
||||
node_arena_1: BumpFrame::new(50),
|
||||
node_arena_2: BumpFrame::new(50),
|
||||
render_cnt: Default::default(),
|
||||
|
@ -53,7 +58,7 @@ impl VirtualDom {
|
|||
let scope = &mut self.scopes[id.0];
|
||||
scope.hook_idx.set(0);
|
||||
|
||||
let res = {
|
||||
let mut new_nodes = {
|
||||
let props = unsafe { &mut *scope.props };
|
||||
let props: &mut dyn AnyProps = unsafe { std::mem::transmute(props) };
|
||||
let res: RenderReturn = props.render(scope);
|
||||
|
@ -61,6 +66,20 @@ impl VirtualDom {
|
|||
res
|
||||
};
|
||||
|
||||
// immediately resolve futures that can be resolved immediatelys
|
||||
let res = match &mut new_nodes {
|
||||
RenderReturn::Sync(_) => new_nodes,
|
||||
RenderReturn::Async(fut) => {
|
||||
use futures_util::FutureExt;
|
||||
let mut cx = Context::from_waker(&noop_waker_ref());
|
||||
|
||||
match fut.poll_unpin(&mut cx) {
|
||||
std::task::Poll::Ready(nodes) => RenderReturn::Sync(nodes),
|
||||
std::task::Poll::Pending => new_nodes,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let frame = match scope.render_cnt % 2 {
|
||||
0 => &mut scope.node_arena_1,
|
||||
1 => &mut scope.node_arena_2,
|
||||
|
|
|
@ -11,7 +11,7 @@ use futures_util::Future;
|
|||
|
||||
use crate::{
|
||||
any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, future_container::FutureQueue,
|
||||
innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, TaskId,
|
||||
innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, suspense::Fiber, TaskId,
|
||||
};
|
||||
|
||||
pub struct Scope<'a, T = ()> {
|
||||
|
@ -66,6 +66,8 @@ pub struct ScopeState {
|
|||
|
||||
pub tasks: FutureQueue,
|
||||
|
||||
pub suspense_boundary: Option<Fiber<'static>>,
|
||||
|
||||
pub props: *mut dyn AnyProps<'static>,
|
||||
}
|
||||
|
||||
|
|
58
packages/core/src/subtree.rs
Normal file
58
packages/core/src/subtree.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
This is a WIP module
|
||||
|
||||
Subtrees allow the virtualdom to split up the mutation stream into smaller chunks which can be directed to different parts of the dom.
|
||||
It's core to implementing multiwindow desktop support, portals, and alternative inline renderers like react-three-fiber.
|
||||
|
||||
The primary idea is to give each renderer a linear element tree managed by Dioxus to maximize performance and minimize memory usage.
|
||||
This can't be done if two renderers need to share the same native tree.
|
||||
With subtrees, we have an entirely different slab of elements
|
||||
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use slab::Slab;
|
||||
|
||||
use crate::{ElementPath, ScopeId};
|
||||
|
||||
/// A collection of elements confined to a single scope under a chunk of the tree
|
||||
///
|
||||
/// All elements in this collection are guaranteed to be in the same scope and share the same numbering
|
||||
///
|
||||
/// This unit can be multithreaded
|
||||
/// Whenever multiple subtrees are present, we can perform **parallel diffing**
|
||||
pub struct Subtree {
|
||||
id: usize,
|
||||
namespace: Cow<'static, str>,
|
||||
root: ScopeId,
|
||||
elements: Slab<ElementPath>,
|
||||
}
|
||||
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// // whenever a user connects, they get a new connection
|
||||
// // this requires the virtualdom to be Send + Sync
|
||||
// rsx! {
|
||||
// ClientForEach(|req| rsx!{
|
||||
// Route {}
|
||||
// Route {}
|
||||
// Route {}
|
||||
// Route {}
|
||||
// Route {}
|
||||
// Route {}
|
||||
// })
|
||||
|
||||
// // windows.map(|w| {
|
||||
// // WebviewWindow {}
|
||||
// // WebviewWindow {}
|
||||
// // WebviewWindow {}
|
||||
// // WebviewWindow {}
|
||||
// // })
|
||||
|
||||
// // if show_settings {
|
||||
// // WebviewWindow {
|
||||
// // Settings {}
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
// }
|
149
packages/core/src/suspense.rs
Normal file
149
packages/core/src/suspense.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
//! Container for polling suspended nodes
|
||||
//!
|
||||
//! Whenever a future is returns a value, we walk the tree upwards and check if any of the parents are suspended.
|
||||
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use futures_util::Future;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
future::poll_fn,
|
||||
pin::Pin,
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
factory::FiberLeaf, innerlude::Mutation, Element, ElementId, ScopeId, VNode, VirtualDom,
|
||||
};
|
||||
|
||||
impl VirtualDom {
|
||||
// todo: lots of hammering lifetimes here...
|
||||
async fn wait_for_suspense(&mut self) {
|
||||
let res = poll_fn(|cx| {
|
||||
let all_suspended_complete = true;
|
||||
|
||||
let suspended_scopes: Vec<_> = self.suspended_scopes.iter().copied().collect();
|
||||
|
||||
for scope in suspended_scopes {
|
||||
let mut fiber = self.scopes[scope.0]
|
||||
.suspense_boundary
|
||||
.as_mut()
|
||||
.expect(" A fiber to be present if the scope is suspended");
|
||||
|
||||
let mut fiber: &mut Fiber = unsafe { std::mem::transmute(fiber) };
|
||||
|
||||
let mutations = &mut fiber.mutations;
|
||||
let mutations: &mut Vec<Mutation> = unsafe { std::mem::transmute(mutations) };
|
||||
|
||||
let keys = fiber.futures.keys().copied().collect::<Vec<_>>();
|
||||
for loc in keys {
|
||||
let fut = *fiber.futures.get_mut(&loc).unwrap();
|
||||
let fut = unsafe { &mut *fut };
|
||||
let fut: &mut FiberLeaf<'_> = unsafe { std::mem::transmute(fut) };
|
||||
|
||||
use futures_util::FutureExt;
|
||||
|
||||
match fut.poll_unpin(cx) {
|
||||
Poll::Ready(nodes) => {
|
||||
// remove the future from the fiber
|
||||
fiber.futures.remove(&loc).unwrap();
|
||||
|
||||
// set the original location to the new nodes
|
||||
// todo!("set the original location to the new nodes");
|
||||
let template = nodes.unwrap();
|
||||
|
||||
let scope = &self.scopes[scope.0];
|
||||
let template = scope.bump().alloc(template);
|
||||
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||
|
||||
// now create the template
|
||||
self.create(mutations, template);
|
||||
}
|
||||
Poll::Pending => todo!("still working huh"),
|
||||
}
|
||||
}
|
||||
|
||||
// let mut fiber = Pin::new(&mut fiber);
|
||||
|
||||
// let mut scope = scope;
|
||||
// let mut vnode = self.scopes[scope.0].vnode.take().unwrap();
|
||||
|
||||
// let mut vnode = Pin::new(&mut vnode);
|
||||
|
||||
// let mut vnode = poll_fn(|cx| {
|
||||
// let mut vnode = Pin::new(&mut vnode);
|
||||
// let mut fiber = Pin::new(&mut fiber);
|
||||
|
||||
// let res = vnode.as_mut().poll(cx);
|
||||
|
||||
// if let Poll::Ready(res) = res {
|
||||
// Poll::Ready(res)
|
||||
// } else {
|
||||
// Poll::Pending
|
||||
// }
|
||||
// })
|
||||
// .await;
|
||||
|
||||
// self.scopes[scope.0].vnode = Some(vnode);
|
||||
// self.scopes[scope.0].suspense_boundary = Some(fiber);
|
||||
}
|
||||
|
||||
match all_suspended_complete {
|
||||
true => Poll::Ready(()),
|
||||
false => Poll::Pending,
|
||||
}
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// impl SuspenseGenerator {
|
||||
// async fn wait_for_work(&mut self) {
|
||||
// use futures_util::future::{select, Either};
|
||||
|
||||
// // let scopes = &mut self.scopes;
|
||||
// let suspense_status = poll_fn(|cx| {
|
||||
// // let mut tasks = scopes.tasks.tasks.borrow_mut();
|
||||
// // tasks.retain(|_, task| task.as_mut().poll(cx).is_pending());
|
||||
|
||||
// match true {
|
||||
// // match tasks.is_empty() {
|
||||
// true => Poll::Ready(()),
|
||||
// false => Poll::Pending,
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Suspense {
|
||||
// // maybe generate futures
|
||||
// // only render when all the futures are ready
|
||||
// // }
|
||||
|
||||
// /*
|
||||
// div {
|
||||
// as1 {}
|
||||
// as2 {}
|
||||
// as3 {}
|
||||
// }
|
||||
// */
|
||||
// // match select(task_poll, self.channel.1.next()).await {
|
||||
// // Either::Left((_, _)) => {}
|
||||
// // Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Fiber<'a> {
|
||||
// The work-in progress of this suspended tree
|
||||
pub mutations: Vec<Mutation<'a>>,
|
||||
|
||||
// All the pending futures (DFS)
|
||||
pub futures:
|
||||
HashMap<LeafLocation, *mut Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
|
||||
pub struct LeafLocation {
|
||||
pub scope: ScopeId,
|
||||
pub element: ElementId,
|
||||
}
|
|
@ -7,6 +7,7 @@ use crate::future_container::FutureQueue;
|
|||
use crate::innerlude::SchedulerMsg;
|
||||
use crate::mutations::Mutation;
|
||||
use crate::nodes::{Template, TemplateId};
|
||||
use crate::suspense::Fiber;
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
|
@ -26,6 +27,7 @@ pub struct VirtualDom {
|
|||
pub(crate) pending_futures: FutureQueue,
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
pub(crate) suspended_scopes: BTreeSet<ScopeId>,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
|
@ -40,6 +42,7 @@ impl VirtualDom {
|
|||
element_stack: vec![ElementId(0)],
|
||||
dirty_scopes: BTreeSet::new(),
|
||||
pending_futures: FutureQueue::new(sender.clone()),
|
||||
suspended_scopes: BTreeSet::new(),
|
||||
receiver,
|
||||
sender,
|
||||
};
|
||||
|
@ -49,6 +52,9 @@ impl VirtualDom {
|
|||
|
||||
let root = res.new_scope(props);
|
||||
|
||||
// the root component is always a suspense boundary for any async children
|
||||
res.scopes[root.0].suspense_boundary = Some(Fiber::default());
|
||||
|
||||
assert_eq!(root, ScopeId(0));
|
||||
|
||||
res
|
||||
|
@ -56,8 +62,6 @@ impl VirtualDom {
|
|||
|
||||
/// Render the virtualdom, without processing any suspense.
|
||||
pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
||||
// let root = self.scopes.get(0).unwrap();
|
||||
|
||||
let root_node: &RenderReturn = self.run_scope(ScopeId(0));
|
||||
let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) };
|
||||
match root_node {
|
||||
|
@ -104,3 +108,18 @@ impl Drop for VirtualDom {
|
|||
// self.drop_scope(ScopeId(0));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
div {
|
||||
Window {}
|
||||
Window {}
|
||||
}
|
||||
|
||||
edits -> Vec<Mutation>
|
||||
|
||||
Subtree {
|
||||
id: 0,
|
||||
namespace: "react-three-fiber",
|
||||
edits: []
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue