Feat: remove our use of ouroborous.

Instead, we're just going stick with unsafely transmuting the static
lifetime when accessing the active frame. This is a bit scarier, but
greatly simplifies containing the VNode.
This commit is contained in:
Jonathan Kelley committed 2021-02-17 20:56:53 -05:00
1 parent e3ede7fcbf
commit bcbb93b697
5 files changed
+113 -137

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
//! render it again //! render it again
//! consume the diffs and write that to a renderer //! consume the diffs and write that to a renderer
use dioxus_core::{prelude::*, scope::Scope}; use dioxus_core::prelude::*;
fn main() -> Result<(), ()> { fn main() -> Result<(), ()> {
let p1 = Props { name: "bob".into() }; let p1 = Props { name: "bob".into() };
+2 -8
View File
@@ -30,15 +30,10 @@ use std::{
pub struct Context<'src> { pub struct Context<'src> {
pub idx: AtomicUsize, pub idx: AtomicUsize,
pub(crate) scope: &'src Scope,
/// Direct access to the properties used to create this component.
// pub props: &'src PropType,
// Borrowed from scope // Borrowed from scope
pub(crate) arena: &'src typed_arena::Arena<Hook>, pub(crate) arena: &'src typed_arena::Arena<Hook>,
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>, pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
pub(crate) bump: &'src Bump, pub(crate) bump: &'src Bump,
// pub(crate) components: &'src generational_arena::Arena<Scope>,
// holder for the src lifetime // holder for the src lifetime
// todo @jon remove this // todo @jon remove this
@@ -74,7 +69,7 @@ impl<'a> Context<'a> {
/// } /// }
///``` ///```
pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> { pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
lazy_nodes(self.bump.borrow()) lazy_nodes(self.bump)
} }
pub fn callback(&self, f: impl Fn(()) + 'static) {} pub fn callback(&self, f: impl Fn(()) + 'static) {}
@@ -105,8 +100,7 @@ pub mod hooks {
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
// impl<'a, P> Context<'a> { /// TODO: @jon, rework this so we dont have to use unsafe to make hooks and then return them
// impl<'a, P> Context<'a, P> {
/// use_hook provides a way to store data between renders for functional components. /// use_hook provides a way to store data between renders for functional components.
/// todo @jon: ensure the hook arena is stable with pin or is stable by default /// todo @jon: ensure the hook arena is stable with pin or is stable by default
pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>( pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>(
+7 -7
View File
@@ -87,19 +87,19 @@ pub mod builder {
// types used internally that are important // types used internally that are important
pub(crate) mod innerlude { pub(crate) mod innerlude {
pub use crate::component::{Component, Properties}; pub(crate) use crate::component::{Component, Properties};
use crate::context::hooks::Hook; use crate::context::hooks::Hook;
pub use crate::context::Context; pub(crate) use crate::context::Context;
pub use crate::error::{Error, Result}; pub(crate) use crate::error::{Error, Result};
use crate::nodes; use crate::nodes;
pub use crate::scope::Scope; pub(crate) use crate::scope::Scope;
pub use crate::virtual_dom::VirtualDom; pub(crate) use crate::virtual_dom::VirtualDom;
pub use nodes::*; pub(crate) use nodes::*;
// pub use nodes::iterables::IterableNodes; // pub use nodes::iterables::IterableNodes;
/// This type alias is an internal way of abstracting over the static functions that represent components. /// This type alias is an internal way of abstracting over the static functions that represent components.
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> &'a VNode<'a>; pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> VNode<'a>;
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>; // pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
// TODO @Jon, fix this // TODO @Jon, fix this
+87 -106
View File
@@ -15,77 +15,13 @@ use std::{
todo, todo,
}; };
pub struct BumpContainer(pub UnsafeCell<Bump>);
impl BumpContainer {
fn new() -> Self {
Self(UnsafeCell::new(Bump::new()))
}
}
impl Deref for BumpContainer {
type Target = Bump;
fn deref(&self) -> &Self::Target {
todo!()
// self.0.borrow()
}
}
impl DerefMut for BumpContainer {
fn deref_mut(&mut self) -> &mut Self::Target {
todo!()
// self.0.borrow_mut()
}
}
unsafe impl StableAddress for BumpContainer {}
#[ouroboros::self_referencing]
pub struct BumpFrame {
pub bump: BumpContainer,
#[covariant]
#[borrows(bump)]
pub head_node: &'this VNode<'this>,
}
pub struct ActiveFrame {
pub idx: AtomicUsize,
pub frames: [BumpFrame; 2],
}
impl ActiveFrame {
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
Self {
idx: 0.into(),
frames: [a, b],
}
}
fn next(&self) -> &BumpFrame {
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
match cur % 1 {
1 => &self.frames[1],
0 => &self.frames[0],
_ => unreachable!("mod cannot by non-zero"),
}
}
// fn next(&self) -> &BumpFrame {
// self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
// let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
// match cur % 2_usize {
// 1 => &self.frames[1],
// 0 => &self.frames[0],
// }
// }
}
/// 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(crate) struct Scope {
// TODO @Jon // TODO @Jon
// These hooks are actually references into the hook arena // These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage // These two could be combined with "OwningRef" to remove unsafe usage
@@ -107,6 +43,8 @@ pub struct Scope {
// IE Which listeners need to be woken up? // IE Which listeners need to be woken up?
pub listeners: Vec<Box<dyn Fn()>>, pub listeners: Vec<Box<dyn Fn()>>,
pub props: Box<dyn std::any::Any>,
// //
pub props_type: TypeId, pub props_type: TypeId,
pub caller: *const i32, pub caller: *const i32,
@@ -128,7 +66,11 @@ pub struct Scope {
impl Scope { impl Scope {
// create a new scope from a function // create a new scope from a function
pub(crate) fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self { pub(crate) fn new<T: 'static>(
f: FC<T>,
props: impl Properties + 'static,
parent: Option<Index>,
) -> Self {
// Capture the props type // Capture the props type
let props_type = TypeId::of::<T>(); let props_type = TypeId::of::<T>();
let hook_arena = typed_arena::Arena::new(); let hook_arena = typed_arena::Arena::new();
@@ -137,24 +79,20 @@ impl Scope {
// Capture the caller // Capture the caller
let caller = f as *const i32; let caller = f as *const i32;
// Create the two buffers the componetn will render into
// There will always be an "old" and "new"
let listeners = Vec::new(); let listeners = Vec::new();
let new_frame = BumpFrameBuilder { let old_frame = BumpFrame {
bump: BumpContainer::new(), bump: Bump::new(),
head_node_builder: |bump| bump.alloc(VNode::text("")), head_node: VNode::text(""),
} };
.build();
let old_frame = BumpFrameBuilder { let new_frame = BumpFrame {
bump: BumpContainer::new(), bump: Bump::new(),
head_node_builder: |bump| bump.alloc(VNode::text("")), head_node: VNode::text(""),
} };
.build();
let frames = ActiveFrame::from_frames(old_frame, new_frame); let frames = ActiveFrame::from_frames(old_frame, new_frame);
let props = Box::new(props);
Self { Self {
hook_arena, hook_arena,
@@ -164,34 +102,37 @@ impl Scope {
frames, frames,
listeners, listeners,
parent, parent,
props,
} }
} }
/// Update this component's props with a new set of props
///
///
pub(crate) fn update_props<P: Properties + Sized + 'static>(
&mut self,
new_props: Box<P>,
) -> crate::error::Result<()> {
Ok(())
}
/// 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(crate) fn run<'a, 'bump, P: Properties + ?Sized>(&'bump mut self, props: &'a P) { pub(crate) fn run<'bump, P: Properties + Sized + 'static>(&'bump mut self) {
// I really wanted to do this safely, but I don't think we can. let frame = {
// We want to reset the bump before writing into it. This requires &mut to the bump
// Ouroborous lets us borrow with self, but the heads (IE the source) cannot be changed while the ref is live
// n.b, there might be a better way of doing this active frame stuff - perhaps swapping
let frame = self.frames.next(); let frame = self.frames.next();
frame.bump.reset();
frame.with_bump(|bump_container| { frame
let bump: &mut Bump = unsafe { &mut *bump_container.0.get() }; };
bump.reset();
let bump = &*bump;
let ctx: Context<'bump> = Context { let ctx: Context<'bump> = Context {
scope: &*self,
_p: PhantomData {},
arena: &self.hook_arena, arena: &self.hook_arena,
hooks: &self.hooks, hooks: &self.hooks,
bump: &frame.bump,
idx: 0.into(), idx: 0.into(),
bump, _p: PhantomData {},
}; };
/* /*
@@ -205,23 +146,63 @@ impl Scope {
-- --
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.
*/ */
let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) }; let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
let nodes: &'bump VNode = caller(ctx, props); let nodes: VNode<'bump> = caller(ctx, self.props.downcast_ref::<P>().unwrap());
});
// let new_nodes = caller(ctx, props); /*
// let r = new_nodes as *const _; SAFETY ALERT
// self.old_root = self.new_root;
// self.new_root = new_nodes as *const _;
// let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node }; DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
// TODO: Iterate through the new nodes Some things to note:
// move any listeners into ourself - 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.
- Public API cannot drop or destructure VNode
*/
// perform the diff, dumping into the mutable change list let unsafe_node = unsafe { std::mem::transmute::<VNode<'bump>, VNode<'static>>(nodes) };
// this doesnt perform any "diff compression" where an event and a re-render frame.head_node = unsafe_node;
// crate::diff::diff(old_nodes, &new_nodes);
todo!()
}
/// 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 current_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
todo!()
}
pub fn prev_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
todo!() todo!()
} }
} }
pub struct BumpFrame {
pub bump: Bump,
pub head_node: VNode<'static>,
}
pub struct ActiveFrame {
pub idx: AtomicUsize,
pub frames: [BumpFrame; 2],
}
impl ActiveFrame {
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
Self {
idx: 0.into(),
frames: [a, b],
}
}
fn next(&mut self) -> &mut BumpFrame {
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
match cur % 1 {
1 => &mut self.frames[1],
0 => &mut self.frames[0],
_ => unreachable!("mod cannot by non-zero"),
}
}
}
+16 -15
View File
@@ -56,27 +56,28 @@ impl VirtualDom {
// 3. Create the lifecycle queue // 3. Create the lifecycle queue
// 4. Create the event queue // 4. Create the event queue
todo!();
// Arena allocate all the components // Arena allocate all the components
// This should make it *really* easy to store references in events and such // This should make it *really* easy to store references in events and such
let mut components = Arena::new(); // let mut components = Arena::new();
// Create a reference to the component in the arena // Create a reference to the component in the arena
let base_scope = components.insert(Scope::new(root, None)); // let base_scope = components.insert(Scope::new(root, None));
// Create a new mount event with no root container // // Create a new mount event with no root container
let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props); // let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
// Create an event queue with a mount for the base scope // // Create an event queue with a mount for the base scope
let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect())); // let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect()));
let _root_prop_type = TypeId::of::<P>(); // let _root_prop_type = TypeId::of::<P>();
Self { // Self {
components, // components,
base_scope, // base_scope,
event_queue, // event_queue,
_root_prop_type, // _root_prop_type,
} // }
} }
/// 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
@@ -207,8 +208,8 @@ impl VirtualDom {
// mount to the root // mount to the root
} }
let g = props.as_ref(); // let g = props.as_ref();
scope.run(g); // scope.run(g);
// scope.run(runner, props, dom); // scope.run(runner, props, dom);
} }