From 58ab51a4e4e8a2a7b9b03a4c9a136648112d39dc Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 9 Jul 2021 01:42:26 -0400 Subject: [PATCH] wip: more refactor --- packages/core/src/component.rs | 21 +-- packages/core/src/context.rs | 10 +- packages/core/src/lib.rs | 2 + packages/core/src/nodebuilder.rs | 4 +- packages/core/src/scope.rs | 250 +++++++++++++++++++++++++++++++ packages/core/src/virtual_dom.rs | 232 ---------------------------- 6 files changed, 261 insertions(+), 258 deletions(-) create mode 100644 packages/core/src/scope.rs diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 26341516e..f96cecc3e 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -43,29 +43,14 @@ pub fn fc_to_builder(_: FC) -> T::Builder { } /// Create inline fragments -/// -- +/// ------------------------ /// /// Fragments capture a series of children without rendering extra nodes. /// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. /// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash. use crate::prelude::*; +#[allow(non_upper_case_globals, non_snake_case)] pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> { - let childs: &'a [VNode<'a>] = cx.children(); - cx.render(LazyNodes::new({ - move |f| { - // - f.fragment_from_iter(childs) - } - })) + cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children()))) } -// #[allow(non_upper_case_globals)] -// pub const Fragment: FC<()> = |cx| { -// use crate::prelude::*; -// let childs = cx.children(); -// cx.render(LazyNodes::new(move |c| { -// c.fragment_from_iter(childs) -// // c.text(format_args!("")) -// // crate::nodebuilder::vfragment(c, None, cx.children()) -// })) -// }; diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index ab85ac060..4e6d0ddff 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -16,6 +16,7 @@ use std::{ pin::Pin, rc::{Rc, Weak}, }; + /// Components in Dioxus use the "Context" object to interact with their lifecycle. /// This lets components schedule updates, integrate hooks, and expose their context via the context api. /// @@ -40,12 +41,9 @@ 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> { @@ -53,7 +51,7 @@ impl<'src, T> Clone for Context<'src, T> { Self { props: self.props, scope: self.scope, - tasks: todo!(), + tasks: self.tasks, } } } @@ -145,7 +143,7 @@ impl<'src, P> Context<'src, P> { // The closure that cleans up whatever mess is left when the component gets torn down // TODO: add this to the "clean up" group for when the component is dropped - _cleanup: impl FnOnce(InternalHookState), + cleanup: impl FnOnce(InternalHookState), ) -> Output { // If the idx is the same as the hook length, then we need to add the current hook if self.scope.hooks.is_finished() { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 72d89fc45..19972f68f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -22,6 +22,7 @@ pub mod hooklist; pub mod hooks; pub mod nodebuilder; pub mod nodes; +pub mod scope; pub mod signals; pub mod styles; pub mod tasks; @@ -43,6 +44,7 @@ pub(crate) mod innerlude { pub use crate::hooks::*; pub use crate::nodebuilder::*; pub use crate::nodes::*; + pub use crate::scope::*; pub use crate::util::*; pub use crate::virtual_dom::*; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index f74e12102..d225523d7 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -14,10 +14,10 @@ use bumpalo::Bump; use crate::{ events::VirtualEvent, - innerlude::{Properties, VComponent, VText, FC}, + innerlude::{Properties, Scope, VComponent, VText, FC}, nodes::{Attribute, Listener, NodeKey, VNode}, prelude::{VElement, VFragment}, - virtual_dom::{RealDomNode, Scope}, + virtual_dom::RealDomNode, }; /// A virtual DOM element builder. diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs new file mode 100644 index 000000000..e5ecb95ce --- /dev/null +++ b/packages/core/src/scope.rs @@ -0,0 +1,250 @@ +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}, + collections::{HashMap, HashSet, VecDeque}, + fmt::Debug, + future::Future, + ops::Deref, + pin::Pin, + rc::{Rc, Weak}, +}; + +/// Every component in Dioxus is represented by a `Scope`. +/// +/// Scopes contain the state for hooks, the component's props, and other lifecycle information. +/// +/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. +/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. +pub struct Scope { + // The parent's scope ID + pub parent: Option, + + // IDs of children that this scope has created + // This enables us to drop the children and their children when this scope is destroyed + pub(crate) descendents: RefCell>, + + pub child_nodes: &'static [VNode<'static>], + + // A reference to the list of components. + // This lets us traverse the component list whenever we need to access our parent or children. + pub arena_link: ScopeArena, + + pub shared_contexts: RefCell>>, + + // Our own ID accessible from the component map + pub arena_idx: ScopeIdx, + + pub height: u32, + + pub event_channel: Rc, + + pub caller: Weak, + + // ========================== + // slightly unsafe stuff + // ========================== + // an internal, highly efficient storage of vnodes + pub frames: ActiveFrame, + + // These hooks are actually references into the hook arena + // 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 + pub hooks: HookList, + + pub(crate) listener_idx: Cell, + + // Unsafety: + // - is self-refenrential and therefore needs to point into the bump + // Stores references into the listeners attached to the vnodes + // NEEDS TO BE PRIVATE + pub(crate) listeners: RefCell, *mut dyn FnMut(VirtualEvent))>>, + + pub(crate) suspended_tasks: Vec<*mut Pin>>>>, +} + +// We need to pin the hook so it doesn't move as we initialize the list of hooks +type Hook = Box; +type EventChannel = Rc; + +impl Scope { + // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) + // we are going to break this lifetime by force in order to save it on ourselves. + // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies. + // This should never happen, but is a good check to keep around + // + // Scopes cannot be made anywhere else except for this file + // Therefore, their lifetimes are connected exclusively to the virtual dom + pub fn new<'creator_node>( + caller: Weak, + arena_idx: ScopeIdx, + parent: Option, + height: u32, + event_channel: EventChannel, + arena_link: ScopeArena, + child_nodes: &'creator_node [VNode<'creator_node>], + ) -> Self { + log::debug!( + "New scope created, height is {}, idx is {:?}", + height, + arena_idx + ); + + // The function to run this scope is actually located in the parent's bump arena. + // Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame. + // If children try to run this invalid caller, it *will* result in UB. + // + // During the lifecycle progression process, this caller will need to be updated. Right now, + // until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller + // lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully, + // this is a bit of a hack, but will remain this way until we've figured out a cleaner solution. + // + // Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction. + let caller = unsafe { + std::mem::transmute::< + Weak, + Weak, + // Weak>, + // Weak>, + >(caller) + }; + + let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + + Self { + child_nodes, + caller, + parent, + arena_idx, + height, + event_channel, + arena_link, + listener_idx: Default::default(), + frames: ActiveFrame::new(), + hooks: Default::default(), + shared_contexts: Default::default(), + listeners: Default::default(), + descendents: Default::default(), + suspended_tasks: Default::default(), + } + } + + pub fn update_caller<'creator_node>(&mut self, caller: Weak) { + // pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { + let broken_caller = unsafe { + std::mem::transmute::< + Weak, + Weak, + // Weak>, + // Weak>, + >(caller) + }; + + self.caller = broken_caller; + } + + pub fn update_children<'creator_node>( + &mut self, + child_nodes: &'creator_node [VNode<'creator_node>], + ) { + let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + self.child_nodes = child_nodes; + } + + /// Create a new context and run the component with references from the Virtual Dom + /// This function downcasts the function pointer based on the stored props_type + /// + /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) + pub fn run_scope<'sel>(&'sel mut self) -> Result<()> { + // Cycle to the next frame and then reset it + // This breaks any latent references, invalidating every pointer referencing into it. + self.frames.next().bump.reset(); + + // Remove all the outdated listeners + self.listeners.borrow_mut().clear(); + + unsafe { self.hooks.reset() }; + self.listener_idx.set(0); + + let caller = self + .caller + .upgrade() + .ok_or(Error::FatalInternal("Failed to get caller"))?; + + // Cast the caller ptr from static to one with our own reference + let c2: &OpaqueComponent = caller.as_ref(); + let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) }; + + self.frames.cur_frame_mut().head_node = unsafe { self.own_vnodes(c3) }; + + Ok(()) + } + + // this is its own function so we can preciesly control how lifetimes flow + unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> { + let new_head: VNode<'a> = f(self); + let out: VNode<'static> = std::mem::transmute(new_head); + out + } + + // A safe wrapper around calling listeners + // calling listeners will invalidate the list of listeners + // The listener list will be completely drained because the next frame will write over previous listeners + pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> { + let EventTrigger { + real_node_id, + event, + .. + } = trigger; + + // todo: implement scanning for outdated events + + // Convert the raw ptr into an actual object + // This operation is assumed to be safe + + log::debug!("Calling listeners! {:?}", self.listeners.borrow().len()); + let mut listners = self.listeners.borrow_mut(); + let (_, listener) = listners + .iter() + .find(|(domptr, _)| { + let p = unsafe { &**domptr }; + p.get() == real_node_id + }) + .expect(&format!( + "Failed to find real node with ID {:?}", + real_node_id + )); + + // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster! + unsafe { + let mut listener_fn = &mut **listener; + listener_fn(event); + } + + Ok(()) + } + + pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { + self.frames.current_head_node() + } + + pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { + self.frames.prev_head_node() + } + + pub(crate) fn cur_frame(&self) -> &BumpFrame { + self.frames.cur_frame() + } + + pub(crate) fn root<'a>(&'a self) -> &'a VNode<'a> { + &self.frames.cur_frame().head_node + } +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 348cb27bc..1e08e638e 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -354,235 +354,3 @@ impl VirtualDom { // These impls are actually wrong. The DOM needs to have a mutex implemented. unsafe impl Sync for VirtualDom {} unsafe impl Send for VirtualDom {} - -/// Every component in Dioxus is represented by a `Scope`. -/// -/// Scopes contain the state for hooks, the component's props, and other lifecycle information. -/// -/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. -/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. -pub struct Scope { - // The parent's scope ID - pub parent: Option, - - // IDs of children that this scope has created - // This enables us to drop the children and their children when this scope is destroyed - pub(crate) descendents: RefCell>, - - pub child_nodes: &'static [VNode<'static>], - - // A reference to the list of components. - // This lets us traverse the component list whenever we need to access our parent or children. - pub arena_link: ScopeArena, - - pub shared_contexts: RefCell>>, - - // Our own ID accessible from the component map - pub arena_idx: ScopeIdx, - - pub height: u32, - - pub event_channel: Rc, - - pub caller: Weak, - - // ========================== - // slightly unsafe stuff - // ========================== - // an internal, highly efficient storage of vnodes - pub frames: ActiveFrame, - - // These hooks are actually references into the hook arena - // 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 - pub hooks: HookList, - - pub(crate) listener_idx: Cell, - - // Unsafety: - // - is self-refenrential and therefore needs to point into the bump - // Stores references into the listeners attached to the vnodes - // NEEDS TO BE PRIVATE - pub(crate) listeners: RefCell, *mut dyn FnMut(VirtualEvent))>>, - - pub(crate) suspended_tasks: Vec<*mut Pin>>>>, -} - -// We need to pin the hook so it doesn't move as we initialize the list of hooks -type Hook = Box; -type EventChannel = Rc; - -impl Scope { - // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) - // we are going to break this lifetime by force in order to save it on ourselves. - // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies. - // This should never happen, but is a good check to keep around - // - // Scopes cannot be made anywhere else except for this file - // Therefore, their lifetimes are connected exclusively to the virtual dom - pub fn new<'creator_node>( - caller: Weak, - arena_idx: ScopeIdx, - parent: Option, - height: u32, - event_channel: EventChannel, - arena_link: ScopeArena, - child_nodes: &'creator_node [VNode<'creator_node>], - ) -> Self { - log::debug!( - "New scope created, height is {}, idx is {:?}", - height, - arena_idx - ); - - // The function to run this scope is actually located in the parent's bump arena. - // Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame. - // If children try to run this invalid caller, it *will* result in UB. - // - // During the lifecycle progression process, this caller will need to be updated. Right now, - // until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller - // lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully, - // this is a bit of a hack, but will remain this way until we've figured out a cleaner solution. - // - // Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction. - let caller = unsafe { - std::mem::transmute::< - Weak, - Weak, - // Weak>, - // Weak>, - >(caller) - }; - - let child_nodes = unsafe { std::mem::transmute(child_nodes) }; - - Self { - child_nodes, - caller, - parent, - arena_idx, - height, - event_channel, - arena_link, - listener_idx: Default::default(), - frames: ActiveFrame::new(), - hooks: Default::default(), - shared_contexts: Default::default(), - listeners: Default::default(), - descendents: Default::default(), - suspended_tasks: Default::default(), - } - } - - pub fn update_caller<'creator_node>(&mut self, caller: Weak) { - // pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { - let broken_caller = unsafe { - std::mem::transmute::< - Weak, - Weak, - // Weak>, - // Weak>, - >(caller) - }; - - self.caller = broken_caller; - } - - pub fn update_children<'creator_node>( - &mut self, - child_nodes: &'creator_node [VNode<'creator_node>], - ) { - let child_nodes = unsafe { std::mem::transmute(child_nodes) }; - self.child_nodes = child_nodes; - } - - /// Create a new context and run the component with references from the Virtual Dom - /// This function downcasts the function pointer based on the stored props_type - /// - /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) - pub fn run_scope<'sel>(&'sel mut self) -> Result<()> { - // Cycle to the next frame and then reset it - // This breaks any latent references, invalidating every pointer referencing into it. - self.frames.next().bump.reset(); - - // Remove all the outdated listeners - self.listeners.borrow_mut().clear(); - - unsafe { self.hooks.reset() }; - self.listener_idx.set(0); - - let caller = self - .caller - .upgrade() - .ok_or(Error::FatalInternal("Failed to get caller"))?; - - // Cast the caller ptr from static to one with our own reference - let c2: &OpaqueComponent = caller.as_ref(); - let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) }; - - self.frames.cur_frame_mut().head_node = unsafe { self.own_vnodes(c3) }; - - Ok(()) - } - - // this is its own function so we can preciesly control how lifetimes flow - unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> { - let new_head: VNode<'a> = f(self); - let out: VNode<'static> = std::mem::transmute(new_head); - out - } - - // A safe wrapper around calling listeners - // calling listeners will invalidate the list of listeners - // The listener list will be completely drained because the next frame will write over previous listeners - pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> { - let EventTrigger { - real_node_id, - event, - .. - } = trigger; - - // todo: implement scanning for outdated events - - // Convert the raw ptr into an actual object - // This operation is assumed to be safe - - log::debug!("Calling listeners! {:?}", self.listeners.borrow().len()); - let mut listners = self.listeners.borrow_mut(); - let (_, listener) = listners - .iter() - .find(|(domptr, _)| { - let p = unsafe { &**domptr }; - p.get() == real_node_id - }) - .expect(&format!( - "Failed to find real node with ID {:?}", - real_node_id - )); - - // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster! - unsafe { - let mut listener_fn = &mut **listener; - listener_fn(event); - } - - Ok(()) - } - - pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { - self.frames.current_head_node() - } - - pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { - self.frames.prev_head_node() - } - - pub(crate) fn cur_frame(&self) -> &BumpFrame { - self.frames.cur_frame() - } - - pub(crate) fn root<'a>(&'a self) -> &'a VNode<'a> { - &self.frames.cur_frame().head_node - } -}