feat: abstract to rc wake

This commit is contained in:
Jonathan Kelley 2022-11-06 14:28:41 -08:00
parent a38fc9e4ab
commit e6c53803a6
12 changed files with 188 additions and 67 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ mod handle;
mod suspense;
mod task;
mod wait;
mod waker;
pub use handle::*;
use slab::Slab;

View file

@ -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) }
pub fn make_task_waker(task: Rc<LocalTask>) -> 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<Self>) {
_ = arc_self
.tx
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
}
}

View file

@ -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!(),
}
}

View file

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

View file

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

View file

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

View file

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