feat: split apart template mutations

This commit is contained in:
Jonathan Kelley 2022-11-08 19:39:37 -08:00
parent 203935834d
commit fc9fe6e560
21 changed files with 432 additions and 223 deletions

View file

@ -4,6 +4,7 @@ use futures_util::Future;
use crate::{
factory::{ComponentReturn, RenderReturn},
innerlude::Scoped,
scopes::{Scope, ScopeState},
Element,
};
@ -69,10 +70,10 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> AnyProps<'a> for VComponentProps<'a, P
// Make sure the scope ptr is not null
// self.props.state.set(scope);
let scope = Scope {
let scope = cx.bump().alloc(Scoped {
props: unsafe { &*self.props },
scope: cx,
};
});
// Call the render function directly
(self.render_fn)(scope).as_return(cx)

View file

@ -1,4 +1,4 @@
use crate::{nodes::VNode, virtualdom::VirtualDom};
use crate::{nodes::VNode, virtual_dom::VirtualDom};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize);

View file

@ -1,31 +1,28 @@
use std::pin::Pin;
use crate::factory::{FiberLeaf, RenderReturn};
use crate::innerlude::SuspenseContext;
use crate::innerlude::{Renderer, SuspenseContext};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
use crate::nodes::{DynamicNode, TemplateNode};
use crate::virtualdom::VirtualDom;
use crate::virtual_dom::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
pub fn create<'a>(
&mut self,
mutations: &mut Vec<Mutation<'a>>,
template: &'a VNode<'a>,
) -> usize {
pub fn create<'a>(&mut self, mutations: &mut Renderer<'a>, template: &'a VNode<'a>) -> usize {
// The best renderers will have templates prehydrated
// Just in case, let's create the template using instructions anyways
if !self.templates.contains_key(&template.template.id) {
for node in template.template.roots {
let mutations = &mut mutations.template_mutations;
self.create_static_node(mutations, template, node);
}
mutations.push(SaveTemplate {
mutations.template_mutations.push(SaveTemplate {
name: template.template.id,
m: template.template.roots.len(),
});
@ -162,7 +159,7 @@ impl VirtualDom {
pub fn create_dynamic_node<'a>(
&mut self,
mutations: &mut Vec<Mutation<'a>>,
mutations: &mut Renderer<'a>,
template: &'a VNode<'a>,
node: &'a DynamicNode<'a>,
idx: usize,
@ -183,44 +180,61 @@ impl VirtualDom {
DynamicNode::Component {
props, placeholder, ..
} => {
println!("creaitng component");
let id = self.new_scope(unsafe { std::mem::transmute(props.get()) });
let render_ret = self.run_scope(id);
let render_ret: &mut RenderReturn = unsafe { std::mem::transmute(render_ret) };
// if boundary or subtree, start working on a new stack of mutations
match render_ret {
RenderReturn::Sync(None) | RenderReturn::Async(_) => {
let new_id = self.next_element(template);
placeholder.set(Some(new_id));
self.scopes[id.0].placeholder.set(Some(new_id));
mutations.push(AssignId {
id: new_id,
path: &template.template.node_paths[idx][1..],
});
0
}
RenderReturn::Sync(Some(template)) => {
let mutations_to_this_point = mutations.len();
self.scope_stack.push(id);
let created = self.create(mutations, template);
let mut created = self.create(mutations, template);
self.scope_stack.pop();
if !self.waiting_on.is_empty() {
if let Some(boundary) =
self.scopes[id.0].has_context::<SuspenseContext>()
{
let mut boundary_mut = boundary.borrow_mut();
let split_off = mutations.split_off(mutations_to_this_point);
let split_off = unsafe { std::mem::transmute(split_off) };
println!("SPLIT OFF: {:#?}", split_off);
boundary_mut.mutations.mutations = split_off;
boundary_mut.waiting_on.extend(self.waiting_on.drain(..));
// Since this is a boundary, use it as a placeholder
let new_id = self.next_element(template);
placeholder.set(Some(new_id));
self.scopes[id.0].placeholder.set(Some(new_id));
mutations.push(AssignId {
id: new_id,
path: &template.template.node_paths[idx][1..],
});
created = 0;
}
}
// handle any waiting on futures accumulated by async calls down the tree
// if this is a boundary, we split off the tree
created
}
// 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));
self.scopes[id.0].placeholder.set(Some(new_id));
mutations.push(AssignId {
id: new_id,
path: &template.template.node_paths[idx][1..],
});
0
}
RenderReturn::Async(fut) => {
let new_id = self.next_element(template);
placeholder.set(Some(new_id));
self.scopes[id.0].placeholder.set(Some(new_id));
mutations.push(AssignId {
id: new_id,
path: &template.template.node_paths[idx][1..],
});
0
}
}
}

View file

@ -1,6 +1,7 @@
use std::any::Any;
use crate::virtualdom::VirtualDom;
use crate::innerlude::Renderer;
use crate::virtual_dom::VirtualDom;
use crate::{Attribute, AttributeValue, TemplateNode};
use crate::any_props::VComponentProps;
@ -25,13 +26,13 @@ pub struct DirtyScope {
}
impl<'b> VirtualDom {
pub fn diff_scope(&mut self, mutations: &mut Vec<Mutation<'b>>, scope: ScopeId) {
pub fn diff_scope(&mut self, mutations: &mut Renderer<'b>, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0];
}
pub fn diff_node(
&mut self,
muts: &mut Vec<Mutation<'b>>,
muts: &mut Renderer<'b>,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
) {
@ -176,7 +177,7 @@ impl<'b> VirtualDom {
// the change list stack is in the same state when this function returns.
fn diff_non_keyed_children(
&mut self,
muts: &mut Vec<Mutation<'b>>,
muts: &mut Renderer<'b>,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
) {
@ -216,7 +217,7 @@ impl<'b> VirtualDom {
// The stack is empty upon entry.
fn diff_keyed_children(
&mut self,
muts: &mut Vec<Mutation<'b>>,
muts: &mut Renderer<'b>,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
) {
@ -295,7 +296,7 @@ impl<'b> VirtualDom {
// /// If there is no offset, then this function returns None and the diffing is complete.
// fn diff_keyed_ends(
// &mut self,
// muts: &mut Vec<Mutation<'b>>,
// muts: &mut Renderer<'b>,
// old: &'b [VNode<'b>],
// new: &'b [VNode<'b>],
// ) -> Option<(usize, usize)> {
@ -354,7 +355,7 @@ impl<'b> VirtualDom {
// #[allow(clippy::too_many_lines)]
// fn diff_keyed_middle(
// &mut self,
// muts: &mut Vec<Mutation<'b>>,
// muts: &mut Renderer<'b>,
// old: &'b [VNode<'b>],
// new: &'b [VNode<'b>],
// ) {
@ -532,7 +533,7 @@ impl<'b> VirtualDom {
/// Remove these nodes from the dom
/// Wont generate mutations for the inner nodes
fn remove_nodes(&mut self, muts: &mut Vec<Mutation<'b>>, nodes: &'b [VNode<'b>]) {
fn remove_nodes(&mut self, muts: &mut Renderer<'b>, nodes: &'b [VNode<'b>]) {
//
}
}

View file

@ -1,4 +1,4 @@
use crate::{arena::ElementId, virtualdom::VirtualDom, Attribute, AttributeValue};
use crate::{arena::ElementId, virtual_dom::VirtualDom, Attribute, AttributeValue};
use std::cell::Cell;
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.

View file

@ -1,4 +1,4 @@
use crate::{nodes::VNode, scopes::ScopeId, virtualdom::VirtualDom, DynamicNode};
use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode};
impl VirtualDom {
pub fn drop_scope(&mut self, id: ScopeId) {

View file

@ -14,7 +14,7 @@ mod properties;
mod scheduler;
mod scope_arena;
mod scopes;
mod virtualdom;
mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::arena::*;
@ -25,7 +25,7 @@ pub(crate) mod innerlude {
pub use crate::properties::*;
pub use crate::scheduler::*;
pub use crate::scopes::*;
pub use crate::virtualdom::*;
pub use crate::virtual_dom::*;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
///
@ -83,6 +83,9 @@ pub use crate::innerlude::{
Scope,
ScopeId,
ScopeState,
Scoped,
SuspenseBoundary,
SuspenseContext,
TaskId,
Template,
TemplateAttribute,
@ -98,8 +101,8 @@ pub use crate::innerlude::{
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
UiEvent, VNode, VirtualDom,
Properties, Scope, ScopeId, ScopeState, Scoped, SuspenseBoundary, SuspenseContext, TaskId,
Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
};
}
@ -132,3 +135,24 @@ macro_rules! to_owned {
let mut $es = $es.to_owned();
)*}
}
/// A helper macro for values into callbacks for async environements.
///
///
macro_rules! callback {
() => {};
}
/// Convert a hook into a hook with an implicit dependency list by analyzing the closure.
///
/// ```
/// // Convert hooks with annoying dependencies into...
///
/// let val = use_effect(cx, (val,) |(val,)| println!("thing {val}"))
///
/// // a simple closure
/// let val = use_effect!(cx, |val| async { println!("thing {val}")) });
/// ```
macro_rules! make_dep_fn {
() => {};
}

View file

@ -1,9 +1,81 @@
use crate::arena::ElementId;
#[derive(Debug)]
pub struct Renderer<'a> {
mutations: Vec<Mutation<'a>>,
pub subtree: usize,
pub mutations: Vec<Mutation<'a>>,
pub template_mutations: Vec<Mutation<'a>>,
// mutations: Vec<Mutations<'a>>,
}
impl<'a> Renderer<'a> {
pub fn new(subtree: usize) -> Self {
Self {
subtree,
mutations: Vec::new(),
template_mutations: Vec::new(),
}
}
}
impl<'a> std::ops::Deref for Renderer<'a> {
type Target = Vec<Mutation<'a>>;
fn deref(&self) -> &Self::Target {
&self.mutations
}
}
impl std::ops::DerefMut for Renderer<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.mutations
}
}
// impl<'a> Renderer<'a> {
// pub fn new(subtree: usize) -> Self {
// Self {
// mutations: vec![Mutations {
// subtree,
// mutations: Vec::new(),
// }],
// }
// }
// }
// impl<'a> Renderer<'a> {
// pub fn push(&mut self, mutation: Mutation<'a>) {
// self.mutations.last_mut().unwrap().mutations.push(mutation)
// }
// pub fn extend(&mut self, mutations: impl IntoIterator<Item = Mutation<'a>>) {
// self.mutations
// .last_mut()
// .unwrap()
// .mutations
// .extend(mutations)
// }
// pub fn len(&self) -> usize {
// self.mutations.last().unwrap().mutations.len()
// }
// pub fn split_off(&mut self, idx: usize) -> Renderer<'a> {
// let mut mutations = self.mutations.split_off(idx);
// let subtree = mutations.pop().unwrap().subtree;
// Renderer { mutations }
// }
// }
// #[derive(Debug)]
// pub struct Mutations<'a> {
// subtree: usize,
// mutations: Vec<Mutation<'a>>,
// }
/*
each subtree has its own numbering scheme
*/
#[derive(Debug)]
pub enum Mutation<'a> {
SetAttribute {

View file

@ -6,7 +6,10 @@ use std::{
};
use super::{waker::RcWake, SchedulerMsg};
use crate::{innerlude::Mutation, Element, ScopeId};
use crate::{
innerlude::{Mutation, Renderer},
Element, ScopeId,
};
use futures_task::Waker;
use futures_util::Future;
@ -14,27 +17,27 @@ use futures_util::Future;
pub struct SuspenseId(pub usize);
pub type SuspenseContext = Rc<RefCell<SuspenseBoundary>>;
/// Essentially a fiber in React
pub struct SuspenseBoundary {
pub id: ScopeId,
pub waiting_on: HashSet<SuspenseId>,
pub mutations: Vec<Mutation<'static>>,
pub mutations: Renderer<'static>,
}
impl SuspenseBoundary {
pub fn new(id: ScopeId) -> Self {
Self {
pub fn new(id: ScopeId) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
id,
waiting_on: Default::default(),
mutations: Default::default(),
}
mutations: Renderer::new(0),
}))
}
}
pub struct SuspenseLeaf {
pub id: SuspenseId,
pub scope_id: ScopeId,
pub boundary: ScopeId,
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub notified: Cell<bool>,
@ -43,10 +46,10 @@ pub struct SuspenseLeaf {
impl RcWake for SuspenseLeaf {
fn wake_by_ref(arc_self: &Rc<Self>) {
if arc_self.notified.get() {
return;
}
arc_self.notified.set(true);
// if arc_self.notified.get() {
// return;
// }
// arc_self.notified.set(true);
_ = arc_self
.tx
.unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));

View file

@ -3,7 +3,7 @@ use futures_util::{FutureExt, StreamExt};
use crate::{
factory::RenderReturn,
innerlude::{Mutation, SuspenseContext},
innerlude::{Mutation, Renderer, SuspenseContext},
VNode, VirtualDom,
};
@ -38,6 +38,8 @@ impl VirtualDom {
}
SchedulerMsg::SuspenseNotified(id) => {
println!("suspense notified");
let leaf = self
.scheduler
.handle
@ -63,11 +65,19 @@ impl VirtualDom {
// continue rendering the tree until we hit yet another suspended component
if let futures_task::Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx)
{
let boundary = &self.scopes[leaf.boundary.0]
let boundary = &self.scopes[leaf.scope_id.0]
.consume_context::<SuspenseContext>()
.unwrap();
println!("ready pool");
let mut fiber = boundary.borrow_mut();
println!(
"Existing mutations {:?}, scope {:?}",
fiber.mutations, fiber.id
);
let scope = &mut self.scopes[scope_id.0];
let arena = scope.current_arena();
@ -77,18 +87,24 @@ impl VirtualDom {
if let RenderReturn::Sync(Some(template)) = ret {
let mutations = &mut fiber.mutations;
let template: &VNode = unsafe { std::mem::transmute(template) };
let mutations: &mut Vec<Mutation> =
let mutations: &mut Renderer =
unsafe { std::mem::transmute(mutations) };
self.scope_stack.push(scope_id);
self.create(mutations, template);
self.scope_stack.pop();
println!("{:?}", mutations);
println!("{:#?}", mutations);
} else {
println!("nodes arent right");
}
} else {
println!("not ready");
}
}
}
// now proces any events. If we end up running a component and it generates mutations, then we should run those mutations
}
}
}

View file

@ -14,7 +14,7 @@ use crate::{
factory::RenderReturn,
innerlude::{SuspenseId, SuspenseLeaf},
scopes::{ScopeId, ScopeState},
virtualdom::VirtualDom,
virtual_dom::VirtualDom,
};
impl VirtualDom {
@ -77,13 +77,13 @@ impl VirtualDom {
let mut leaves = self.scheduler.handle.leaves.borrow_mut();
let entry = leaves.vacant_entry();
let key = entry.key();
let suspense_id = SuspenseId(key);
let leaf = Rc::new(SuspenseLeaf {
scope_id,
task: task.as_mut(),
id: SuspenseId(key),
id: suspense_id,
tx: self.scheduler.handle.sender.clone(),
boundary: ScopeId(0),
notified: false.into(),
});
@ -112,6 +112,7 @@ impl VirtualDom {
// Insert the future into fiber leaves and break
_ => {
entry.insert(leaf);
self.waiting_on.push(suspense_id);
break;
}
};

View file

@ -19,23 +19,14 @@ use crate::{
TaskId,
};
pub struct Scope<'a, T = ()> {
pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
pub struct Scoped<'a, T = ()> {
pub scope: &'a ScopeState,
pub props: &'a T,
}
impl<T> Copy for Scope<'_, T> {}
impl<T> Clone for Scope<'_, T> {
fn clone(&self) -> Self {
Self {
props: self.props,
scope: self.scope,
}
}
}
impl<'a, T> std::ops::Deref for Scope<'a, T> {
impl<'a, T> std::ops::Deref for Scoped<'a, T> {
type Target = &'a ScopeState;
fn deref(&self) -> &Self::Target {
@ -298,6 +289,19 @@ impl ScopeState {
}
}
/// Return any context of type T if it exists on this scope
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
match self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
Some(shared) => Some(
(*shared
.downcast_ref::<T>()
.expect("Context of type T should exist"))
.clone(),
),
None => None,
}
}
/// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
self.tasks.spawn(self.id, fut)

View file

@ -3,7 +3,7 @@ use crate::arena::ElementPath;
use crate::component::Component;
use crate::diff::DirtyScope;
use crate::factory::RenderReturn;
use crate::innerlude::{Scheduler, SchedulerMsg};
use crate::innerlude::{Renderer, Scheduler, SchedulerMsg};
use crate::mutations::Mutation;
use crate::nodes::{Template, TemplateId};
@ -13,20 +13,22 @@ use crate::{
};
use crate::{scheduler, Element, Scope};
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use scheduler::{SuspenseBoundary, SuspenseContext};
use futures_util::Future;
use scheduler::{SuspenseBoundary, SuspenseContext, SuspenseId};
use slab::Slab;
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::rc::Rc;
pub struct VirtualDom {
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
pub(crate) elements: Slab<ElementPath>,
pub(crate) scopes: Slab<ScopeState>,
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) element_stack: Vec<ElementId>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Scheduler,
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) waiting_on: Vec<SuspenseId>,
}
impl VirtualDom {
@ -40,6 +42,7 @@ impl VirtualDom {
scope_stack: Vec::new(),
element_stack: vec![ElementId(0)],
dirty_scopes: BTreeSet::new(),
waiting_on: Vec::new(),
scheduler,
};
@ -49,21 +52,25 @@ impl VirtualDom {
let root = res.new_scope(props);
// the root component is always a suspense boundary for any async children
res.scopes[root.0].provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(root))));
res.scopes[root.0].provide_context(SuspenseBoundary::new(root));
assert_eq!(root, ScopeId(0));
res
}
/// Render the virtualdom, without processing any suspense.
pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
///
/// This does register futures with wakers, but does not process any of them.
pub fn rebuild<'a>(&'a mut self) -> Renderer<'a> {
let mut mutations = Renderer::new(0);
let root_node: &RenderReturn = self.run_scope(ScopeId(0));
let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) };
let mut created = 0;
match root_node {
RenderReturn::Sync(Some(node)) => {
self.scope_stack.push(ScopeId(0));
self.create(mutations, node);
created = self.create(&mut mutations, node);
self.scope_stack.pop();
}
RenderReturn::Sync(None) => {
@ -71,22 +78,22 @@ impl VirtualDom {
}
RenderReturn::Async(_) => unreachable!(),
}
mutations.push(Mutation::AppendChildren { m: created });
mutations
}
/// Render what you can given the timeline and then move on
pub async fn render_with_deadline<'a>(
&'a mut self,
future: impl std::future::Future<Output = ()>,
mutations: &mut Vec<Mutation<'a>>,
) {
///
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
pub async fn render_with_deadline(
&mut self,
deadline: impl Future<Output = ()>,
) -> Vec<Mutation> {
todo!()
}
// Whenever the future is canceled, the VirtualDom will be
pub async fn render<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
//
}
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id.0)
}

View file

@ -21,6 +21,5 @@ async fn ChildAsync(cx: Scope<'_>) -> Element {
fn it_works() {
let mut dom = VirtualDom::new(app);
let mut mutations = vec![];
dom.rebuild(&mut mutations);
let mut mutations = dom.rebuild();
}

View file

@ -1,13 +1,11 @@
use std::{cell::Cell, ptr::null_mut, time::Duration};
use dioxus_core::*;
use std::{cell::RefCell, rc::Rc, time::Duration};
#[tokio::test]
async fn it_works() {
let mut dom = VirtualDom::new(app);
let mut mutations = vec![];
dom.rebuild(&mut mutations);
let mutations = dom.rebuild();
println!("mutations: {:?}", mutations);
@ -15,8 +13,23 @@ async fn it_works() {
}
fn app(cx: Scope) -> Element {
let dy = cx.component(async_child, (), "async_child");
VNode::single_component(&cx, dy, "app")
println!("running root app");
VNode::single_component(
cx,
cx.component(suspense_boundary, (), "suspense_boundary"),
"app",
)
}
fn suspense_boundary(cx: Scope) -> Element {
println!("running boundary");
let _ = cx.use_hook(|| {
cx.provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(cx.scope_id()))))
});
VNode::single_component(cx, cx.component(async_child, (), "async_child"), "app")
}
async fn async_child(cx: Scope<'_>) -> Element {
@ -34,8 +47,9 @@ async fn async_child(cx: Scope<'_>) -> Element {
println!("Future awaited and complete");
let dy = cx.component(async_child, (), "async_child");
VNode::single_component(&cx, dy, "app")
// VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty")
VNode::single_component(cx, cx.component(async_text, (), "async_text"), "app")
}
async fn async_text(cx: Scope<'_>) -> Element {
VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty")
}

View file

@ -6,8 +6,7 @@ use dioxus_core::*;
async fn it_works() {
let mut dom = VirtualDom::new(app);
let mut mutations = vec![];
dom.rebuild(&mut mutations);
let mutations = dom.rebuild();
println!("mutations: {:?}", mutations);

View file

@ -32,6 +32,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
criterion = "0.3.5"
thiserror = "1.0.30"
env_logger = "0.9.0"
tokio = { version = "1.21.2", features = ["full"] }
# dioxus-edit-stream = { path = "../edit-stream" }
[[bench]]

View file

@ -1,3 +1,5 @@
use std::future::IntoFuture;
use dioxus::prelude::*;
fn basic_syntax_is_a_template(cx: Scope) -> Element {
@ -23,41 +25,63 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
})
}
fn basic_template(cx: Scope) -> Element {
cx.render(rsx! {
div {
basic_child { }
async_child { }
}
})
#[inline_props]
fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
cx.use_hook(|| cx.provide_context(SuspenseBoundary::new(cx.scope_id())));
cx.render(rsx! { children })
}
fn basic_child(cx: Scope) -> Element {
todo!()
cx.render(rsx! {
div { "basic child 1" }
})
}
async fn async_child(cx: Scope<'_>) -> Element {
todo!()
let username = use_future!(cx, || async {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
"async child 1"
});
let age = use_future!(cx, || async {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
println!("long future completed");
1234
});
let (_user, _age) = use_future!(cx, || async {
tokio::join!(
tokio::time::sleep(std::time::Duration::from_secs(1)),
tokio::time::sleep(std::time::Duration::from_secs(2))
);
("async child 1", 1234)
})
.await;
let (username, age) = tokio::join!(username.into_future(), age.into_future());
cx.render(rsx!(
div { "Hello! {username}, you are {age}, {_user} {_age}" }
))
}
#[test]
fn basic_prints() {
let mut dom = VirtualDom::new(basic_template);
#[tokio::test]
async fn basic_prints() {
let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
div {
h1 { "var" }
suspense_boundary {
basic_child { }
async_child { }
}
}
})
});
let mut edits = Vec::new();
dom.rebuild(&mut edits);
dbg!(edits);
dbg!(dom.rebuild());
let mut edits = Vec::new();
dom.rebuild(&mut edits);
dom.wait_for_work().await;
dbg!(edits);
// let renderer = dioxus_edit_stream::Mutations::default();
//
// dbg!(renderer.edits);
// takes_it(basic_child);
dbg!(dom.rebuild());
}
// fn takes_it(f: fn(Scope) -> Element) {}
// fn takes_it(f: fn(Scope) -> Element) {}

View file

@ -1,6 +1,12 @@
#![allow(missing_docs)]
use dioxus_core::{ScopeState, TaskId};
use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
use std::{
any::Any,
cell::{Cell, RefCell},
future::{Future, IntoFuture},
rc::Rc,
sync::Arc,
};
/// A future that resolves to a value.
///
@ -28,16 +34,13 @@ where
let state = cx.use_hook(move || UseFuture {
update: cx.schedule_update(),
needs_regen: Cell::new(true),
slot: Rc::new(Cell::new(None)),
value: None,
values: Default::default(),
task: Cell::new(None),
dependencies: Vec::new(),
waker: Default::default(),
});
if let Some(value) = state.slot.take() {
state.value = Some(value);
state.task.set(None);
}
*state.waker.borrow_mut() = None;
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
// We don't need regen anymore
@ -47,8 +50,9 @@ where
let fut = future(dependencies.out());
// Clone in our cells
let slot = state.slot.clone();
let values = state.values.clone();
let schedule_update = state.update.clone();
let waker = state.waker.clone();
// Cancel the current future
if let Some(current) = state.task.take() {
@ -57,8 +61,16 @@ where
state.task.set(Some(cx.push_future(async move {
let res = fut.await;
slot.set(Some(res));
schedule_update();
values.borrow_mut().push(Box::leak(Box::new(res)));
// if there's a waker, we dont re-render the component. Instead we just progress that future
match waker.borrow().as_ref() {
Some(waker) => waker.wake_by_ref(),
None => {
println!("scheduling update");
// schedule_update()
}
}
})));
}
@ -74,10 +86,10 @@ pub enum FutureState<'a, T> {
pub struct UseFuture<T> {
update: Arc<dyn Fn()>,
needs_regen: Cell<bool>,
value: Option<T>,
slot: Rc<Cell<Option<T>>>,
task: Cell<Option<TaskId>>,
dependencies: Vec<Box<dyn Any>>,
waker: Rc<RefCell<Option<std::task::Waker>>>,
values: Rc<RefCell<Vec<*mut T>>>,
}
pub enum UseFutureState<'a, T> {
@ -105,22 +117,25 @@ impl<T> UseFuture<T> {
// clears the value in the future slot without starting the future over
pub fn clear(&self) -> Option<T> {
(self.update)();
self.slot.replace(None)
todo!()
// (self.update)();
// self.slot.replace(None)
}
// Manually set the value in the future slot without starting the future over
pub fn set(&self, new_value: T) {
self.slot.set(Some(new_value));
self.needs_regen.set(true);
(self.update)();
// self.slot.set(Some(new_value));
// self.needs_regen.set(true);
// (self.update)();
todo!()
}
/// Return any value, even old values if the future has not yet resolved.
///
/// If the future has never completed, the returned value will be `None`.
pub fn value(&self) -> Option<&T> {
self.value.as_ref()
// self.value.as_ref()
todo!()
}
/// Get the ID of the future in Dioxus' internal scheduler
@ -130,18 +145,49 @@ impl<T> UseFuture<T> {
/// Get the current stateof the future.
pub fn state(&self) -> UseFutureState<T> {
match (&self.task.get(), &self.value) {
// If we have a task and an existing value, we're reloading
(Some(_), Some(val)) => UseFutureState::Reloading(val),
todo!()
// match (&self.task.get(), &self.value) {
// // If we have a task and an existing value, we're reloading
// (Some(_), Some(val)) => UseFutureState::Reloading(val),
// no task, but value - we're done
(None, Some(val)) => UseFutureState::Complete(val),
// // no task, but value - we're done
// (None, Some(val)) => UseFutureState::Complete(val),
// no task, no value - something's wrong? return pending
(None, None) => UseFutureState::Pending,
// // no task, no value - something's wrong? return pending
// (None, None) => UseFutureState::Pending,
// Task, no value - we're still pending
(Some(_), None) => UseFutureState::Pending,
// // Task, no value - we're still pending
// (Some(_), None) => UseFutureState::Pending,
// }
}
}
impl<'a, T> IntoFuture for &'a UseFuture<T> {
type Output = &'a T;
type IntoFuture = UseFutureAwait<'a, T>;
fn into_future(self) -> Self::IntoFuture {
UseFutureAwait { hook: self }
}
}
pub struct UseFutureAwait<'a, T> {
hook: &'a UseFuture<T>,
}
impl<'a, T> Future for UseFutureAwait<'a, T> {
type Output = &'a T;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
println!("polling future");
match self.hook.values.borrow_mut().last().cloned() {
Some(value) => std::task::Poll::Ready(unsafe { &*value }),
None => {
self.hook.waker.replace(Some(cx.waker().clone()));
std::task::Poll::Pending
}
}
}
}
@ -239,6 +285,18 @@ impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,);
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,);
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,);
#[macro_export]
macro_rules! use_future {
($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) };
($cx:ident, | $($args:tt),* | $($rest:tt)*) => {
use_future(
$cx,
($($args),*),
|($($args),*)| $($rest)*
)
};
}
#[cfg(test)]
mod tests {
use super::*;
@ -256,22 +314,32 @@ mod tests {
e: i32,
}
fn app(cx: Scope<MyProps>) -> Element {
async fn app(cx: Scope<'_, MyProps>) -> Element {
// should only ever run once
let fut = use_future(&cx, (), |_| async move {
//
});
let fut = use_future(cx, (), |_| async move {});
// runs when a is changed
let fut = use_future(&cx, (&cx.props.a,), |(a,)| async move {
//
});
let fut = use_future(cx, (&cx.props.a,), |(a,)| async move {});
// runs when a or b is changed
let fut = use_future(&cx, (&cx.props.a, &cx.props.b), |(a, b)| async move {
//
let fut = use_future(cx, (&cx.props.a, &cx.props.b), |(a, b)| async move { 123 });
let a = use_future!(cx, || async move {
// do the thing!
});
let b = &123;
let c = &123;
let a = use_future!(cx, |b, c| async move {
let a = b + c;
let blah = "asd";
});
let g2 = a.await;
let g = fut.await;
None
}
}

View file

@ -472,6 +472,8 @@ fn api_makes_sense() {
}
});
cx.render(LazyNodes::new(|f| f.static_text("asd")))
// cx.render(LazyNodes::new(|f| f.static_text("asd")))
todo!()
}
}

View file

@ -1,41 +0,0 @@
use std::{cell::Cell, future::Future, rc::Rc};
use dioxus_core::{Element, ScopeState, TaskId};
pub fn use_suspense<R: 'static, F: Future<Output = R> + 'static>(
cx: &ScopeState,
create_future: impl FnOnce() -> F,
render: impl FnOnce(&R) -> Element,
) -> Element {
let sus = cx.use_hook(|| {
let fut = create_future();
let wip_value: Rc<Cell<Option<R>>> = Default::default();
let wip = wip_value.clone();
let new_fut = async move {
let val = fut.await;
wip.set(Some(val));
};
let task = cx.push_future(new_fut);
SuspenseInner {
_task: task,
value: None,
_wip_value: wip_value,
}
});
if let Some(value) = sus.value.as_ref() {
render(value)
} else {
// generate a placeholder node if the future isnt ready
None
}
}
struct SuspenseInner<R> {
_task: TaskId,
_wip_value: Rc<Cell<Option<R>>>,
value: Option<R>,
}