feat: dyn scope
This commit is contained in:
1 parent
8dc26195e2
commit
89f2290692
2 files changed
+177
-137
No files matched your search
+145
-104
@@ -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,65 +52,63 @@ 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 listeners = Default::default();
|
||||||
let caller = f as *const ();
|
|
||||||
|
|
||||||
let listeners = Default::default();
|
let old_frame = BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: VNode::text(""),
|
||||||
|
};
|
||||||
|
|
||||||
let old_frame = BumpFrame {
|
let new_frame = BumpFrame {
|
||||||
bump: Bump::new(),
|
bump: Bump::new(),
|
||||||
head_node: VNode::text(""),
|
head_node: VNode::text(""),
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_frame = BumpFrame {
|
let frames = ActiveFrame::from_frames(old_frame, new_frame);
|
||||||
bump: Bump::new(),
|
|
||||||
head_node: VNode::text(""),
|
|
||||||
};
|
|
||||||
|
|
||||||
let frames = ActiveFrame::from_frames(old_frame, new_frame);
|
Box::new(Scope {
|
||||||
|
myidx,
|
||||||
|
hook_arena,
|
||||||
|
hooks,
|
||||||
|
caller,
|
||||||
|
frames,
|
||||||
|
listeners,
|
||||||
|
parent,
|
||||||
|
props,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// box the props
|
impl<P: Properties + 'static> Scoped for Scope<P> {
|
||||||
let props = Box::new(props);
|
fn run(&mut self) {
|
||||||
|
self.run()
|
||||||
|
}
|
||||||
|
|
||||||
let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
|
fn compare_props(&self, new: &Any) -> bool {
|
||||||
|
new.downcast_ref::<P>()
|
||||||
Self {
|
.map(|f| &self.props == f)
|
||||||
myidx,
|
.expect("Props should not be of a different type")
|
||||||
hook_arena,
|
|
||||||
hooks,
|
|
||||||
caller,
|
|
||||||
frames,
|
|
||||||
listeners,
|
|
||||||
parent,
|
|
||||||
props,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
|
self.frames.prev_head_node()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawComponent {
|
impl<P: Properties> Scope<P> {
|
||||||
// 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 {
|
|
||||||
/// 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,44 +171,46 @@ impl Scope {
|
|||||||
listeners: &self.listeners,
|
listeners: &self.listeners,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe {
|
// unsafe {
|
||||||
/*
|
/*
|
||||||
SAFETY ALERT
|
SAFETY ALERT
|
||||||
|
|
||||||
This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
|
This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
|
||||||
We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
||||||
we transmute the function back using the props as reference.
|
we transmute the function back using the props as reference.
|
||||||
|
|
||||||
we could do a better check to make sure that the TypeID is correct before casting
|
we could do a better check to make sure that the TypeID is correct before casting
|
||||||
--
|
--
|
||||||
This is safe because we check that the generic type matches before casting.
|
This is safe because we check that the generic type matches before casting.
|
||||||
*/
|
*/
|
||||||
// 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);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SAFETY ALERT
|
SAFETY ALERT
|
||||||
|
|
||||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||||
|
|
||||||
Some things to note:
|
Some things to note:
|
||||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||||
- The VNode has a private API and can only be used from accessors.
|
- The VNode has a private API and can only be used from accessors.
|
||||||
- Public API cannot drop or destructure VNode
|
- Public API cannot drop or destructure VNode
|
||||||
*/
|
*/
|
||||||
// the nodes we care about have been unsafely extended to a static lifetime in context
|
// the nodes we care about have been unsafely extended to a static lifetime in context
|
||||||
frame.head_node = node_slot
|
frame.head_node = node_slot
|
||||||
.deref()
|
.deref()
|
||||||
.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) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user