diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 2f418701b..2b58ae251 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true -} \ No newline at end of file + "rust-analyzer.inlayHints.enable": false +} diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index e1383bd1e..daf17b964 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -42,6 +42,7 @@ futures = "0.3.15" async-std = { version="1.9.0", features=["attributes"] } +appendlist = "1.4.0" [features] default = ["serialize"] diff --git a/packages/core/examples/async.rs b/packages/core/examples/async.rs new file mode 100644 index 000000000..c5a718075 --- /dev/null +++ b/packages/core/examples/async.rs @@ -0,0 +1,21 @@ +use std::pin::Pin; + +use dioxus_core::prelude::*; +use futures::Future; + +fn main() {} + +const App: FC<()> = |cx| { + let mut fut = cx.use_hook( + || { + // + Box::pin(async { loop {} }) as Pin>> + }, + |f| f, + |_| {}, + ); + + cx.submit_task(fut); + + todo!() +}; diff --git a/packages/core/src/hooklist.rs b/packages/core/src/hooklist.rs new file mode 100644 index 000000000..64f77e6fc --- /dev/null +++ b/packages/core/src/hooklist.rs @@ -0,0 +1,65 @@ +use std::{ + any::Any, + cell::{Cell, UnsafeCell}, +}; + +pub struct HookList { + vals: appendlist::AppendList>>, + idx: Cell, +} + +impl Default for HookList { + fn default() -> Self { + Self { + vals: Default::default(), + idx: Cell::new(0), + } + } +} + +struct InnerHook { + cell: UnsafeCell, +} +impl InnerHook { + fn new(new: T) -> Self { + Self { + cell: UnsafeCell::new(new), + } + } +} + +impl HookList { + pub(crate) fn push(&self, new: T) { + self.vals.push(InnerHook::new(Box::new(new))) + } + + pub(crate) fn next(&self) -> Option<&mut T> { + self.vals.get(self.idx.get()).and_then(|inn| { + self.idx.set(self.idx.get() + 1); + let mut raw_box = unsafe { &mut *inn.cell.get() }; + raw_box.downcast_mut::() + }) + } + + /// 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 + pub(crate) unsafe fn reset(&mut self) { + self.idx.set(0); + } + + #[inline] + pub(crate) fn len(&self) -> usize { + self.vals.len() + } + + #[inline] + pub(crate) fn cur_idx(&self) -> usize { + self.idx.get() + } + + #[inline] + pub(crate) fn is_finished(&self) -> bool { + self.idx.get() == self.vals.len() + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index b74c641ef..9cebae9e1 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -13,10 +13,11 @@ pub mod serialize; pub mod arena; pub mod component; +pub mod hooklist; pub mod styles; +pub mod tasks; pub mod util; // Logic for extending FC - -// pub mod debug_renderer; + // pub mod debug_renderer; pub mod diff; pub mod error; // Error type we expose to the renderers diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e4c25b129..e116a32b6 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -427,6 +427,7 @@ fn create_closure<'a, P: 'a>( let cx: Context

= Context { props: safe_props, scope: scp, + tasks: todo!(), }; let g = component(cx); diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs new file mode 100644 index 000000000..88d4ce8d9 --- /dev/null +++ b/packages/core/src/tasks.rs @@ -0,0 +1,38 @@ +//! The TaskQueue serves as a centralized async store for all tasks in Dioxus. +//! When a component renders, it may submit an async task to the queue. +//! +//! Then the task complete, it is emitted from the virtual dom in the event loop, which is then fed back into the virtualdom +//! as an event trigger. +//! +//! When a component is scheduled to re-render, the awaing task must be dumped from the queue. +//! +//! This is all pretty unsafe stuff. +//! The major invariant here is that tasks that enter the queue may be invalidated during transitions. + +use std::pin::Pin; + +use futures::{Future, Stream, StreamExt}; +use slotmap::{DefaultKey, SlotMap}; + +use crate::events::EventTrigger; + +pub struct TaskQueue { + slots: SlotMap, +} + +impl TaskQueue { + unsafe fn push_task(&mut self, task: Task) -> TaskHandle { + todo!() + } + + async fn next(&mut self) -> EventTrigger { + for (key, task) in self.slots.iter_mut() { + let ptr = task.0; + } + todo!() + } +} + +struct Task(*mut Pin>>); + +struct TaskHandle {} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 42c7023c8..a79a82ede 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -19,11 +19,14 @@ //! This module includes just the barebones for a complete VirtualDOM API. //! Additional functionality is defined in the respective files. +use crate::hooklist::HookList; use crate::{arena::ScopeArena, innerlude::*}; +use appendlist::AppendList; use bumpalo::Bump; use futures::FutureExt; use slotmap::DefaultKey; use slotmap::SlotMap; +use std::marker::PhantomData; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -159,7 +162,11 @@ impl VirtualDom { // the lifetime of this closure is just as long as the lifetime on the scope reference // this closure moves root props (which is static) into this closure let props = unsafe { &*(&root_props as *const _) }; - root(Context { props, scope }) + root(Context { + props, + scope, + tasks: todo!(), + }) }); // Create a weak reference to the OpaqueComponent for the root scope to use as its render function @@ -379,8 +386,6 @@ pub struct Scope { pub caller: Weak, - pub hookidx: Cell, - // ========================== // slightly unsafe stuff // ========================== @@ -391,8 +396,8 @@ pub struct Scope { // These two could be combined with "OwningRef" to remove unsafe usage // or we could dedicate a tiny bump arena just for them // could also use ourborous - hooks: RefCell>, - + hooks: HookList, + // hooks: RefCell>, pub(crate) listener_idx: Cell, // Unsafety: @@ -410,7 +415,7 @@ pub struct Scope { } // We need to pin the hook so it doesn't move as we initialize the list of hooks -type Hook = Pin>; +type Hook = Box; type EventChannel = Rc; impl Scope { @@ -470,7 +475,6 @@ impl Scope { hooks: Default::default(), shared_contexts: Default::default(), listeners: Default::default(), - hookidx: Default::default(), descendents: Default::default(), suspended_tasks: Default::default(), } @@ -510,7 +514,7 @@ impl Scope { // Remove all the outdated listeners self.listeners.borrow_mut().clear(); - self.hookidx.set(0); + unsafe { self.hooks.reset() }; self.listener_idx.set(0); let caller = self @@ -611,7 +615,13 @@ impl Scope { pub struct Context<'src, T> { pub props: &'src T, pub scope: &'src Scope, + pub tasks: &'src AppendList<&'src mut DTask>, + // pub task: &'src RefCell>, } +pub type DTask = Pin>>; +// // pub task: &'src RefCell>>>>, +// pub task: Option<()>, // pub task: &'src RefCell>>>>, +// pub task: &'src RefCell>>>> impl<'src, T> Copy for Context<'src, T> {} impl<'src, T> Clone for Context<'src, T> { @@ -619,6 +629,7 @@ impl<'src, T> Clone for Context<'src, T> { Self { props: self.props, scope: self.scope, + tasks: todo!(), } } } @@ -712,29 +723,13 @@ impl<'src, P> Context<'src, P> { // TODO: add this to the "clean up" group for when the component is dropped _cleanup: impl FnOnce(InternalHookState), ) -> Output { - let scope = self.scope; - - let idx = scope.hookidx.get(); - - // Grab out the hook list - let mut hooks = scope.hooks.borrow_mut(); - // If the idx is the same as the hook length, then we need to add the current hook - if idx >= hooks.len() { + if self.scope.hooks.is_finished() { let new_state = initializer(); - hooks.push(Box::pin(new_state)); + self.scope.hooks.push(Box::new(new_state)); } - scope.hookidx.set(idx + 1); - - let stable_ref = hooks - .get_mut(idx) - .expect("Should not fail, idx is validated") - .as_mut(); - - let pinned_state = unsafe { Pin::get_unchecked_mut(stable_ref) }; - - let internal_state = pinned_state.downcast_mut::().expect( + let state = self.scope.hooks.next::().expect( r###" Unable to retrive the hook that was initialized in this index. Consult the `rules of hooks` to understand how to use hooks properly. @@ -744,8 +739,7 @@ Any function prefixed with "use" should not be called conditionally. "###, ); - // We extend the lifetime of the internal state - runner(unsafe { &mut *(internal_state as *mut _) }) + runner(state) } /// This hook enables the ability to expose state to children further down the VirtualDOM Tree. @@ -882,16 +876,21 @@ Any function prefixed with "use" should not be called conditionally. /// pub fn submit_task( &self, - task: &'src mut Pin + 'static>>, + mut task: &'src mut Pin + 'static>>, ) -> TaskHandle { + self.tasks.push(task); + // let mut g = self.task.borrow_mut(); + // *g = Some(task); // the pointer to the task is stable - we guarantee stability of all &'src references - let task_ptr = task as *mut _; + // let task_ptr = task as *mut _; - TaskHandle {} + TaskHandle { _p: PhantomData {} } } } -pub struct TaskHandle {} +pub struct TaskHandle<'src> { + _p: PhantomData<&'src ()>, +} #[derive(Clone)] pub struct SuspendedContext {}