feat: dyn scope

This commit is contained in:
Jonathan Kelley committed 2021-03-04 23:57:25 -05:00
1 parent 8dc26195e2
commit 89f2290692
2 files changed
+126 -86

No files matched your search

+94 -53
View File
@@ -11,22 +11,33 @@ use std::{
ops::Deref, ops::Deref,
}; };
pub trait Properties: PartialEq {}
impl Properties for () {}
pub trait Scoped {
fn run(&mut self);
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
fn call_listener(&mut self, trigger: EventTrigger);
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
}
/// Every component in Dioxus is represented by a `Scope`. /// Every component in Dioxus is represented by a `Scope`.
/// ///
/// Scopes contain the state for hooks, the component's props, and other lifecycle information. /// 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. /// 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. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
pub struct Scope { pub struct Scope<P: Properties> {
// Map to the parent // Map to the parent
pub parent: Option<ScopeIdx>, pub parent: Option<ScopeIdx>,
// lying, cheating reference >:(
pub props: Box<dyn std::any::Any>,
// our own index // our own index
pub myidx: ScopeIdx, pub myidx: ScopeIdx,
pub caller: FC<P>,
pub props: P,
// ========================== // ==========================
// slightly unsafe stuff // slightly unsafe stuff
// ========================== // ==========================
@@ -41,30 +52,22 @@ pub struct Scope {
// Unsafety: // Unsafety:
// - is self-refenrential and therefore needs to point into the bump // - is self-refenrential and therefore needs to point into the bump
//
// Stores references into the listeners attached to the vnodes // Stores references into the listeners attached to the vnodes
// NEEDS TO BE PRIVATE // NEEDS TO BE PRIVATE
listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>, listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
// Unsafety
// - is a raw ptr because we need to compare
pub caller: *const (),
} }
impl Scope { // instead of having it as a trait method, we use a single function
// create a new scope from a function // todo: do the unsafety magic stuff to erase the type of p
pub fn new<'a, P1, P2: 'static>( pub fn create_scoped<P: Properties + 'static>(
f: FC<P1>, caller: FC<P>,
props: P1, props: P,
myidx: ScopeIdx, myidx: ScopeIdx,
parent: Option<ScopeIdx>, parent: Option<ScopeIdx>,
) -> Self { ) -> Box<dyn Scoped> {
let hook_arena = typed_arena::Arena::new(); let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new()); let hooks = RefCell::new(Vec::new());
// Capture the caller
let caller = f as *const ();
let listeners = Default::default(); let listeners = Default::default();
let old_frame = BumpFrame { let old_frame = BumpFrame {
@@ -79,12 +82,7 @@ impl Scope {
let frames = ActiveFrame::from_frames(old_frame, new_frame); let frames = ActiveFrame::from_frames(old_frame, new_frame);
// box the props Box::new(Scope {
let props = Box::new(props);
let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
Self {
myidx, myidx,
hook_arena, hook_arena,
hooks, hooks,
@@ -93,13 +91,24 @@ impl Scope {
listeners, listeners,
parent, parent,
props, props,
})
} }
impl<P: Properties + 'static> Scoped for Scope<P> {
fn run(&mut self) {
self.run()
}
fn compare_props(&self, new: &Any) -> bool {
new.downcast_ref::<P>()
.map(|f| &self.props == f)
.expect("Props should not be of a different type")
} }
// A safe wrapper around calling listeners // A safe wrapper around calling listeners
// calling listeners will invalidate the list of 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 // The listener list will be completely drained because the next frame will write over previous listeners
pub fn call_listener(&mut self, trigger: EventTrigger) { fn call_listener(&mut self, trigger: EventTrigger) {
let EventTrigger { let EventTrigger {
listener_id, listener_id,
event: source, event: source,
@@ -126,26 +135,23 @@ impl Scope {
self.listeners.borrow_mut().drain(..); self.listeners.borrow_mut().drain(..);
} }
} }
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node()
} }
pub struct RawComponent { fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
// used as a memoization strategy self.frames.prev_head_node()
comparator: *const Box<dyn Fn(&Box<dyn Any>) -> bool>, }
// used to actually run the component
// encapsulates props
runner: *const Box<dyn Fn(Context) -> DomTree>,
// the actual FC<T>
raw: *const (),
} }
impl Scope { impl<P: Properties> Scope<P> {
/// Create a new context and run the component with references from the Virtual Dom /// 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 /// 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) /// 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<'bump, PLocked: Sized + 'static>(&'bump mut self) { pub fn run<'bump>(&'bump mut self) {
// pub fn run<'bump, PLocked: Sized + 'static>(&'bump mut self) {
let frame = { let frame = {
let frame = self.frames.next(); let frame = self.frames.next();
frame.bump.reset(); frame.bump.reset();
@@ -165,7 +171,7 @@ impl Scope {
listeners: &self.listeners, listeners: &self.listeners,
}; };
unsafe { // unsafe {
/* /*
SAFETY ALERT SAFETY ALERT
@@ -179,8 +185,10 @@ impl Scope {
*/ */
// we use plocked to be able to remove the borrowed lifetime // we use plocked to be able to remove the borrowed lifetime
// these lifetimes could be very broken, so we need to dynamically manage them // these lifetimes could be very broken, so we need to dynamically manage them
let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller); // let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
let props = self.props.downcast_ref::<PLocked>().unwrap(); // let props = self.props.downcast_ref::<PLocked>().unwrap();
let caller = self.caller;
let props = &self.props;
// Note that the actual modification of the vnode head element occurs during this call // Note that the actual modification of the vnode head element occurs during this call
let _: DomTree = caller(ctx, props); let _: DomTree = caller(ctx, props);
@@ -202,7 +210,7 @@ impl Scope {
.borrow_mut() .borrow_mut()
.take() .take()
.expect("Viewing did not happen"); .expect("Viewing did not happen");
} // }
} }
} }
@@ -221,17 +229,18 @@ fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) {
// ========================== // ==========================
// Active-frame related code // Active-frame related code
// ========================== // ==========================
impl Scope {
/// Accessor to get the root node and its children (safely)\
/// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat
pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node()
}
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { // impl<P: Properties> Scope<P> {
self.frames.prev_head_node() // /// Accessor to get the root node and its children (safely)\
} // /// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat
} // pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
// self.frames.current_head_node()
// }
// pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
// self.frames.prev_head_node()
// }
// }
// todo, do better with the active frame stuff // todo, do better with the active frame stuff
// somehow build this vnode with a lifetime tied to self // somehow build this vnode with a lifetime tied to self
@@ -323,8 +332,8 @@ mod tests {
let props = (); let props = ();
let parent = None; let parent = None;
let mut nodes = generational_arena::Arena::new(); let mut nodes = generational_arena::Arena::new();
nodes.insert_with(|f| { nodes.insert_with(|myidx| {
let scope = Scope::new::<(), ()>(example, props, f, parent); let scope = create_scoped(example, props, myidx, parent);
}); });
} }
@@ -390,3 +399,35 @@ mod tests {
let props = ExampleProps { name: &source_text }; let props = ExampleProps { name: &source_text };
} }
} }
#[cfg(asd)]
mod old {
/// The ComponentCaller struct is an opaque object that encapsultes the memoization and running functionality for FC
///
/// It's opaque because during the diffing mechanism, the type of props is sealed away in a closure. This makes it so
/// scope doesn't need to be generic
pub struct ComponentCaller {
// used as a memoization strategy
comparator: Box<dyn Fn(&Box<dyn Any>) -> bool>,
// used to actually run the component
// encapsulates props
runner: Box<dyn Fn(Context) -> DomTree>,
props_type: TypeId,
// the actual FC<T>
raw: *const (),
}
impl ComponentCaller {
fn new<P>(props: P) -> Self {
let comparator = Box::new(|f| false);
todo!();
// Self { comparator }
}
fn update_props<P>(props: P) {}
}
}
+32 -33
View File
@@ -1,6 +1,7 @@
// use crate::{changelist::EditList, nodes::VNode}; // use crate::{changelist::EditList, nodes::VNode};
use crate::innerlude::*; use crate::scope::{create_scoped, Scoped};
use crate::{innerlude::*, scope::Properties};
use bumpalo::Bump; use bumpalo::Bump;
use generational_arena::Arena; use generational_arena::Arena;
use std::{ use std::{
@@ -18,8 +19,12 @@ use std::{
/// Differences are converted into patches which a renderer can use to draw the UI. /// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom { pub struct VirtualDom {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with /// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena. /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
pub(crate) components: Arena<Scope>, ///
/// eventually, come up with a better datastructure that reuses boxes for known P types
/// like a generational typemap bump arena
/// -> IE a cache line for each P type with soem heuristics on optimizing layout
pub(crate) components: Arena<Box<dyn Scoped>>,
/// The index of the root component. /// The index of the root component.
/// Will not be ready if the dom is fresh /// Will not be ready if the dom is fresh
@@ -49,7 +54,7 @@ impl VirtualDom {
/// ///
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree. /// to toss out the entire tree.
pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = Arena::new(); let mut components = Arena::new();
let event_queue = RefCell::new(VecDeque::new()); let event_queue = RefCell::new(VecDeque::new());
@@ -57,8 +62,7 @@ impl VirtualDom {
// Create a reference to the component in the arena // Create a reference to the component in the arena
// Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
// This puts the dom in a usable state on creation, rather than being potentially invalid // This puts the dom in a usable state on creation, rather than being potentially invalid
let base_scope = let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
components.insert_with(|id| Scope::new::<_, P>(root, root_props, id, None));
// evaluate the component, pushing any updates its generates into the lifecycle queue // evaluate the component, pushing any updates its generates into the lifecycle queue
// todo! // todo!
@@ -93,7 +97,7 @@ impl VirtualDom {
.get_mut(self.base_scope) .get_mut(self.base_scope)
.expect("Root should always exist"); .expect("Root should always exist");
component.run::<()>(); component.run();
diff_machine.diff_node(component.old_frame(), component.new_frame()); diff_machine.diff_node(component.old_frame(), component.new_frame());
@@ -138,22 +142,8 @@ impl VirtualDom {
self.diff_bump.reset(); self.diff_bump.reset();
let mut diff_machine = DiffMachine::new(&self.diff_bump); let mut diff_machine = DiffMachine::new(&self.diff_bump);
// this is still a WIP component.run();
// we'll need to re-fecth all the scopes that were changed and build the diff machine
// fetch the component again
// let component = self
// .components
// .get_mut(self.base_scope)
// .expect("Root should always exist");
component.run::<()>();
diff_machine.diff_node(component.old_frame(), component.new_frame()); diff_machine.diff_node(component.old_frame(), component.new_frame());
// diff_machine.diff_node(
// component.old_frame(),
// component.new_frame(),
// Some(self.base_scope),
// );
Ok(diff_machine.consume()) Ok(diff_machine.consume())
// Err(crate::error::Error::NoEvent) // Err(crate::error::Error::NoEvent)
@@ -201,18 +191,27 @@ impl VirtualDom {
/// With access to the virtual dom, schedule an update to the Root component's props. /// With access to the virtual dom, schedule an update to the Root component's props.
/// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event /// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event
/// back into the event system to get an edit list. /// back into the event system to get an edit list.
pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> { /// todo
// Ensure the props match /// change this to accept a modification closure, so the user gets 0-cost access to the props item.
if TypeId::of::<P>() != self._root_prop_type { /// Good for cases where the props is changed remotely (or something similar) and building a whole new props item would
return Err(Error::WrongProps); /// be wasteful
} pub fn update_props<P: 'static>(
&mut self,
updater: impl FnOnce(&mut P),
) -> Result<LifecycleEvent> {
todo!()
// // pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
// // Ensure the props match
// if TypeId::of::<P>() != self._root_prop_type {
// return Err(Error::WrongProps);
// }
Ok(LifecycleEvent { // Ok(LifecycleEvent {
event_type: LifecycleType::PropsChanged { // event_type: LifecycleType::PropsChanged {
props: Box::new(new_props), // props: Box::new(new_props),
component: self.base_scope, // component: self.base_scope,
}, // },
}) // })
} }
} }