diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 8cda002a0..22b4a888b 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -99,7 +99,10 @@ impl VirtualDom { } // We're on top of a node that has a dynamic child for a descendant - while let Some((idx, path)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) { + // Skip any node that's a root + while let Some((idx, path)) = + dynamic_nodes.next_if(|(_, p)| p.len() > 1 && p[0] == root_idx as u8) + { let node = &template.dynamic_nodes[idx]; let m = self.create_dynamic_node(mutations, template, node, idx); if m > 0 { @@ -180,6 +183,7 @@ 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); @@ -211,6 +215,7 @@ impl VirtualDom { 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 diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 9f8b06dc5..57ba619e6 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -72,7 +72,7 @@ impl<'b> VirtualDom { { #[rustfmt::skip] match (left_node, right_node) { - (DynamicNode::Component { props: lprops, .. }, DynamicNode::Component { is_static , props: rprops, .. }) => { + (DynamicNode::Component { props: lprops, .. }, DynamicNode::Component { static_props: is_static , props: rprops, .. }) => { let left_props = unsafe { &mut *lprops.get()}; let right_props = unsafe { &mut *rprops.get()}; diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 15f5d1584..cd0962358 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -95,7 +95,7 @@ impl ScopeState { DynamicNode::Component { name: fn_name, - is_static: P::IS_STATIC, + static_props: P::IS_STATIC, props: Cell::new(detached_dyn), placeholder: Cell::new(None), } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9e6742ec3..fbed74b46 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,4 +1,4 @@ -use crate::{any_props::AnyProps, arena::ElementId}; +use crate::{any_props::AnyProps, arena::ElementId, ScopeState}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -26,6 +26,29 @@ pub struct VNode<'a> { pub dynamic_attrs: &'a [Attribute<'a>], } +impl<'a> VNode<'a> { + pub fn single_component( + cx: &'a ScopeState, + node: DynamicNode<'a>, + id: &'static str, + ) -> Option { + Some(VNode { + node_id: Cell::new(ElementId(0)), + key: None, + parent: None, + root_ids: &[], + dynamic_nodes: cx.bump().alloc([node]), + dynamic_attrs: &[], + template: Template { + id, + roots: &[TemplateNode::Dynamic(0)], + node_paths: &[&[0]], + attr_paths: &[], + }, + }) + } +} + #[derive(Debug, Clone, Copy)] pub struct Template<'a> { pub id: &'a str, @@ -77,7 +100,7 @@ pub enum DynamicNode<'a> { // IE in caps or with underscores Component { name: &'static str, - is_static: bool, + static_props: bool, props: Cell<*mut dyn AnyProps<'a>>, placeholder: Cell>, }, diff --git a/packages/core/src/scheduler/handle.rs b/packages/core/src/scheduler/handle.rs index f2be43cf2..8525ba8de 100644 --- a/packages/core/src/scheduler/handle.rs +++ b/packages/core/src/scheduler/handle.rs @@ -16,8 +16,12 @@ impl std::ops::Deref for SchedulerHandle { pub struct HandleInner { pub sender: futures_channel::mpsc::UnboundedSender, - pub tasks: RefCell>, - pub leaves: RefCell>, + + /// Tasks created with cx.spawn + pub tasks: RefCell>>, + + /// Async components + pub leaves: RefCell>>, } impl SchedulerHandle { diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs index c52c278da..69d5c5724 100644 --- a/packages/core/src/scheduler/mod.rs +++ b/packages/core/src/scheduler/mod.rs @@ -6,6 +6,7 @@ mod handle; mod suspense; mod task; mod wait; +mod waker; pub use handle::*; use slab::Slab; diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index a66f2d748..ff75f8cd7 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -1,12 +1,21 @@ -use std::{cell::RefCell, mem, ops::DerefMut, pin::Pin, process::Output, rc::Rc, sync::Arc}; +use std::{ + cell::{RefCell, UnsafeCell}, + marker::PhantomData, + mem::{self, MaybeUninit}, + ops::DerefMut, + pin::Pin, + process::Output, + rc::Rc, + sync::Arc, +}; use futures_task::{waker, ArcWake, Context, RawWaker, RawWakerVTable, Waker}; use futures_util::{pin_mut, Future, FutureExt}; use slab::Slab; -use crate::ScopeId; +use crate::{Element, ScopeId}; -use super::{HandleInner, SchedulerHandle, SchedulerMsg}; +use super::{waker::RcWake, HandleInner, SchedulerHandle, SchedulerMsg}; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -14,27 +23,25 @@ pub struct TaskId(pub usize); /// the task itself is the waker -#[derive(Clone)] pub struct LocalTask { id: TaskId, scope: ScopeId, tx: futures_channel::mpsc::UnboundedSender, - pub task: *mut dyn Future, + pub task: UnsafeCell + 'static>>>, } impl HandleInner { pub fn spawn(&self, scope: ScopeId, task: impl Future + 'static) -> TaskId { let mut tasks = self.tasks.borrow_mut(); - let entry = tasks.vacant_entry(); let task_id = TaskId(entry.key()); - entry.insert(LocalTask { + entry.insert(Rc::new(LocalTask { id: task_id, tx: self.sender.clone(), - task: Box::into_raw(Box::new(task)), + task: UnsafeCell::new(Box::pin(task)), scope, - }); + })); self.sender .unbounded_send(SchedulerMsg::TaskNotified(task_id)) @@ -54,28 +61,15 @@ impl HandleInner { } } -pub fn make_task_waker(task: &LocalTask) -> Waker { - let raw = RawWaker::new(task as *const LocalTask as *const _, task_vtable()); - unsafe { Waker::from_raw(raw) } +pub fn make_task_waker(task: Rc) -> Waker { + let ptr = Rc::into_raw(task).cast::<()>(); + super::waker::make_rc_waker(task) } -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 LocalTask); - task.tx - .unbounded_send(SchedulerMsg::TaskNotified(task.id)) - .expect("Scheduler should exist"); -} - -unsafe fn drop_task(_data: *const ()) { - // doesnt do anything +impl RcWake for LocalTask { + fn wake_by_ref(arc_self: &Rc) { + _ = arc_self + .tx + .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)); + } } diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 0071d1cb7..aa5eaa6fb 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,7 +1,5 @@ -use std::{ops::DerefMut, pin::Pin}; - use futures_task::Context; -use futures_util::StreamExt; +use futures_util::{FutureExt, StreamExt}; use crate::{innerlude::make_task_waker, VirtualDom}; @@ -13,38 +11,28 @@ impl VirtualDom { /// This is cancel safe, so if the future is dropped, you can push events into the virtualdom pub async fn wait_for_work(&mut self) { loop { - let msg = self.scheduler.rx.next().await.unwrap(); - - println!("msg received: {:?}", msg); - - match msg { + match self.scheduler.rx.next().await.unwrap() { SchedulerMsg::Event => todo!(), SchedulerMsg::Immediate(_) => todo!(), SchedulerMsg::DirtyAll => todo!(), + SchedulerMsg::TaskNotified(id) => { let mut tasks = self.scheduler.handle.tasks.borrow_mut(); let local_task = &tasks[id.0]; - // // attach the waker to itself - let waker = make_task_waker(local_task); + // 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 mut cx = Context::from_waker(&waker); - let mut fut = unsafe { &mut *local_task.task }; - let pinned = unsafe { Pin::new_unchecked(fut.deref_mut()) }; + // safety: the waker owns its task and everythig is single threaded + let fut = unsafe { &mut *local_task.task.get() }; - match pinned.poll(&mut cx) { - futures_task::Poll::Ready(_) => { - // remove the task - tasks.remove(id.0); - } - futures_task::Poll::Pending => {} - } - - if tasks.is_empty() { - return; + if let futures_task::Poll::Ready(_) = fut.poll_unpin(&mut cx) { + tasks.remove(id.0); } } - // SchedulerMsg::TaskNotified(id) => {}, + SchedulerMsg::SuspenseNotified(_) => todo!(), } } diff --git a/packages/core/src/scheduler/waker.rs b/packages/core/src/scheduler/waker.rs new file mode 100644 index 000000000..bba60efe5 --- /dev/null +++ b/packages/core/src/scheduler/waker.rs @@ -0,0 +1,54 @@ +use std::{ + cell::{RefCell, UnsafeCell}, + marker::PhantomData, + mem::{self, MaybeUninit}, + ops::DerefMut, + pin::Pin, + process::Output, + rc::Rc, + sync::Arc, +}; + +use futures_task::{waker, RawWaker, RawWakerVTable, Waker}; + +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 5b9ef9004..4d88bb54d 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -8,7 +8,7 @@ use crate::{ bump_frame::BumpFrame, factory::RenderReturn, scopes::{ScopeId, ScopeState}, - virtualdom::VirtualDom, + virtualdom::VirtualDom, innerlude::SuspenseLeaf, }; impl VirtualDom { @@ -68,11 +68,25 @@ impl VirtualDom { 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()); + // use futures_util::FutureExt; + + let leaves = self.scheduler.handle.leaves.borrow_mut(); + + leaves.insert(Rc::new(SuspenseLeaf { + id: todo!(), + scope: todo!(), + boundary: todo!(), + tx: todo!(), + task: todo!(), + })); + + let waker = crate::scheduler::make_suspense_waker(task); match fut.poll_unpin(&mut cx) { - std::task::Poll::Ready(nodes) => RenderReturn::Sync(nodes), + std::task::Poll::Ready(nodes) => { + leaves.remove(key;) + RenderReturn::Sync(nodes) + }, std::task::Poll::Pending => new_nodes, } } diff --git a/packages/core/tests/suspend.rs b/packages/core/tests/suspend.rs new file mode 100644 index 000000000..b46244c90 --- /dev/null +++ b/packages/core/tests/suspend.rs @@ -0,0 +1,38 @@ +use std::{cell::Cell, ptr::null_mut, time::Duration}; + +use dioxus_core::*; + +#[tokio::test] +async fn it_works() { + let mut dom = VirtualDom::new(app); + + let mut mutations = vec![]; + dom.rebuild(&mut mutations); + + println!("mutations: {:?}", mutations); + + dom.wait_for_work().await; +} + +fn app(cx: Scope) -> Element { + let dy = cx.component(async_child, (), "async_child"); + VNode::single_component(&cx, dy, "app") +} + +async fn async_child(cx: Scope<'_>) -> Element { + println!("rendering async child"); + + let fut = cx.use_hook(|| { + Box::pin(async { + println!("Starting sleep"); + tokio::time::sleep(Duration::from_secs(1)).await; + println!("Sleep ended"); + }) + }); + + fut.await; + + println!("Future awaited"); + + None +} diff --git a/packages/core/tests/task.rs b/packages/core/tests/task.rs index abf453fa1..04ac5f95a 100644 --- a/packages/core/tests/task.rs +++ b/packages/core/tests/task.rs @@ -17,14 +17,14 @@ async fn it_works() { fn app(cx: Scope) -> Element { cx.spawn(async { for x in 0..10 { - tokio::time::sleep(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_millis(500)).await; println!("Hello, world! {x}"); } }); cx.spawn(async { for x in 0..10 { - tokio::time::sleep(Duration::from_millis(500)).await; + tokio::time::sleep(Duration::from_millis(250)).await; println!("Hello, world does! {x}"); } });