wip: so much suspense code

This commit is contained in:
Jonathan Kelley 2022-11-03 22:30:26 -07:00
parent d2ce57ba6e
commit aec1b326ba
9 changed files with 305 additions and 37 deletions

View file

@ -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
}
}
}

View file

@ -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>;
}

View file

@ -14,6 +14,8 @@ mod nodes;
mod properties;
mod scope_arena;
mod scopes;
// mod subtree;
mod suspense;
mod virtualdom;
pub(crate) mod innerlude {

View file

@ -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

View file

@ -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,

View file

@ -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>,
}

View 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 {}
// // }
// // }
// }
// }

View 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,
}

View file

@ -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: []
}
*/