diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 22b4a888b..dff3d4cdd 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -188,7 +188,6 @@ impl VirtualDom { let render_ret = self.run_scope(id); - // shut up about lifetimes please, I know what I'm doing let render_ret: &mut RenderReturn = unsafe { std::mem::transmute(render_ret) }; match render_ret { @@ -204,53 +203,22 @@ impl VirtualDom { 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); - - let scope = self.scope_stack.last().unwrap(); - let scope = &self.scopes[scope.0]; - - let boundary = scope.consume_context::().unwrap(); - - // try to poll the future once - many times it will be ready immediately or require little to no work - - todo!(); - - // // 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) { - // 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) }; - - // // 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.suspended_scopes.insert(*scope_id); - // break; - - // } - 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 } } diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index cd0962358..a75b99e42 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -119,7 +119,7 @@ where fn as_return(self, cx: &'a ScopeState) -> RenderReturn<'a> { let f: &mut dyn Future> = cx.bump().alloc(self); let boxed = unsafe { BumpBox::from_raw(f) }; - let pined: Pin> = boxed.into(); + let pined: BumpBox<_> = boxed.into(); RenderReturn::Async(pined) } } @@ -133,7 +133,7 @@ fn takes_it() { pub enum RenderReturn<'a> { Sync(Element<'a>), - Async(Pin> + 'a>>), + Async(BumpBox<'a, dyn Future> + 'a>), } pub type FiberLeaf<'a> = Pin> + 'a>>; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index fbed74b46..3942de69d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -47,6 +47,27 @@ impl<'a> VNode<'a> { }, }) } + + pub fn single_text( + cx: &'a ScopeState, + text: &'static [TemplateNode<'static>], + id: &'static str, + ) -> Option { + Some(VNode { + node_id: Cell::new(ElementId(0)), + key: None, + parent: None, + root_ids: &[], + dynamic_nodes: &[], + dynamic_attrs: &[], + template: Template { + id, + roots: text, + node_paths: &[&[0]], + attr_paths: &[], + }, + }) + } } #[derive(Debug, Clone, Copy)] @@ -62,25 +83,13 @@ impl<'a> std::hash::Hash for Template<'a> { self.id.hash(state); } } - +impl Eq for Template<'_> {} impl PartialEq for Template<'_> { fn eq(&self, other: &Self) -> bool { self.id == other.id } } -impl Eq for Template<'_> {} -impl PartialOrd for Template<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - self.id.partial_cmp(other.id) - } -} -impl Ord for Template<'_> { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.id.cmp(other.id) - } -} - /// A weird-ish variant of VNodes with way more limited types #[derive(Debug, Clone, Copy)] pub enum TemplateNode<'a> { @@ -96,24 +105,17 @@ pub enum TemplateNode<'a> { } pub enum DynamicNode<'a> { - // Anything declared in component form - // IE in caps or with underscores Component { name: &'static str, static_props: bool, props: Cell<*mut dyn AnyProps<'a>>, placeholder: Cell>, }, - - // Comes in with string interpolation or from format_args, include_str, etc Text { id: Cell, value: &'a str, }, - - // Anything that's coming in as an iterator Fragment(&'a [VNode<'a>]), - Placeholder(Cell), } @@ -153,8 +155,8 @@ impl<'a> std::fmt::Debug for AttributeValue<'a> { Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(), Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(), Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(), - Self::Listener(arg0) => f.debug_tuple("Listener").finish(), - Self::Any(arg0) => f.debug_tuple("Any").finish(), + Self::Listener(_) => f.debug_tuple("Listener").finish(), + Self::Any(_) => f.debug_tuple("Any").finish(), Self::None => write!(f, "None"), } } @@ -167,7 +169,7 @@ impl<'a> PartialEq for AttributeValue<'a> { (Self::Float(l0), Self::Float(r0)) => l0 == r0, (Self::Int(l0), Self::Int(r0)) => l0 == r0, (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, - (Self::Listener(l0), Self::Listener(r0)) => true, + (Self::Listener(_), Self::Listener(_)) => true, (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(*r0), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } @@ -186,20 +188,14 @@ impl<'a> AttributeValue<'a> { _ => return false, } } - - fn is_listener(&self) -> bool { - matches!(self, AttributeValue::Listener(_)) - } } pub trait AnyValue { fn any_cmp(&self, other: &dyn AnyValue) -> bool; fn our_typeid(&self) -> TypeId; } -impl AnyValue for T -where - T: PartialEq + Any, -{ + +impl AnyValue for T { fn any_cmp(&self, other: &dyn AnyValue) -> bool { if self.type_id() != other.our_typeid() { return false; diff --git a/packages/core/src/scheduler/bumpslab.rs b/packages/core/src/scheduler/bumpslab.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/core/src/scheduler/bumpslab.rs @@ -0,0 +1 @@ + diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs index 69d5c5724..6e56deec8 100644 --- a/packages/core/src/scheduler/mod.rs +++ b/packages/core/src/scheduler/mod.rs @@ -1,7 +1,9 @@ +use slab::Slab; use std::sync::Arc; use crate::ScopeId; +mod bumpslab; mod handle; mod suspense; mod task; @@ -9,9 +11,9 @@ mod wait; mod waker; pub use handle::*; -use slab::Slab; pub use suspense::*; pub use task::*; +pub use waker::RcWake; /// The type of message that can be sent to the scheduler. /// diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index 8bf8732c9..995428e4c 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -1,21 +1,24 @@ -use std::{collections::HashSet, rc::Rc}; +use std::{ + cell::{Cell, RefCell}, + collections::HashSet, + pin::Pin, + rc::Rc, +}; -use futures_task::{RawWaker, RawWakerVTable, Waker}; +use super::{waker::RcWake, SchedulerMsg}; +use crate::{innerlude::Mutation, Element, ScopeId}; +use futures_task::Waker; use futures_util::Future; -use crate::{innerlude::Mutation, Element, Scope, ScopeId}; - -use super::SchedulerMsg; - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct SuspenseId(pub usize); -pub type SuspenseContext = Rc; +pub type SuspenseContext = Rc>; /// Essentially a fiber in React pub struct SuspenseBoundary { pub id: ScopeId, - waiting_on: HashSet, - mutations: Vec>, + pub waiting_on: HashSet, + pub mutations: Vec>, } impl SuspenseBoundary { @@ -28,44 +31,24 @@ impl SuspenseBoundary { } } -/* - - -many times the future will be ready every time it's polled, so we can spin on it until it doesnt wake us up immediately - - -*/ pub struct SuspenseLeaf { pub id: SuspenseId, - pub scope: ScopeId, + pub scope_id: ScopeId, pub boundary: ScopeId, pub tx: futures_channel::mpsc::UnboundedSender, + pub notified: Cell, pub task: *mut dyn Future>, } -pub fn make_suspense_waker(task: &SuspenseLeaf) -> Waker { - let raw = RawWaker::new(task as *const SuspenseLeaf as *const _, task_vtable()); - unsafe { Waker::from_raw(raw) } -} - -fn task_vtable() -> &'static RawWakerVTable { - &RawWakerVTable::new(clone, wake, wake_by_ref, drop_task) -} - -unsafe fn clone(data: *const ()) -> RawWaker { - RawWaker::new(data as *const (), task_vtable()) -} -unsafe fn wake(data: *const ()) { - wake_by_ref(data); -} -unsafe fn wake_by_ref(data: *const ()) { - let task = &*(data as *const SuspenseLeaf); - task.tx - .unbounded_send(SchedulerMsg::SuspenseNotified(task.id)) - .expect("Scheduler should exist"); -} - -unsafe fn drop_task(_data: *const ()) { - // doesnt do anything +impl RcWake for SuspenseLeaf { + fn wake_by_ref(arc_self: &Rc) { + if arc_self.notified.get() { + return; + } + arc_self.notified.set(true); + _ = arc_self + .tx + .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id)); + } } diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index ff75f8cd7..692f9f436 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -61,11 +61,6 @@ impl HandleInner { } } -pub fn make_task_waker(task: Rc) -> Waker { - let ptr = Rc::into_raw(task).cast::<()>(); - super::waker::make_rc_waker(task) -} - impl RcWake for LocalTask { fn wake_by_ref(arc_self: &Rc) { _ = arc_self diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index aa5eaa6fb..17ddfac4c 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,9 +1,13 @@ use futures_task::Context; use futures_util::{FutureExt, StreamExt}; -use crate::{innerlude::make_task_waker, VirtualDom}; +use crate::{ + factory::RenderReturn, + innerlude::{Mutation, SuspenseContext}, + VNode, VirtualDom, +}; -use super::SchedulerMsg; +use super::{waker::RcWake, SchedulerMsg, SuspenseLeaf}; impl VirtualDom { /// Wait for futures internal to the virtualdom @@ -22,7 +26,7 @@ impl VirtualDom { // attach the waker to itself // todo: don't make a new waker every time, make it once and then just clone it - let waker = make_task_waker(local_task.clone()); + let waker = local_task.waker(); let mut cx = Context::from_waker(&waker); // safety: the waker owns its task and everythig is single threaded @@ -33,7 +37,57 @@ impl VirtualDom { } } - SchedulerMsg::SuspenseNotified(_) => todo!(), + SchedulerMsg::SuspenseNotified(id) => { + let leaf = self + .scheduler + .handle + .leaves + .borrow_mut() + .get(id.0) + .unwrap() + .clone(); + + let scope_id = leaf.scope_id; + + // todo: cache the waker + let waker = leaf.waker(); + let mut cx = Context::from_waker(&waker); + + let fut = unsafe { &mut *leaf.task }; + + let mut pinned = unsafe { std::pin::Pin::new_unchecked(fut) }; + let as_pinned_mut = &mut pinned; + + // the component finished rendering and gave us nodes + // 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 futures_task::Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) + { + let boundary = &self.scopes[leaf.boundary.0] + .consume_context::() + .unwrap(); + + let mut fiber = boundary.borrow_mut(); + let scope = &mut self.scopes[scope_id.0]; + let arena = scope.current_arena(); + + let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes)); + arena.node.set(ret); + + if let RenderReturn::Sync(Some(template)) = ret { + let mutations = &mut fiber.mutations; + let template: &VNode = unsafe { std::mem::transmute(template) }; + let mutations: &mut Vec = + unsafe { std::mem::transmute(mutations) }; + + self.scope_stack.push(scope_id); + self.create(mutations, template); + self.scope_stack.pop(); + + println!("{:?}", mutations); + } + } + } } } } diff --git a/packages/core/src/scheduler/waker.rs b/packages/core/src/scheduler/waker.rs index bba60efe5..9add24ba4 100644 --- a/packages/core/src/scheduler/waker.rs +++ b/packages/core/src/scheduler/waker.rs @@ -1,54 +1,36 @@ -use std::{ - cell::{RefCell, UnsafeCell}, - marker::PhantomData, - mem::{self, MaybeUninit}, - ops::DerefMut, - pin::Pin, - process::Output, - rc::Rc, - sync::Arc, -}; +use futures_task::{RawWaker, RawWakerVTable, Waker}; +use std::{mem, rc::Rc}; -use futures_task::{waker, RawWaker, RawWakerVTable, Waker}; +pub trait RcWake: Sized { + /// Create a waker from this self-wakening object + fn waker(self: &Rc) -> Waker { + unsafe fn rc_vtable() -> &'static RawWakerVTable { + &RawWakerVTable::new( + |data| { + let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::())); + let _rc_clone: mem::ManuallyDrop<_> = arc.clone(); + RawWaker::new(data, rc_vtable::()) + }, + |data| Rc::from_raw(data.cast::()).wake(), + |data| { + let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::())); + RcWake::wake_by_ref(&arc); + }, + |data| drop(Rc::::from_raw(data.cast::())), + ) + } + + unsafe { + Waker::from_raw(RawWaker::new( + Rc::into_raw(self.clone()).cast(), + rc_vtable::(), + )) + } + } + + fn wake_by_ref(arc_self: &Rc); -pub trait RcWake { fn wake(self: Rc) { Self::wake_by_ref(&self) } - fn wake_by_ref(arc_self: &Rc); -} - -pub fn make_rc_waker(rc: Rc) -> Waker { - unsafe { Waker::from_raw(RawWaker::new(Rc::into_raw(rc).cast(), rc_vtable::())) } -} - -fn rc_vtable() -> &'static RawWakerVTable { - &RawWakerVTable::new( - clone_rc_raw::, - wake_rc_raw::, - wake_by_ref_rc_raw::, - drop_rc_raw::, - ) -} - -// FIXME: panics on Rc::clone / refcount changes could wreak havoc on the -// code here. We should guard against this by aborting. -unsafe fn clone_rc_raw(data: *const ()) -> RawWaker { - let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::())); - let _rc_clone: mem::ManuallyDrop<_> = arc.clone(); - RawWaker::new(data, rc_vtable::()) -} - -unsafe fn wake_rc_raw(data: *const ()) { - let arc: Rc = Rc::from_raw(data.cast::()); - arc.wake(); -} - -unsafe fn wake_by_ref_rc_raw(data: *const ()) { - let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::())); - arc.wake(); -} - -unsafe fn drop_rc_raw(data: *const ()) { - drop(Rc::::from_raw(data.cast::())) } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 4d88bb54d..bbf3d00e2 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -1,14 +1,20 @@ -use std::task::Context; - -use futures_util::task::noop_waker_ref; +use crate::{innerlude::SuspenseContext, scheduler::RcWake}; +use futures_util::{pin_mut, task::noop_waker_ref}; +use std::{ + mem, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use crate::{ any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, factory::RenderReturn, + innerlude::{SuspenseId, SuspenseLeaf}, scopes::{ScopeId, ScopeState}, - virtualdom::VirtualDom, innerlude::SuspenseLeaf, + virtualdom::VirtualDom, }; impl VirtualDom { @@ -25,6 +31,7 @@ impl VirtualDom { id, height, props, + placeholder: None.into(), node_arena_1: BumpFrame::new(50), node_arena_2: BumpFrame::new(50), render_cnt: Default::default(), @@ -52,46 +59,66 @@ impl VirtualDom { .and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState)) } - pub fn run_scope(&mut self, id: ScopeId) -> &mut RenderReturn { - let scope = &mut self.scopes[id.0]; - scope.hook_idx.set(0); + pub fn run_scope(&mut self, scope_id: ScopeId) -> &mut RenderReturn { + let mut new_nodes = unsafe { + let scope = &mut self.scopes[scope_id.0]; + scope.hook_idx.set(0); - let mut new_nodes = { - let props = unsafe { &mut *scope.props }; - let props: &mut dyn AnyProps = unsafe { std::mem::transmute(props) }; + let props: &mut dyn AnyProps = mem::transmute(&mut *scope.props); let res: RenderReturn = props.render(scope); - let res: RenderReturn<'static> = unsafe { std::mem::transmute(res) }; + let res: RenderReturn<'static> = mem::transmute(res); 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; + // immediately resolve futures that can be resolved + if let RenderReturn::Async(task) = &mut new_nodes { + use futures_util::FutureExt; - let leaves = self.scheduler.handle.leaves.borrow_mut(); + let mut leaves = self.scheduler.handle.leaves.borrow_mut(); + let entry = leaves.vacant_entry(); + let key = entry.key(); - leaves.insert(Rc::new(SuspenseLeaf { - id: todo!(), - scope: todo!(), - boundary: todo!(), - tx: todo!(), - task: todo!(), - })); + let leaf = Rc::new(SuspenseLeaf { + scope_id, + task: task.as_mut(), + id: SuspenseId(key), + tx: self.scheduler.handle.sender.clone(), + boundary: ScopeId(0), + notified: false.into(), + }); - let waker = crate::scheduler::make_suspense_waker(task); + let _leaf = leaf.clone(); + let waker = leaf.waker(); + let mut cx = Context::from_waker(&waker); + let mut pinned = unsafe { Pin::new_unchecked(task.as_mut()) }; - match fut.poll_unpin(&mut cx) { - std::task::Poll::Ready(nodes) => { - leaves.remove(key;) - RenderReturn::Sync(nodes) - }, - std::task::Poll::Pending => new_nodes, - } + loop { + 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); + break; + } + + // If no nodes are produced but the future woke up immediately, then try polling it again + // This circumvents things like yield_now, but is important is important when rendering + // components that are just a stream of immediately ready futures + _ if _leaf.notified.get() => { + _leaf.notified.set(false); + continue; + } + + // If no nodes are produced, then we need to wait for the future to be woken up + // Insert the future into fiber leaves and break + _ => { + entry.insert(leaf); + break; + } + }; } }; + let scope = &mut self.scopes[scope_id.0]; let frame = match scope.render_cnt % 2 { 0 => &mut scope.node_arena_1, 1 => &mut scope.node_arena_2, @@ -99,10 +126,10 @@ impl VirtualDom { }; // set the head of the bump frame - let alloced = frame.bump.alloc(res); + let alloced = frame.bump.alloc(new_nodes); frame.node.set(alloced); // rebind the lifetime now that its stored internally - unsafe { std::mem::transmute(alloced) } + unsafe { mem::transmute(alloced) } } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index b48c6fbe3..380da37b8 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -72,6 +72,7 @@ pub struct ScopeState { pub tasks: SchedulerHandle, pub props: *mut dyn AnyProps<'static>, + pub placeholder: Cell>, } impl ScopeState { diff --git a/packages/core/src/virtualdom.rs b/packages/core/src/virtualdom.rs index 767ac9141..66986ec54 100644 --- a/packages/core/src/virtualdom.rs +++ b/packages/core/src/virtualdom.rs @@ -13,9 +13,11 @@ use crate::{ }; use crate::{scheduler, Element, Scope}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; -use scheduler::SuspenseContext; +use scheduler::{SuspenseBoundary, SuspenseContext}; use slab::Slab; +use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; +use std::rc::Rc; pub struct VirtualDom { pub(crate) templates: HashMap>, @@ -47,7 +49,7 @@ 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(SuspenseContext::new(root)); + res.scopes[root.0].provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(root)))); assert_eq!(root, ScopeId(0)); diff --git a/packages/core/tests/suspend.rs b/packages/core/tests/suspend.rs index b46244c90..7ac05a1fa 100644 --- a/packages/core/tests/suspend.rs +++ b/packages/core/tests/suspend.rs @@ -32,7 +32,10 @@ async fn async_child(cx: Scope<'_>) -> Element { fut.await; - println!("Future awaited"); + println!("Future awaited and complete"); - None + let dy = cx.component(async_child, (), "async_child"); + VNode::single_component(&cx, dy, "app") + + // VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty") }