feat: more suspended nodes!
This commit is contained in:
1 parent
c52af221f7
commit
de9f61bcf4
20 files changed
+644
-148
No files matched your search
@@ -1,7 +1,9 @@
|
||||
use crate::innerlude::*;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use futures_util::FutureExt;
|
||||
|
||||
use std::any::Any;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use std::{
|
||||
@@ -134,13 +136,13 @@ impl<'src, P> Context<'src, P> {
|
||||
where
|
||||
State: 'static,
|
||||
Output: 'src,
|
||||
Init: FnOnce() -> State,
|
||||
Init: FnOnce(usize) -> State,
|
||||
Run: FnOnce(&'src mut State) -> Output,
|
||||
Cleanup: FnOnce(State),
|
||||
{
|
||||
// If the idx is the same as the hook length, then we need to add the current hook
|
||||
if self.scope.hooks.at_end() {
|
||||
let new_state = initializer();
|
||||
let new_state = initializer(self.scope.hooks.len());
|
||||
self.scope.hooks.push(new_state);
|
||||
}
|
||||
|
||||
@@ -172,7 +174,7 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
let ty = TypeId::of::<T>();
|
||||
|
||||
let is_initialized = self.use_hook(
|
||||
|| false,
|
||||
|_| false,
|
||||
|s| {
|
||||
let i = s.clone();
|
||||
*s = true;
|
||||
@@ -208,7 +210,7 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
}
|
||||
|
||||
self.use_hook(
|
||||
move || UseContextHook {
|
||||
move |_| UseContextHook {
|
||||
par: None as Option<Rc<T>>,
|
||||
we: None as Option<Weak<T>>,
|
||||
},
|
||||
@@ -299,10 +301,9 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
/// Awaits the given task, forcing the component to re-render when the value is ready.
|
||||
///
|
||||
///
|
||||
pub fn use_task<Out, Fut, Init>(
|
||||
&self,
|
||||
task_initializer: Init,
|
||||
) -> (&TaskHandle, &mut Option<Out>)
|
||||
///
|
||||
///
|
||||
pub fn use_task<Out, Fut, Init>(&self, task_initializer: Init) -> (&TaskHandle, &Option<Out>)
|
||||
where
|
||||
Out: 'static,
|
||||
Fut: Future<Output = Out> + 'static,
|
||||
@@ -315,7 +316,7 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
|
||||
// whenever the task is complete, save it into th
|
||||
self.use_hook(
|
||||
move || {
|
||||
move |hook_idx| {
|
||||
let task_fut = task_initializer();
|
||||
|
||||
let task_dump = Rc::new(RefCell::new(None));
|
||||
@@ -328,7 +329,7 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
*slot.as_ref().borrow_mut() = Some(output);
|
||||
update();
|
||||
EventTrigger {
|
||||
event: VirtualEvent::FiberEvent,
|
||||
event: VirtualEvent::AsyncEvent { hook_idx },
|
||||
originator,
|
||||
priority: EventPriority::Low,
|
||||
real_node_id: None,
|
||||
@@ -344,12 +345,20 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
|
||||
hook.value = Some(val);
|
||||
}
|
||||
(&TaskHandle { _p: PhantomData }, &mut hook.value)
|
||||
(&TaskHandle { _p: PhantomData }, &hook.value)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SuspenseHook {
|
||||
pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
|
||||
pub callback: SuspendedCallback,
|
||||
}
|
||||
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> VNode<'a>>;
|
||||
|
||||
impl<'src, P> Context<'src, P> {
|
||||
/// Asynchronously render new nodes once the given future has completed.
|
||||
///
|
||||
/// # Easda
|
||||
@@ -360,18 +369,67 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
/// # Example
|
||||
///
|
||||
///
|
||||
pub fn use_suspense<Out, Fut: 'static>(
|
||||
pub fn use_suspense<Out, Fut, Cb>(
|
||||
&'src self,
|
||||
_task_initializer: impl FnOnce() -> Fut,
|
||||
_callback: impl FnOnce(SuspendedContext, Out) -> VNode<'src> + 'src,
|
||||
task_initializer: impl FnOnce() -> Fut,
|
||||
user_callback: Cb,
|
||||
) -> VNode<'src>
|
||||
where
|
||||
Out: 'src,
|
||||
Fut: Future<Output = Out>,
|
||||
Fut: Future<Output = Out> + 'static,
|
||||
Out: 'static,
|
||||
Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> VNode<'a> + 'static,
|
||||
{
|
||||
// self.use_hook(|| , runner, cleanup)
|
||||
self.use_hook(
|
||||
move |hook_idx| {
|
||||
let value = Rc::new(RefCell::new(None));
|
||||
|
||||
todo!()
|
||||
let slot = value.clone();
|
||||
let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
|
||||
let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
|
||||
let v: &dyn Any = v.as_ref().unwrap().as_ref();
|
||||
let real_val = v.downcast_ref::<Out>().unwrap();
|
||||
user_callback(ctx, real_val)
|
||||
});
|
||||
|
||||
let originator = self.scope.arena_idx.clone();
|
||||
let task_fut = task_initializer();
|
||||
|
||||
let slot = value.clone();
|
||||
self.submit_task(Box::pin(task_fut.then(move |output| async move {
|
||||
// When the new value arrives, set the hooks internal slot
|
||||
// Dioxus will call the user's callback to generate new nodes outside of the diffing system
|
||||
*slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
|
||||
EventTrigger {
|
||||
event: VirtualEvent::SuspenseEvent { hook_idx },
|
||||
originator,
|
||||
priority: EventPriority::Low,
|
||||
real_node_id: None,
|
||||
}
|
||||
})));
|
||||
|
||||
SuspenseHook { value, callback }
|
||||
},
|
||||
move |hook| {
|
||||
match hook.value.borrow().as_ref() {
|
||||
Some(val) => {
|
||||
let cx = SuspendedContext {
|
||||
bump: &self.scope.cur_frame().bump,
|
||||
};
|
||||
(&hook.callback)(cx)
|
||||
}
|
||||
None => {
|
||||
//
|
||||
VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Suspended,
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,4 +437,6 @@ pub struct TaskHandle<'src> {
|
||||
_p: PhantomData<&'src ()>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct SuspendedContext {}
|
||||
pub struct SuspendedContext<'a> {
|
||||
pub bump: &'a Bump,
|
||||
}
|
||||
@@ -77,7 +77,8 @@ use DomEdit::*;
|
||||
impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||
// Navigation
|
||||
pub(crate) fn push(&mut self, root: RealDomNode) {
|
||||
self.edits.push(PushRoot { root });
|
||||
let id = root.as_u64();
|
||||
self.edits.push(PushRoot { id });
|
||||
}
|
||||
pub(crate) fn pop(&mut self) {
|
||||
self.edits.push(PopRoot {});
|
||||
@@ -105,6 +106,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||
|
||||
// Create
|
||||
pub(crate) fn create_text_node(&mut self, text: &'bump str, id: RealDomNode) {
|
||||
let id = id.as_u64();
|
||||
self.edits.push(CreateTextNode { text, id });
|
||||
}
|
||||
pub(crate) fn create_element(
|
||||
@@ -113,6 +115,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||
ns: Option<&'static str>,
|
||||
id: RealDomNode,
|
||||
) {
|
||||
let id = id.as_u64();
|
||||
match ns {
|
||||
Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }),
|
||||
None => self.edits.push(CreateElement { id, tag }),
|
||||
@@ -121,6 +124,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||
|
||||
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
||||
pub(crate) fn create_placeholder(&mut self, id: RealDomNode) {
|
||||
let id = id.as_u64();
|
||||
self.edits.push(CreatePlaceholder { id });
|
||||
}
|
||||
|
||||
@@ -136,7 +140,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||
scope,
|
||||
event,
|
||||
idx: element_id,
|
||||
node: realnode,
|
||||
node: realnode.as_u64(),
|
||||
});
|
||||
}
|
||||
pub(crate) fn remove_event_listener(&mut self, event: &'static str) {
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
|
||||
//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
|
||||
|
||||
|
||||
|
||||
use crate::innerlude::{RealDomNode, ScopeIdx};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -24,10 +22,10 @@ pub struct EventTrigger {
|
||||
}
|
||||
|
||||
impl EventTrigger {
|
||||
pub fn new_from_task(originator: ScopeIdx) -> Self {
|
||||
pub fn new_from_task(originator: ScopeIdx, hook_idx: usize) -> Self {
|
||||
Self {
|
||||
originator,
|
||||
event: VirtualEvent::FiberEvent,
|
||||
event: VirtualEvent::AsyncEvent { hook_idx },
|
||||
priority: EventPriority::Low,
|
||||
real_node_id: None,
|
||||
}
|
||||
@@ -103,11 +101,18 @@ pub enum VirtualEvent {
|
||||
MouseEvent(on::MouseEvent),
|
||||
PointerEvent(on::PointerEvent),
|
||||
|
||||
// Whenever a task is ready (complete) Dioxus produces this "FiberEvent"
|
||||
FiberEvent,
|
||||
|
||||
// image event has conflicting method types
|
||||
// ImageEvent(event_data::ImageEvent),
|
||||
|
||||
// Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
|
||||
//
|
||||
// Async events don't necessarily propagate into a scope being ran. It's up to the event itself
|
||||
// to force an update for itself.
|
||||
AsyncEvent { hook_idx: usize },
|
||||
|
||||
// These are more intrusive than the rest
|
||||
SuspenseEvent { hook_idx: usize },
|
||||
|
||||
OtherEvent,
|
||||
}
|
||||
|
||||
@@ -159,10 +164,12 @@ pub mod on {
|
||||
|
||||
$(
|
||||
$(#[$method_attr])*
|
||||
pub fn $name<'a, F: FnMut($wrapper) + 'a>(
|
||||
pub fn $name<'a, F>(
|
||||
c: NodeFactory<'a>,
|
||||
mut callback: F,
|
||||
) -> Listener<'a> {
|
||||
) -> Listener<'a>
|
||||
where F: FnMut($wrapper) + 'a
|
||||
{
|
||||
let bump = &c.bump();
|
||||
Listener {
|
||||
event: stringify!($name),
|
||||
|
||||
@@ -29,8 +29,20 @@ impl<T> InnerHook<T> {
|
||||
}
|
||||
|
||||
impl HookList {
|
||||
pub(crate) fn push<T: 'static>(&self, new: T) {
|
||||
self.vals.push(InnerHook::new(Box::new(new)))
|
||||
/// Unsafely get a mutable reference to any of the hooks
|
||||
///
|
||||
/// This is unsafe because an &mut T might be aliased if the hook data is already borrowed/in use in the component
|
||||
///
|
||||
/// This method should be reserved for internal methods that are guaranteed that this hook is not aliased anyhwere
|
||||
/// inside the component body, or outside into children components.
|
||||
///
|
||||
/// This method is currently used only by the suspense system whose hook implementation guarantees that all &T is dropped
|
||||
/// before the suspense handler is ran.
|
||||
pub(crate) unsafe fn get_mut<T: 'static>(&self, idx: usize) -> Option<&mut T> {
|
||||
self.vals.get(idx).and_then(|inn| {
|
||||
let raw_box = unsafe { &mut *inn.cell.get() };
|
||||
raw_box.downcast_mut::<T>()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
|
||||
@@ -41,9 +53,17 @@ impl HookList {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push<T: 'static>(&self, new: T) {
|
||||
self.vals.push(InnerHook::new(Box::new(new)))
|
||||
}
|
||||
|
||||
/// This resets the internal iterator count
|
||||
/// It's okay that we've given out each hook, but now we have the opportunity to give it out again
|
||||
/// Therefore, resetting is cosudered unsafe
|
||||
///
|
||||
/// This should only be ran by Dioxus itself before "running scope".
|
||||
/// Dioxus knows how to descened through the tree to prevent mutable aliasing.
|
||||
pub(crate) unsafe fn reset(&mut self) {
|
||||
self.idx.set(0);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ impl Scope {
|
||||
..
|
||||
} = trigger;
|
||||
|
||||
if let &VirtualEvent::FiberEvent = &event {
|
||||
if let &VirtualEvent::AsyncEvent { .. } = &event {
|
||||
log::info!("arrived a fiber event");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//!
|
||||
//!
|
||||
|
||||
use crate::{innerlude::ScopeIdx, RealDomNode};
|
||||
use crate::innerlude::ScopeIdx;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
|
||||
@@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[serde(tag = "type")]
|
||||
pub enum DomEdit<'bump> {
|
||||
PushRoot {
|
||||
root: RealDomNode,
|
||||
id: u64,
|
||||
},
|
||||
PopRoot,
|
||||
AppendChildren {
|
||||
@@ -29,24 +29,24 @@ pub enum DomEdit<'bump> {
|
||||
RemoveAllChildren,
|
||||
CreateTextNode {
|
||||
text: &'bump str,
|
||||
id: RealDomNode,
|
||||
id: u64,
|
||||
},
|
||||
CreateElement {
|
||||
tag: &'bump str,
|
||||
id: RealDomNode,
|
||||
id: u64,
|
||||
},
|
||||
CreateElementNs {
|
||||
tag: &'bump str,
|
||||
id: RealDomNode,
|
||||
id: u64,
|
||||
ns: &'static str,
|
||||
},
|
||||
CreatePlaceholder {
|
||||
id: RealDomNode,
|
||||
id: u64,
|
||||
},
|
||||
NewEventListener {
|
||||
event: &'static str,
|
||||
scope: ScopeIdx,
|
||||
node: RealDomNode,
|
||||
node: u64,
|
||||
idx: usize,
|
||||
},
|
||||
RemoveEventListener {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use slotmap::{DefaultKey, KeyData};
|
||||
use slotmap::{DefaultKey, Key, KeyData};
|
||||
|
||||
use crate::innerlude::*;
|
||||
|
||||
@@ -66,13 +66,16 @@ impl RealDomNode {
|
||||
let key: DefaultKey = data.into();
|
||||
Self(key)
|
||||
}
|
||||
pub fn empty_cell() -> Cell<Self> {
|
||||
Cell::new(Self::empty())
|
||||
}
|
||||
pub fn from_u64(id: u64) -> Self {
|
||||
let data = KeyData::from_ffi(id);
|
||||
let key: DefaultKey = data.into();
|
||||
Self(key)
|
||||
}
|
||||
pub fn empty_cell() -> Cell<Self> {
|
||||
Cell::new(Self::empty())
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
self.0.data().as_ffi()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -297,10 +297,29 @@ impl VirtualDom {
|
||||
match &trigger.event {
|
||||
VirtualEvent::OtherEvent => todo!(),
|
||||
|
||||
// Fiber events
|
||||
// VirtualEvent::FiberEvent => {
|
||||
// //
|
||||
// }
|
||||
// Nothing yet
|
||||
VirtualEvent::AsyncEvent { .. } => {}
|
||||
|
||||
// Suspense Events! A component's suspended node is updated
|
||||
VirtualEvent::SuspenseEvent { hook_idx } => {
|
||||
let scope = self.components.try_get_mut(trigger.originator).unwrap();
|
||||
|
||||
// safety: we are sure that there are no other references to the inner content of this hook
|
||||
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
||||
|
||||
let cx = SuspendedContext {
|
||||
bump: &scope.cur_frame().bump,
|
||||
};
|
||||
|
||||
// generate the new node!
|
||||
let callback: VNode<'s> = (&hook.callback)(cx);
|
||||
|
||||
// diff that node with the node that was originally suspended!
|
||||
|
||||
// hook.callback;
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
// This is the "meat" of our cooperative scheduler
|
||||
// As updates flow in, we re-evalute the event queue and decide if we should be switching the type of work
|
||||
|
||||
Reference in new issue
Block a user