- integrate subscription service into context.
- Update documentation
This commit is contained in:
Jonathan Kelley committed 2021-02-08 16:57:34 -05:00
1 parent ee23ea6c3a
commit 204f0d9f16
13 files changed
+416 -358

No files matched your search

+17 -14
View File
@@ -1,6 +1,6 @@
use crate::nodes::VNode;
use crate::prelude::*;
use crate::scope::Hook;
use crate::{inner::Scope, nodes::VNode};
use bumpalo::Bump;
use std::{
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
@@ -26,21 +26,22 @@ use std::{
/// ```
// todo: force lifetime of source into T as a valid lifetime too
// it's definitely possible, just needs some more messing around
pub struct Context<'src, T> {
pub struct Context<'src, PropType> {
/// Direct access to the properties used to create this component.
pub props: T,
pub props: PropType,
pub idx: AtomicUsize,
// Borrowed from scope
pub(crate) arena: &'src typed_arena::Arena<Hook>,
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
pub(crate) components: &'src generational_arena::Arena<Scope>,
// holder for the src lifetime
// todo @jon remove this
pub _p: std::marker::PhantomData<&'src ()>,
}
impl<'a, T> Context<'a, T> {
impl<'a, PropType> Context<'a, PropType> {
/// Access the children elements passed into the component
pub fn children(&self) -> Vec<VNode> {
todo!("Children API not yet implemented for component Context")
@@ -52,13 +53,17 @@ impl<'a, T> Context<'a, T> {
}
/// Create a subscription that schedules a future render for the reference component
pub fn subscribe(&self) -> impl FnOnce() -> () {
pub fn schedule_update(&self) -> impl Fn() -> () {
todo!("Subscription API is not ready yet");
|| {}
}
/// 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.
///
/// ## Example
///
/// ```ignore
/// fn Component(ctx: Context<Props>) -> VNode {
/// // Lazy assemble the VNode tree
@@ -68,7 +73,7 @@ impl<'a, T> Context<'a, T> {
/// ctx.view(lazy_tree)
/// }
///```
pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
pub fn view(self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
todo!()
}
@@ -83,12 +88,13 @@ impl<'a, T> Context<'a, T> {
}
/// use_hook provides a way to store data between renders for functional components.
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
&'comp self,
/// 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>(
&'scope self,
// The closure that builds the hook state
initializer: impl FnOnce() -> InternalHookState,
// The closure that takes the hookstate and returns some value
runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
runner: impl FnOnce(&'internal mut InternalHookState) -> Output,
// The closure that cleans up whatever mess is left when the component gets torn down
// TODO: add this to the "clean up" group for when the component is dropped
cleanup: impl FnOnce(InternalHookState),
@@ -132,13 +138,10 @@ impl<'a, T> Context<'a, T> {
- We don't expose the raw hook pointer outside of the scope of use_hook
- The reference is tied to context, meaning it can only be used while ctx is around to free it
*/
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
let borrowed_hook: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() };
let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
// todo: set up an updater with the subscription API
let updater = ();
runner(internal_state, updater)
runner(internal_state)
}
}
+87
View File
@@ -0,0 +1,87 @@
//! Context API
//!
//! The context API provides a mechanism for components to grab
//!
//!
//!
use std::marker::PhantomPinned;
/// Any item that works with app
pub trait AppContext {}
#[derive(Copy, Clone)]
pub struct ContextGuard<'a, T> {
inner: *mut T,
_p: std::marker::PhantomData<&'a ()>,
}
impl<'a, PropType> super::context::Context<'a, PropType> {
/// # SAFETY ALERT
///
/// The underlying context mechanism relies on mutating &mut T while &T is held by components in the tree.
/// By definition, this is UB. Therefore, implementing use_context should be done with upmost care to invalidate and
/// prevent any code where &T is still being held after &mut T has been taken and T has been mutated.
///
/// While mutating &mut T while &T is captured by listeners, we can do any of:
/// 1) Prevent those listeners from being called and avoid "producing" UB values
/// 2) Delete instances of closures where &T is captured before &mut T is taken
/// 3) Make clones of T to preserve the original &T.
///
/// To guarantee safe usage of state management solutions, we provide Dioxus-Reducer and Dioxus-Dataflow built on the
/// SafeContext API. This should provide as an example of how to implement context safely for 3rd party state management.
///
/// It's important to recognize that while safety is a top concern for Dioxus, ergonomics do take prescendence.
/// Contrasting with the JS ecosystem, Rust is faster, but actually "less safe". JS is, by default, a "safe" language.
/// However, it does not protect you against data races: the primary concern for 3rd party implementers of Context.
///
/// We guarantee that any &T will remain consistent throughout the life of the Virtual Dom and that
/// &T is owned by components owned by the VirtualDom. Therefore, it is impossible for &T to:
/// - be dangling or unaligned
/// - produce an invalid value
/// - produce uninitialized memory
///
/// The only UB that is left to the implementer to prevent are Data Races.
///
/// Here's a strategy that is UB:
/// 1. &T is handed out via use_context
/// 2. an event is reduced against the state
/// 3. An &mut T is taken
/// 4. &mut T is mutated.
///
/// Now, any closures that caputed &T are subject to a data race where they might have skipped checks and UB
/// *will* affect the program.
///
/// Here's a strategy that's not UB (implemented by SafeContext):
/// 1. ContextGuard<T> is handed out via use_context.
/// 2. An event is reduced against the state.
/// 3. The state is cloned.
/// 4. All subfield selectors are evaluated and then diffed with the original.
/// 5. Fields that have changed have their ContextGuard poisoned, revoking their ability to take &T.a.
/// 6. The affected fields of Context are mutated.
/// 7. Scopes with poisoned guards are regenerated so they can take &T.a again, calling their lifecycle.
///
/// In essence, we've built a "partial borrowing" mechanism for Context objects.
///
/// =================
/// nb
/// =================
/// If you want to build a state management API directly and deal with all the unsafe and UB, we provide
/// `use_context_unchecked` with all the stability with *no* guarantess of Data Race protection. You're on
/// your own to not affect user applications.
///
/// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
/// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
pub fn use_context<C: AppContext>(&'a self) -> C {
todo!()
}
pub unsafe fn use_context_unchecked<C: AppContext>() {}
}
struct SafeContext<T> {
value: T,
// This context is pinned
_pinned: PhantomPinned,
}
+1
View File
@@ -67,6 +67,7 @@
pub mod component;
pub mod context;
pub mod contextapi;
pub mod debug_renderer;
pub mod events;
pub mod nodebuilder;
+26 -26
View File
@@ -336,7 +336,7 @@ where
#[inline]
pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
where
F: 'static + Fn(),
F: Fn(()) + 'a,
{
self.listeners.push(Listener {
event,
@@ -1052,28 +1052,28 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
Attribute { name, value }
}
// /// Create an event listener.
// ///
// /// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
// /// is the function that will be invoked when the event occurs.
// ///
// /// # Example
// ///
// /// ```no_run
// /// use dodrio::{builder::*, bumpalo::Bump};
// ///
// /// let b = Bump::new();
// ///
// /// let listener = on(&b, "click", |root, vdom, event| {
// /// // do something when a click happens...
// /// });
// /// ```
// pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
// where
// F: Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static,
// {
// Listener {
// event,
// callback: bump.alloc(callback),
// }
// }
/// Create an event listener.
///
/// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
/// is the function that will be invoked when the event occurs.
///
/// # Example
///
/// ```no_run
/// use dodrio::{builder::*, bumpalo::Bump};
///
/// let b = Bump::new();
///
/// let listener = on(&b, "click", |root, vdom, event| {
/// // do something when a click happens...
/// });
/// ```
pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
where
F: Fn(()) + 'a,
{
Listener {
event,
callback: bump.alloc(callback),
}
}
+1 -1
View File
@@ -178,7 +178,7 @@ mod velement {
/// The type of event to listen for.
pub(crate) event: &'a str,
/// The callback to invoke when the event happens.
pub(crate) callback: &'a (dyn Fn()),
pub(crate) callback: &'a (dyn Fn(())),
}
/// The key for keyed children.
+17 -8
View File
@@ -1,6 +1,7 @@
use crate::nodes::VNode;
use crate::prelude::*;
use bumpalo::Bump;
use generational_arena::Index;
use std::{
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
};
@@ -18,35 +19,43 @@ pub struct Scope {
hooks: RefCell<Vec<*mut Hook>>,
hook_arena: typed_arena::Arena<Hook>,
// Map to the parent
parent: Option<Index>,
props_type: TypeId,
caller: *const i32,
}
impl Scope {
// create a new scope from a function
pub fn new<T: 'static>(f: FC<T>) -> Self {
pub fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
// Capture the props type
let props_type = TypeId::of::<T>();
let arena = typed_arena::Arena::new();
let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new());
let caller = f as *const i32;
Self {
hook_arena: arena,
hook_arena,
hooks,
props_type,
caller,
parent,
}
}
pub fn create_context<T: Properties>(&mut self) -> Context<T> {
pub fn create_context<'runner, T: Properties>(
&'runner mut self,
components: &'runner generational_arena::Arena<Scope>,
) -> Context<T> {
Context {
_p: PhantomData {},
arena: &self.hook_arena,
hooks: &self.hooks,
idx: 0.into(),
props: T::new(),
components,
}
}
@@ -54,7 +63,7 @@ impl Scope {
/// This function downcasts the function pointer based on the stored props_type
fn run<T: 'static>(&self, f: FC<T>) {}
fn call<T: Properties + 'static>(&mut self, val: T) {
fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
if self.props_type == TypeId::of::<T>() {
/*
SAFETY ALERT
@@ -66,9 +75,9 @@ impl Scope {
This is safe because we check that the generic type matches before casting.
*/
let caller = unsafe { std::mem::transmute::<*const i32, FC<T>>(self.caller) };
let ctx = self.create_context::<T>();
// TODO: do something with these nodes
let nodes = caller(ctx);
// let ctx = self.create_context::<T>();
// // TODO: do something with these nodes
// let nodes = caller(ctx);
} else {
panic!("Do not try to use `call` on Scopes with the wrong props type")
}
+1 -1
View File
@@ -80,7 +80,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
let mut components = Arena::new();
// Create a reference to the component in the arena
let base_scope = components.insert(Scope::new(root));
let base_scope = components.insert(Scope::new(root, None));
// Create a new mount event with no root container
let first_event = LifecycleEvent::mount(base_scope, None, 0);