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
+128 -88

No files matched your search

+96 -55
View File
@@ -11,22 +11,33 @@ use std::{
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`.
///
/// 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 {
pub struct Scope<P: Properties> {
// Map to the parent
pub parent: Option<ScopeIdx>,
// lying, cheating reference >:(
pub props: Box<dyn std::any::Any>,
// our own index
pub myidx: ScopeIdx,
pub caller: FC<P>,
pub props: P,
// ==========================
// slightly unsafe stuff
// ==========================
@@ -41,30 +52,22 @@ pub struct Scope {
// 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
listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
// Unsafety
// - is a raw ptr because we need to compare
pub caller: *const (),
}
impl Scope {
// create a new scope from a function
pub fn new<'a, P1, P2: 'static>(
f: FC<P1>,
props: P1,
// instead of having it as a trait method, we use a single function
// todo: do the unsafety magic stuff to erase the type of p
pub fn create_scoped<P: Properties + 'static>(
caller: FC<P>,
props: P,
myidx: ScopeIdx,
parent: Option<ScopeIdx>,
) -> Self {
) -> Box<dyn Scoped> {
let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new());
// Capture the caller
let caller = f as *const ();
let listeners = Default::default();
let old_frame = BumpFrame {
@@ -79,12 +82,7 @@ impl Scope {
let frames = ActiveFrame::from_frames(old_frame, new_frame);
// box the props
let props = Box::new(props);
let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
Self {
Box::new(Scope {
myidx,
hook_arena,
hooks,
@@ -93,13 +91,24 @@ impl Scope {
listeners,
parent,
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
// 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) {
fn call_listener(&mut self, trigger: EventTrigger) {
let EventTrigger {
listener_id,
event: source,
@@ -126,26 +135,23 @@ impl Scope {
self.listeners.borrow_mut().drain(..);
}
}
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node()
}
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.prev_head_node()
}
}
pub struct RawComponent {
// used as a memoization strategy
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
/// 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<'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 = self.frames.next();
frame.bump.reset();
@@ -165,7 +171,7 @@ impl Scope {
listeners: &self.listeners,
};
unsafe {
// unsafe {
/*
SAFETY ALERT
@@ -179,8 +185,10 @@ impl Scope {
*/
// we use plocked to be able to remove the borrowed lifetime
// these lifetimes could be very broken, so we need to dynamically manage them
let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
let props = self.props.downcast_ref::<PLocked>().unwrap();
// let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
// 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
let _: DomTree = caller(ctx, props);
@@ -202,7 +210,7 @@ impl Scope {
.borrow_mut()
.take()
.expect("Viewing did not happen");
}
// }
}
}
@@ -221,17 +229,18 @@ fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) {
// ==========================
// 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> {
self.frames.prev_head_node()
}
}
// impl<P: Properties> Scope<P> {
// /// 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
// somehow build this vnode with a lifetime tied to self
@@ -323,8 +332,8 @@ mod tests {
let props = ();
let parent = None;
let mut nodes = generational_arena::Arena::new();
nodes.insert_with(|f| {
let scope = Scope::new::<(), ()>(example, props, f, parent);
nodes.insert_with(|myidx| {
let scope = create_scoped(example, props, myidx, parent);
});
}
@@ -390,3 +399,35 @@ mod tests {
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::innerlude::*;
use crate::scope::{create_scoped, Scoped};
use crate::{innerlude::*, scope::Properties};
use bumpalo::Bump;
use generational_arena::Arena;
use std::{
@@ -18,8 +19,12 @@ use std::{
/// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom {
/// 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.
pub(crate) components: Arena<Scope>,
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
///
/// 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.
/// 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
/// 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 event_queue = RefCell::new(VecDeque::new());
@@ -57,8 +62,7 @@ impl VirtualDom {
// 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
// This puts the dom in a usable state on creation, rather than being potentially invalid
let base_scope =
components.insert_with(|id| Scope::new::<_, P>(root, root_props, id, None));
let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
// evaluate the component, pushing any updates its generates into the lifecycle queue
// todo!
@@ -93,7 +97,7 @@ impl VirtualDom {
.get_mut(self.base_scope)
.expect("Root should always exist");
component.run::<()>();
component.run();
diff_machine.diff_node(component.old_frame(), component.new_frame());
@@ -138,22 +142,8 @@ impl VirtualDom {
self.diff_bump.reset();
let mut diff_machine = DiffMachine::new(&self.diff_bump);
// this is still a WIP
// 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::<()>();
component.run();
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())
// 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.
/// 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.
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);
}
/// todo
/// change this to accept a modification closure, so the user gets 0-cost access to the props item.
/// Good for cases where the props is changed remotely (or something similar) and building a whole new props item would
/// 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 {
event_type: LifecycleType::PropsChanged {
props: Box::new(new_props),
component: self.base_scope,
},
})
// Ok(LifecycleEvent {
// event_type: LifecycleType::PropsChanged {
// props: Box::new(new_props),
// component: self.base_scope,
// },
// })
}
}