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:
1 parent
e3ede7fcbf
commit
bcbb93b697
5 files changed
+126
-150
No files matched your search
@@ -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() };
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+100
-119
@@ -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,64 +102,107 @@ 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
|
let frame = self.frames.next();
|
||||||
// Ouroborous lets us borrow with self, but the heads (IE the source) cannot be changed while the ref is live
|
frame.bump.reset();
|
||||||
|
frame
|
||||||
|
};
|
||||||
|
|
||||||
// n.b, there might be a better way of doing this active frame stuff - perhaps swapping
|
let ctx: Context<'bump> = Context {
|
||||||
let frame = self.frames.next();
|
arena: &self.hook_arena,
|
||||||
|
hooks: &self.hooks,
|
||||||
|
bump: &frame.bump,
|
||||||
|
idx: 0.into(),
|
||||||
|
_p: PhantomData {},
|
||||||
|
};
|
||||||
|
|
||||||
frame.with_bump(|bump_container| {
|
/*
|
||||||
let bump: &mut Bump = unsafe { &mut *bump_container.0.get() };
|
SAFETY ALERT
|
||||||
bump.reset();
|
|
||||||
|
|
||||||
let bump = &*bump;
|
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 transmute the function back using the props as reference.
|
||||||
|
|
||||||
let ctx: Context<'bump> = Context {
|
we could do a better check to make sure that the TypeID is correct before casting
|
||||||
scope: &*self,
|
--
|
||||||
_p: PhantomData {},
|
This is safe because we check that the generic type matches before casting.
|
||||||
arena: &self.hook_arena,
|
*/
|
||||||
hooks: &self.hooks,
|
|
||||||
idx: 0.into(),
|
|
||||||
bump,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
|
||||||
SAFETY ALERT
|
let nodes: VNode<'bump> = caller(ctx, self.props.downcast_ref::<P>().unwrap());
|
||||||
|
|
||||||
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,
|
SAFETY ALERT
|
||||||
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
|
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||||
--
|
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||||
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 nodes: &'bump VNode = caller(ctx, props);
|
|
||||||
});
|
|
||||||
|
|
||||||
// let new_nodes = caller(ctx, props);
|
Some things to note:
|
||||||
// let r = new_nodes as *const _;
|
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||||
// self.old_root = self.new_root;
|
- The VNode has a private API and can only be used from accessors.
|
||||||
// self.new_root = new_nodes as *const _;
|
- Public API cannot drop or destructure VNode
|
||||||
|
*/
|
||||||
|
|
||||||
// let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
|
let unsafe_node = unsafe { std::mem::transmute::<VNode<'bump>, VNode<'static>>(nodes) };
|
||||||
|
frame.head_node = unsafe_node;
|
||||||
|
|
||||||
// TODO: Iterate through the new nodes
|
todo!()
|
||||||
// move any listeners into ourself
|
}
|
||||||
|
|
||||||
// perform the diff, dumping into the mutable change list
|
/// Accessor to get the root node and its children (safely)\
|
||||||
// this doesnt perform any "diff compression" where an event and a re-render
|
/// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat
|
||||||
// crate::diff::diff(old_nodes, &new_nodes);
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user