mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
feat: abstract to rc wake
This commit is contained in:
parent
a38fc9e4ab
commit
e6c53803a6
12 changed files with 188 additions and 67 deletions
|
@ -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
|
||||
|
|
|
@ -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()};
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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>>,
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -6,6 +6,7 @@ mod handle;
|
|||
mod suspense;
|
||||
mod task;
|
||||
mod wait;
|
||||
mod waker;
|
||||
|
||||
pub use handle::*;
|
||||
use slab::Slab;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
packages/core/src/scheduler/waker.rs
Normal file
54
packages/core/src/scheduler/waker.rs
Normal 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>()))
|
||||
}
|
|
@ -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
packages/core/tests/suspend.rs
Normal file
38
packages/core/tests/suspend.rs
Normal 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
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue