Selaa lähdekoodia

feat: abstract to rc wake

Jonathan Kelley 2 vuotta sitten
vanhempi
commit
e6c53803a6

+ 6 - 1
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::<SuspenseContext>().unwrap();
 
                         // try to poll the future once - many times it will be ready immediately or require little to no work

+ 1 - 1
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()};
 

+ 1 - 1
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),
         }

+ 25 - 2
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<Self> {
+        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<Option<ElementId>>,
     },

+ 6 - 2
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<SchedulerMsg>,
-    pub tasks: RefCell<Slab<LocalTask>>,
-    pub leaves: RefCell<Slab<SuspenseLeaf>>,
+
+    /// Tasks created with cx.spawn
+    pub tasks: RefCell<Slab<Rc<LocalTask>>>,
+
+    /// Async components
+    pub leaves: RefCell<Slab<Rc<SuspenseLeaf>>>,
 }
 
 impl SchedulerHandle {

+ 1 - 0
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;

+ 25 - 31
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<SchedulerMsg>,
-    pub task: *mut dyn Future<Output = ()>,
+    pub task: UnsafeCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
 }
 
 impl HandleInner {
     pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + '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) }
-}
-
-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");
+pub fn make_task_waker(task: Rc<LocalTask>) -> Waker {
+    let ptr = Rc::into_raw(task).cast::<()>();
+    super::waker::make_rc_waker(task)
 }
 
-unsafe fn drop_task(_data: *const ()) {
-    // doesnt do anything
+impl RcWake for LocalTask {
+    fn wake_by_ref(arc_self: &Rc<Self>) {
+        _ = arc_self
+            .tx
+            .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
+    }
 }

+ 11 - 23
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!(),
             }
         }

+ 54 - 0
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>) {
+        Self::wake_by_ref(&self)
+    }
+    fn wake_by_ref(arc_self: &Rc<Self>);
+}
+
+pub fn make_rc_waker<T: RcWake>(rc: Rc<T>) -> Waker {
+    unsafe { Waker::from_raw(RawWaker::new(Rc::into_raw(rc).cast(), rc_vtable::<T>())) }
+}
+
+fn rc_vtable<T: RcWake>() -> &'static RawWakerVTable {
+    &RawWakerVTable::new(
+        clone_rc_raw::<T>,
+        wake_rc_raw::<T>,
+        wake_by_ref_rc_raw::<T>,
+        drop_rc_raw::<T>,
+    )
+}
+
+// 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<T: RcWake>(data: *const ()) -> RawWaker {
+    let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
+    let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
+    RawWaker::new(data, rc_vtable::<T>())
+}
+
+unsafe fn wake_rc_raw<T: RcWake>(data: *const ()) {
+    let arc: Rc<T> = Rc::from_raw(data.cast::<T>());
+    arc.wake();
+}
+
+unsafe fn wake_by_ref_rc_raw<T: RcWake>(data: *const ()) {
+    let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
+    arc.wake();
+}
+
+unsafe fn drop_rc_raw<T: RcWake>(data: *const ()) {
+    drop(Rc::<T>::from_raw(data.cast::<T>()))
+}

+ 18 - 4
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,
                 }
             }

+ 38 - 0
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
+}

+ 2 - 2
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}");
         }
     });