Feat: notes on safety, and inline listeners

This commit is contained in:
Jonathan Kelley committed 2021-03-04 13:49:18 -05:00
1 parent c70652a3c9
commit bdd6be309e
5 files changed
+133 -103

No files matched your search

+32 -41
View File
@@ -39,16 +39,14 @@ pub struct Context<'src> {
pub(crate) final_nodes: Rc<RefCell<Option<VNode<'static>>>>,
pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
// holder for the src lifetime
// todo @jon remove this
pub _p: std::marker::PhantomData<&'src ()>,
}
impl<'a> Context<'a> {
// pub fn props<P>() -> &'a P {
// todo!()
// }
// impl<'a, PropType> Context<'a, PropType> {
/// Access the children elements passed into the component
pub fn children(&self) -> Vec<VNode> {
@@ -73,7 +71,19 @@ impl<'a> Context<'a> {
) -> VNode<'a> {
todo!()
}
}
// NodeCtx is used to build VNodes in the component's memory space.
// This struct adds metadata to the final DomTree about listeners, attributes, and children
#[derive(Debug, Clone)]
pub struct NodeCtx<'a> {
pub bump: &'a Bump,
pub idx: RefCell<usize>,
pub scope: ScopeIdx,
pub listeners: &'a RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
}
impl<'a> Context<'a> {
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
///
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
@@ -92,8 +102,9 @@ impl<'a> Context<'a> {
pub fn render(self, lazy_nodes: impl FnOnce(&NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
let ctx = NodeCtx {
bump: self.bump,
idx: 0.into(),
scope: self.scope,
idx: 0.into(),
listeners: self.listeners,
};
let safe_nodes = lazy_nodes(&ctx);
@@ -103,26 +114,8 @@ impl<'a> Context<'a> {
}
}
// NodeCtx is used to build VNodes in the component's memory space.
// This struct adds metadata to the final DomTree about listeners, attributes, and children
#[derive(Debug, Clone)]
pub struct NodeCtx<'a> {
pub bump: &'a Bump,
pub idx: RefCell<usize>,
pub scope: ScopeIdx,
}
impl NodeCtx<'_> {
#[inline]
pub fn bump(&self) -> &Bump {
self.bump
}
}
/// This module provides internal state management functionality for Dioxus components
pub mod hooks {
//! This module provides internal state management functionality for Dioxus components
//!
use super::*;
#[derive(Debug)]
@@ -196,23 +189,21 @@ pub mod hooks {
}
}
mod context_api {
//! Context API
//!
//! The context API provides a mechanism for components to borrow state from other components higher in the tree.
//! By combining the Context API and the Subscription API, we can craft ergonomic global state management systems.
//!
//! This API is inherently dangerous because we could easily cause UB by allowing &T and &mut T to exist at the same time.
//! To prevent this, we expose the RemoteState<T> and RemoteLock<T> types which act as a form of reverse borrowing.
//! This is very similar to RwLock, except that RemoteState is copy-able. Unlike RwLock, derefing RemoteState can
//! cause panics if the pointer is null. In essence, we sacrifice the panic protection for ergonomics, but arrive at
//! a similar end result.
//!
//! Instead of placing the onus on the receiver of the data to use it properly, we wrap the source object in a
//! "shield" where gaining &mut access can only be done if no active StateGuards are open. This would fail and indicate
//! a failure of implementation.
//!
//!
/// Context API
///
/// The context API provides a mechanism for components to borrow state from other components higher in the tree.
/// By combining the Context API and the Subscription API, we can craft ergonomic global state management systems.
///
/// This API is inherently dangerous because we could easily cause UB by allowing &T and &mut T to exist at the same time.
/// To prevent this, we expose the RemoteState<T> and RemoteLock<T> types which act as a form of reverse borrowing.
/// This is very similar to RwLock, except that RemoteState is copy-able. Unlike RwLock, derefing RemoteState can
/// cause panics if the pointer is null. In essence, we sacrifice the panic protection for ergonomics, but arrive at
/// a similar end result.
///
/// Instead of placing the onus on the receiver of the data to use it properly, we wrap the source object in a
/// "shield" where gaining &mut access can only be done if no active StateGuards are open. This would fail and indicate
/// a failure of implementation.
pub mod context_api {
use std::ops::Deref;
+11 -1
View File
@@ -1,6 +1,6 @@
//! Helpers for building virtual DOM VNodes.
use std::ops::Deref;
use std::{borrow::BorrowMut, ops::Deref};
use crate::{
context::NodeCtx,
@@ -344,6 +344,7 @@ where
// todo:
// increment listner id from nodectx ref
// add listener attrs here instead of later?
self.listeners.push(Listener {
event,
callback: self.ctx.bump.alloc(callback),
@@ -353,6 +354,15 @@ where
// bump the context id forward
*self.ctx.idx.borrow_mut() += 1;
// Add this listener to the context list
// This casts the listener to a self-referential pointer
// This is okay because the bump arena is stable
self.listeners.last().map(|g| {
let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
self.ctx.listeners.borrow_mut().push(r.callback as *const _);
});
self
}
}
+62 -60
View File
@@ -3,18 +3,12 @@ use crate::context::hooks::Hook;
use crate::innerlude::*;
use crate::nodes::VNode;
use bumpalo::Bump;
// use generational_arena::ScopeIdx;
use std::{
any::TypeId,
borrow::{Borrow, BorrowMut},
cell::{RefCell, UnsafeCell},
future::Future,
any::{Any, TypeId},
cell::RefCell,
marker::PhantomData,
ops::{Deref, DerefMut},
sync::atomic::AtomicUsize,
sync::atomic::Ordering,
todo,
ops::Deref,
};
/// Every component in Dioxus is represented by a `Scope`.
@@ -24,31 +18,35 @@ use std::{
/// 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(crate) struct Scope {
// TODO @Jon
// These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage
// could also use ourborous
pub hooks: RefCell<Vec<*mut Hook>>,
pub hook_arena: typed_arena::Arena<Hook>,
// Map to the parent
pub parent: Option<ScopeIdx>,
pub frames: ActiveFrame,
// List of listeners generated when CTX is evaluated
pub listeners: Vec<*const dyn Fn(crate::events::VirtualEvent)>,
// pub listeners: Vec<*const dyn Fn(crate::events::VirtualEvent)>,
// pub listeners: Vec<Box<dyn Fn(crate::events::VirtualEvent)>>,
// lying, cheating reference >:(
pub props: Box<dyn std::any::Any>,
// our own index
pub myidx: ScopeIdx,
// pub props_type: TypeId,
// ==========================
// slightly unsafe stuff
// ==========================
// an internal, highly efficient storage of vnodes
pub frames: ActiveFrame,
// These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage
// could also use ourborous
pub hooks: RefCell<Vec<*mut Hook>>,
pub hook_arena: typed_arena::Arena<Hook>,
// Unsafety:
// - is self-refenrential and therefore needs to point into the bump
//
// Stores references into the listeners attached to the vnodes
pub listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
// Unsafety
// - is a raw ptr because we need to compare
pub caller: *const (),
}
@@ -66,8 +64,7 @@ impl Scope {
// Capture the caller
let caller = f as *const ();
let listeners = vec![];
// let listeners: Vec<Box<dyn Fn(crate::events::VirtualEvent)>> = vec![];
let listeners = Default::default();
let old_frame = BumpFrame {
bump: Bump::new(),
@@ -97,7 +94,21 @@ impl Scope {
props,
}
}
}
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 {
/// 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
///
@@ -106,11 +117,11 @@ impl Scope {
let frame = {
let frame = self.frames.next();
frame.bump.reset();
log::debug!("Rednering into frame {:?}", frame as *const _);
frame
};
let node_slot = std::rc::Rc::new(RefCell::new(None));
let ctx: Context<'bump> = Context {
arena: &self.hook_arena,
hooks: &self.hooks,
@@ -119,6 +130,7 @@ impl Scope {
_p: PhantomData {},
final_nodes: node_slot.clone(),
scope: self.myidx,
listeners: &self.listeners,
};
unsafe {
@@ -139,7 +151,7 @@ impl Scope {
let props = self.props.downcast_ref::<PLocked>().unwrap();
// Note that the actual modification of the vnode head element occurs during this call
let _nodes: DomTree = caller(ctx, props);
let _: DomTree = caller(ctx, props);
/*
SAFETY ALERT
@@ -158,32 +170,8 @@ impl Scope {
.borrow_mut()
.take()
.expect("Viewing did not happen");
// todo:
// make this so we dont have to iterate through the vnodes to get its listener
let mut listeners = vec![];
retrieve_listeners(&frame.head_node, &mut listeners);
self.listeners = listeners
.into_iter()
.map(|f| {
let g = f.callback;
g as *const _
})
.collect();
// consume the listeners from the head_node into a list of boxed ref listeners
}
}
/// 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()
}
}
fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) {
@@ -198,6 +186,21 @@ 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()
}
}
// todo, do better with the active frame stuff
// somehow build this vnode with a lifetime tied to self
// This root node has "static" lifetime, but it's really not static.
@@ -205,7 +208,7 @@ fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) {
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
// ! do not copy this reference are things WILL break !
pub struct ActiveFrame {
pub idx: AtomicUsize,
pub idx: RefCell<usize>,
pub frames: [BumpFrame; 2],
}
@@ -223,7 +226,7 @@ impl ActiveFrame {
}
fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match self.idx.borrow().load(Ordering::Relaxed) & 1 == 0 {
let raw_node = match *self.idx.borrow() & 1 == 0 {
true => &self.frames[0],
false => &self.frames[1],
};
@@ -237,7 +240,7 @@ impl ActiveFrame {
}
fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match self.idx.borrow().load(Ordering::Relaxed) & 1 != 0 {
let raw_node = match *self.idx.borrow() & 1 != 0 {
true => &self.frames[0],
false => &self.frames[1],
};
@@ -251,10 +254,9 @@ impl ActiveFrame {
}
fn next(&mut self) -> &mut BumpFrame {
self.idx.fetch_add(1, Ordering::Relaxed);
let cur = self.idx.borrow().load(Ordering::Relaxed);
*self.idx.borrow_mut() += 1;
if cur % 2 == 0 {
if *self.idx.borrow() % 2 == 0 {
&mut self.frames[0]
} else {
&mut self.frames[1]
@@ -262,7 +264,7 @@ impl ActiveFrame {
}
}
// #[cfg(test)]
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
+2 -1
View File
@@ -136,11 +136,12 @@ impl VirtualDom {
.get_mut(component_id)
.expect("Component should exist if an event was triggered");
log::debug!("list: {}", component.listeners.len());
log::debug!("list: {}", component.listeners.borrow().len());
let listener = unsafe {
component
.listeners
.borrow()
.get(listener_id as usize)
.expect("Listener should exist if it was triggered")
.as_ref()