From 125f5426a4719bdbad42c6c166e3a6002a2ce3f0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 11 Feb 2021 23:03:01 -0500 Subject: [PATCH] Feat: push new component API and context API --- packages/core/.vscode/spellright.dict | 2 + packages/core/examples/alternative.rs | 19 ++ packages/core/examples/contextapi.rs | 64 ++----- packages/core/examples/dummy.rs | 104 ++++++++++- packages/core/examples/listener.rs | 41 +++-- packages/core/examples/step.rs | 2 +- packages/core/src/component.rs | 19 +- packages/core/src/context.rs | 251 ++++++++++++++++++++------ packages/core/src/contextapi.rs | 87 --------- packages/core/src/debug_renderer.rs | 6 +- packages/core/src/lib.rs | 16 +- packages/core/src/nodebuilder.rs | 25 +-- packages/core/src/scope.rs | 57 +++--- packages/core/src/virtual_dom.rs | 3 +- 14 files changed, 432 insertions(+), 264 deletions(-) create mode 100644 packages/core/.vscode/spellright.dict create mode 100644 packages/core/examples/alternative.rs delete mode 100644 packages/core/src/contextapi.rs diff --git a/packages/core/.vscode/spellright.dict b/packages/core/.vscode/spellright.dict new file mode 100644 index 000000000..2e96c2100 --- /dev/null +++ b/packages/core/.vscode/spellright.dict @@ -0,0 +1,2 @@ +Dodrio +VDoms diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs new file mode 100644 index 000000000..5ed5b1fe1 --- /dev/null +++ b/packages/core/examples/alternative.rs @@ -0,0 +1,19 @@ +//! An alternative function syntax +//! + +use std::marker::PhantomData; + +use dioxus_core::prelude::VNode; + +fn main() {} + +struct Context2<'a> { + _p: PhantomData<&'a ()>, +} + +type FC2<'a, 'b, 'c: 'a + 'b, P> = fn(Context2<'a>, &'b P) -> VNode<'c>; + +static Example: FC2<()> = |ctx, props| { + // + todo!() +}; diff --git a/packages/core/examples/contextapi.rs b/packages/core/examples/contextapi.rs index 06b120f49..dad7568d5 100644 --- a/packages/core/examples/contextapi.rs +++ b/packages/core/examples/contextapi.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, ops::Deref}; +use std::{borrow::Borrow, marker::PhantomData, ops::Deref}; use builder::{button, div}; use dioxus_core::prelude::*; @@ -7,34 +7,27 @@ fn main() {} struct SomeContext { items: Vec, } -/* -desired behavior: -free to move the context guard around -not free to move contents of context guard into closure +struct Props { + name: String, +} -rules: -can deref in a function -cannot drag the refs into the closure w -*/ +#[allow(unused)] +static Example: FC = |ctx, props| { + let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap()); -static Example: FC<()> = |ctx| { - // let value = use_context(&ctx, |ctx: &SomeContext| ctx.items.last().unwrap()); - - // let b = *value; - // let v2 = *value; - let cb = move |e| { - // let g = b.as_str(); - // let g = (v2).as_str(); - // let g = (value).as_str(); - // let g = b.as_str(); - }; - // let r = *value; - // let r2 = *r; - - ctx.view(|bump| { + ctx.view(move |bump| { button(bump) - .listeners([builder::on(bump, "click", cb)]) + .on("click", move |_| { + // // + println!("Value is {}", props.name); + println!("Value is {}", value.as_str()); + println!("Value is {}", *value); + }) + // + .on("click", move |_| { + println!("Value is {}", props.name); + }) .finish() }) // ctx.view(html! { @@ -48,24 +41,3 @@ static Example: FC<()> = |ctx| { // // }) }; - -#[derive(Clone, Copy)] -struct ContextGuard { - val: PhantomData, -} - -impl<'a, T> Deref for ContextGuard { - type Target = T; - - fn deref(&self) -> &Self::Target { - todo!() - } -} - -fn use_context<'scope, 'dope, 'a, P: Properties, I, O: 'a>( - ctx: &'scope Context

, - s: fn(&'a I) -> O, -) -> &'scope ContextGuard { - // ) -> &'scope ContextGuard { - todo!() -} diff --git a/packages/core/examples/dummy.rs b/packages/core/examples/dummy.rs index 43550b569..a26aa2baf 100644 --- a/packages/core/examples/dummy.rs +++ b/packages/core/examples/dummy.rs @@ -1,9 +1,101 @@ -#![allow(unused, non_upper_case_globals)] -use bumpalo::Bump; -use dioxus_core::nodebuilder::*; -use dioxus_core::prelude::VNode; -use dioxus_core::prelude::*; -use once_cell::sync::{Lazy, OnceCell}; +// #![allow(unused, non_upper_case_globals)] +// use bumpalo::Bump; +// use dioxus_core::nodebuilder::*; +// use dioxus_core::prelude::VNode; +// use dioxus_core::prelude::*; +// use once_cell::sync::{Lazy, OnceCell}; use std::{collections::HashMap, future::Future, marker::PhantomData}; +use std::ops::Deref; + +/* +A guard over underlying T that provides access in callbacks via "Copy" +*/ + +// #[derive(Clone)] +struct ContextGuard2 { + _val: std::marker::PhantomData, +} +impl Clone for ContextGuard2 { + // we aren't cloning the underlying data so clone isn't necessary + fn clone(&self) -> Self { + todo!() + } +} +impl Copy for ContextGuard2 {} + +impl ContextGuard2 { + fn get<'a>(&'a self) -> ContextLock<'a, T> { + todo!() + } +} + +struct ContextLock<'a, T> { + _val: std::marker::PhantomData<&'a T>, +} +impl<'a, T: 'a + 'static> Deref for ContextLock<'a, T> { + type Target = T; + + fn deref<'b>(&'b self) -> &'b T { + todo!() + } +} + +/* +The source of the data that gives out context guards +*/ +struct Context<'a> { + _p: std::marker::PhantomData<&'a ()>, +} + +impl<'a> Context<'a> { + fn use_context<'b, I, O: 'b>(&self, f: fn(&'b I) -> O) -> ContextGuard2 { + todo!() + } + fn add_listener(&self, f: impl Fn(()) + 'static) { + todo!() + } + + fn view(self, f: impl FnOnce(&'a String) + 'a) {} + // fn view(self, f: impl for<'b> FnOnce(&'a String) + 'a) {} + // fn view(self, f: impl for<'b> FnOnce(&'b String) + 'a) {} +} + +struct Example { + value: String, +} +/* +Example compiling +*/ +fn t<'a>(ctx: Context<'a>) { + let value = ctx.use_context(|b: &Example| &b.value); + + // Works properly, value is moved by copy into the closure + let refed = value.get(); + println!("Value is {}", refed.as_str()); + let r2 = refed.as_str(); + + ctx.add_listener(move |_| { + // let val = value.get().as_str(); + let val2 = r2.as_bytes(); + }); + + // let refed = value.deref(); + // returns &String + + // returns &String + // let refed = value.deref(); // returns &String + // let refed = value.deref(); // returns &String + + // Why does this work? This closure should be static but is holding a reference to refed + // The context guard is meant to prevent any references moving into the closure + // if the references move they might become invalid due to mutlithreading issues + ctx.add_listener(move |_| { + // let val = value.as_str(); + // let val2 = refed.as_bytes(); + }); + + ctx.view(move |b| {}); +} + fn main() {} diff --git a/packages/core/examples/listener.rs b/packages/core/examples/listener.rs index c9d5ea01c..9d7175054 100644 --- a/packages/core/examples/listener.rs +++ b/packages/core/examples/listener.rs @@ -4,19 +4,28 @@ use dioxus_core::prelude::*; fn main() {} -static Example: FC<()> = |ctx| { - let (val1, set_val1) = use_state(&ctx, || "b1"); +static Example: FC<()> = |ctx, props| { + todo!() + // let (val1, set_val1) = use_state(&ctx, || "b1"); - ctx.view(html! { -

- - - -
-

"Value is: {val1}"

-
-
- }) + // ctx.view(|bump| { + // builder::button(bump) + // .on("click", move |c| { + // // + // println!("Value is {}", val1); + // }) + // .finish() + // }) + // ctx.view(html! { + //
+ // + // + // + //
+ //

"Value is: {val1}"

+ //
+ //
+ // }) }; use use_state_def::use_state; @@ -52,8 +61,8 @@ mod use_state_def { /// } /// } /// ``` - pub fn use_state<'b, 'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>( - ctx: &'b Context<'a, P>, + pub fn use_state<'b, 'a, T: 'static, F: FnOnce() -> T + 'static>( + ctx: &'b Context<'a>, initial_state_fn: F, ) -> (&'a T, &'a impl Fn(T)) { ctx.use_hook( @@ -116,8 +125,8 @@ mod use_ref_def { /// To read the value, borrow the ref. /// To change it, use modify. /// Modifications to this value do not cause updates to the component - pub fn use_ref<'a, P, T: 'static>( - ctx: &'a Context<'a, P>, + pub fn use_ref<'a, T: 'static>( + ctx: &'a Context<'a>, initial_state_fn: impl FnOnce() -> T + 'static, ) -> &'a UseRef { ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {}) diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 551374f53..990b81183 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -25,7 +25,7 @@ impl Properties for Props { } } -static Example: FC = |ctx| { +static Example: FC = |ctx, props| { ctx.view(html! {

diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 0295dd750..7097a6ec0 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -43,20 +43,25 @@ mod tests { todo!() } - fn test_component(ctx: Context<()>) -> VNode { - ctx.view(html! {
}) - } + static TestComponent: FC<()> = |ctx, props| { + // + ctx.view(html! { +
+
+ }) + }; - fn test_component2(ctx: Context<()>) -> VNode { + static TestComponent2: FC<()> = |ctx, props| { + // ctx.view(|bump: &Bump| VNode::text("blah")) - } + }; #[test] fn ensure_types_work() { let bump = Bump::new(); // Happiness! The VNodes are now allocated onto the bump vdom - let _ = test_static_fn(&bump, test_component); - let _ = test_static_fn(&bump, test_component2); + let _ = test_static_fn(&bump, TestComponent); + let _ = test_static_fn(&bump, TestComponent2); } } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 04a894564..8e82d45a5 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -1,7 +1,7 @@ use crate::prelude::*; -use crate::scope::Hook; use crate::{inner::Scope, nodes::VNode}; use bumpalo::Bump; +use hooks::Hook; use std::{ any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize, }; @@ -26,9 +26,10 @@ 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, PropType> { +pub struct Context<'src> { + // pub struct Context<'src, PropType> { /// Direct access to the properties used to create this component. - pub props: PropType, + // pub props: &'src PropType, pub idx: AtomicUsize, // Borrowed from scope @@ -41,7 +42,8 @@ pub struct Context<'src, PropType> { pub _p: std::marker::PhantomData<&'src ()>, } -impl<'a, PropType> Context<'a, PropType> { +impl<'a> Context<'a> { + // impl<'a, PropType> Context<'a, PropType> { /// Access the children elements passed into the component pub fn children(&self) -> Vec { todo!("Children API not yet implemented for component Context") @@ -73,10 +75,12 @@ impl<'a, PropType> Context<'a, PropType> { /// 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> + 'a) -> VNode<'a> { todo!() } + pub fn callback(&self, f: impl Fn(()) + 'static) {} + /// Create a suspended component from a future. /// /// When the future completes, the component will be renderered @@ -86,62 +90,205 @@ impl<'a, PropType> Context<'a, PropType> { ) -> VNode<'a> { todo!() } +} - /// 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 - 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(&'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), - ) -> Output { - let raw_hook = { - let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed); +pub mod hooks { + //! This module provides internal state management functionality for Dioxus components + //! - // Mutate hook list if necessary - let mut hooks = self.hooks.borrow_mut(); + use super::*; - // Initialize the hook by allocating it in the typed arena. - // We get a reference from the arena which is owned by the component scope - // This is valid because "Context" is only valid while the scope is borrowed - if idx >= hooks.len() { - let new_state = initializer(); - let boxed_state: Box = Box::new(new_state); - let hook = self.arena.alloc(Hook::new(boxed_state)); + pub struct Hook(pub Box); - // Push the raw pointer instead of the &mut - // A "poor man's OwningRef" - hooks.push(hook); - } - self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + impl Hook { + pub fn new(state: Box) -> Self { + Self(state) + } + } - *hooks.get(idx).unwrap() - }; + impl<'a> Context<'a> { + // impl<'a, P> Context<'a> { + // impl<'a, P> Context<'a, P> { + /// 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 + 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(&'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), + ) -> Output { + let raw_hook = { + let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed); - /* - ** UNSAFETY ALERT ** - Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay. + // Mutate hook list if necessary + let mut hooks = self.hooks.borrow_mut(); - However, typed-arena gives a mutable reference to the stored data which is stable for any inserts - into the arena. During the first call of the function, we need to add the mutable reference given to us by - the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated - when the component itself is deallocated. + // Initialize the hook by allocating it in the typed arena. + // We get a reference from the arena which is owned by the component scope + // This is valid because "Context" is only valid while the scope is borrowed + if idx >= hooks.len() { + let new_state = initializer(); + let boxed_state: Box = Box::new(new_state); + let hook = self.arena.alloc(Hook::new(boxed_state)); - This is okay because: - - The lifetime of the component arena is tied to the lifetime of these raw hooks - - Usage of the raw hooks is tied behind the Vec refcell - - Output is static, meaning it can't take a reference to the data - - 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: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() }; + // Push the raw pointer instead of the &mut + // A "poor man's OwningRef" + hooks.push(hook); + } + self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let internal_state = borrowed_hook.0.downcast_mut::().unwrap(); + *hooks.get(idx).unwrap() + }; - runner(internal_state) + /* + ** UNSAFETY ALERT ** + Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay. + + However, typed-arena gives a mutable reference to the stored data which is stable for any inserts + into the arena. During the first call of the function, we need to add the mutable reference given to us by + the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated + when the component itself is deallocated. + + This is okay because: + - The lifetime of the component arena is tied to the lifetime of these raw hooks + - Usage of the raw hooks is tied behind the Vec refcell + - Output is static, meaning it can't take a reference to the data + - 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: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() }; + + let internal_state = borrowed_hook.0.downcast_mut::().unwrap(); + + runner(internal_state) + } } } + +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 and RemoteLock 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. + //! + //! + use super::*; + + use std::{marker::PhantomPinned, ops::Deref}; + + pub struct RemoteState { + inner: *const T, + } + impl Copy for RemoteState {} + + impl Clone for RemoteState { + fn clone(&self) -> Self { + Self { inner: self.inner } + } + } + + static DEREF_ERR_MSG: &'static str = r#""" +[ERROR] +This state management implementation is faulty. Report an issue on whatever implementation is using this. +Context should *never* be dangling!. If a Context is torn down, so should anything that references it. +"""#; + + impl Deref for RemoteState { + type Target = T; + + fn deref(&self) -> &Self::Target { + // todo! + // Try to borrow the underlying context manager, register this borrow with the manager as a "weak" subscriber. + // This will prevent the panic and ensure the pointer still exists. + // For now, just get an immutable reference to the underlying context data. + // + // It's important to note that ContextGuard is not a public API, and can only be made from UseContext. + // This guard should only be used in components, and never stored in hooks + unsafe { + match self.inner.as_ref() { + Some(ptr) => ptr, + None => panic!(DEREF_ERR_MSG), + } + } + } + } + + impl<'a> super::Context<'a> { + // impl<'a, P> super::Context<'a, P> { + pub fn use_context(&'a self, narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState { + todo!() + } + } + + /// # 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. + /// 4) Disable any &T remotely (like RwLock, RefCell, etc) + /// + /// 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 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. + fn blah() {} +} diff --git a/packages/core/src/contextapi.rs b/packages/core/src/contextapi.rs deleted file mode 100644 index 19c3c84fa..000000000 --- a/packages/core/src/contextapi.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! 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 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(&'a self) -> C { - todo!() - } - - pub unsafe fn use_context_unchecked() {} -} - -struct SafeContext { - value: T, - - // This context is pinned - _pinned: PhantomPinned, -} diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index fe6177c25..76e977768 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -28,7 +28,11 @@ mod tests { #[test] fn ensure_creation() -> Result<(), ()> { - let mut dom = VirtualDom::new(|ctx| ctx.view(html! {
"hello world"
})); + let mut dom = VirtualDom::new(|ctx, props| { + // + ctx.view(html! {
"hello world"
}) + }); + dom.progress()?; Ok(()) } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index ae238c76b..ef1cfef27 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -31,8 +31,8 @@ //! #[derive(Properties)] //! struct Props { name: String } //! -//! static Example: FC = |ctx| { -//! html! {
"Hello {ctx.props.name}!"
} +//! static Example: FC = |ctx, props| { +//! html! {
"Hello {props.name}!"
} //! } //! ``` //! @@ -40,7 +40,7 @@ //! ``` //! use dioxus_core::prelude::*; //! -//! #[functional_component] +//! #[fc] //! static Example: FC = |ctx, name: String| { //! html! {
"Hello {name}!"
} //! } @@ -67,7 +67,6 @@ pub mod component; pub mod context; -pub mod contextapi; pub mod debug_renderer; pub mod events; pub mod nodebuilder; @@ -83,15 +82,18 @@ pub mod builder { // types used internally that are important pub(crate) mod inner { pub use crate::component::{Component, Properties}; + use crate::context::hooks::Hook; pub use crate::context::Context; use crate::nodes; - pub use crate::scope::{Hook, Scope}; + pub use crate::scope::Scope; pub use crate::virtual_dom::VirtualDom; pub use nodes::*; // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; + + pub type FC

= for<'a> fn(Context<'a>, &'a P) -> VNode<'a>; + // pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate @@ -115,7 +117,7 @@ pub mod prelude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; + pub use crate::inner::FC; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 26b9b7f31..c8318438d 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -334,14 +334,15 @@ where /// .finish(); /// ``` #[inline] - pub fn on(mut self, event: &'a str, callback: F) -> Self - where - F: Fn(()) + 'a, + pub fn on(mut self, event: &'a str, callback: impl Fn(()) + 'a) -> Self +// pub fn on(mut self, event: &'a str, callback: impl Fn(()) -> () + 'static) -> Self +// F: Fn(()) + 'static, + // F: Fn(()) + 'a, { - self.listeners.push(Listener { - event, - callback: self.bump.alloc(callback), - }); + // self.listeners.push(Listener { + // event, + // callback: self.bump.alloc(callback), + // }); self } } @@ -1068,10 +1069,12 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> { /// // do something when a click happens... /// }); /// ``` -pub fn on<'a, 'b, F: 'b>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a> -where - 'b: 'a + 'static, - F: Fn(()) + 'b, +pub fn on<'a, 'b, F: 'static>( + bump: &'a Bump, + event: &'a str, + callback: impl Fn(()) + 'static, +) -> Listener<'a> +// F: Fn(()) + 'b, { Listener { event, diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index f9533c710..3231d0269 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,9 +1,11 @@ +use crate::context::hooks::Hook; +use crate::inner::*; 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, + any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData, + sync::atomic::AtomicUsize, }; /// Every component in Dioxus is represented by a `Scope`. @@ -48,13 +50,15 @@ impl Scope { pub fn create_context<'runner, T: Properties>( &'runner mut self, components: &'runner generational_arena::Arena, - ) -> Context { + props: &'runner T, + ) -> Context { + // ) -> Context { Context { _p: PhantomData {}, arena: &self.hook_arena, hooks: &self.hooks, idx: 0.into(), - props: T::new(), + // props, components, } } @@ -62,32 +66,27 @@ 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 fn run(&self, f: FC) {} - - fn call<'a, T: Properties + 'static>(&'a mut self, val: T) { - if self.props_type == TypeId::of::() { - /* - SAFETY ALERT - - 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. - - This is safe because we check that the generic type matches before casting. - */ - let caller = unsafe { std::mem::transmute::<*const i32, FC>(self.caller) }; - // let ctx = self.create_context::(); - // // 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") - } - } } -pub struct Hook(pub Box); +mod bad_unsafety { + // todo + // fn call<'a, T: Properties + 'static>(&'a mut self, val: T) { + // if self.props_type == TypeId::of::() { + // /* + // SAFETY ALERT -impl Hook { - pub fn new(state: Box) -> Self { - Self(state) - } + // 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. + + // This is safe because we check that the generic type matches before casting. + // */ + // let caller = unsafe { std::mem::transmute::<*const i32, FC>(self.caller) }; + // // let ctx = self.create_context::(); + // // // 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") + // } + // } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 151eedd36..76219e8a1 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -142,7 +142,8 @@ impl VirtualDom

{ pub async fn progess_completely() {} /// Create a new context object for a given component and scope - fn new_context(&self) -> Context { + fn new_context(&self) -> Context { + // fn new_context(&self) -> Context { todo!() }