浏览代码

Feat: more updates to hooks

Jonathan Kelley 4 年之前
父节点
当前提交
2c2882c9a2

+ 1 - 1
.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": false
+    "rust-analyzer.inlayHints.enable": true
 }

+ 2 - 1
packages/core/Cargo.toml

@@ -11,7 +11,8 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
 
 [dependencies]
 # dodrio-derive = { path = "../html-macro-2", version = "0.1.0" }
-dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
+# dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
+dioxus-html-2 = { path = "../html-macro-2", version = "0.1.0" }
 dioxus-core-macro = { path = "../core-macro" }
 # Backs some static data
 once_cell = "1.5.2"

+ 14 - 12
packages/core/examples/macrosrc.rs

@@ -55,27 +55,29 @@ impl<'src> Properties for Props<'src> {
     }
 }
 
-fn component<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
+fn component<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
     // Write asynchronous rendering code that immediately returns a "suspended" VNode
     // The concurrent API will then progress this component when the future finishes
     // You can suspend the entire component, or just parts of it
     let product_list = ctx.suspend(async {
         // Suspend the rendering that completes when the future is done
         match fetch_data().await {
-            Ok(data) => html! { <div> </div>},
-            Err(_) => html! { <div> </div>},
+            Ok(data) => html! { <div> "success!" </div>},
+            Err(_) => html! { <div> "failure :(" </div>},
         }
     });
 
-    todo!()
-    // ctx.view(html! {
-    //     <div>
-    //         // <h1> "Products" </h1>
-    //         // // Subnodes can even be suspended
-    //         // // When completely rendered, they won't cause the component itself to re-render, just their slot
-    //         // <p> { product_list } </p>
-    //     </div>
-    // })
+    // todo!()
+    ctx.view(html! {
+        <div>
+            <h1> "Products" </h1>
+            <button> "hello!" </button>
+            // Subnodes can even be suspended
+            // When completely rendered, they won't cause the component itself to re-render, just their slot
+
+            // <p> { product_list } </p>
+        </div>
+    })
 }
 
 fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {

+ 136 - 19
packages/core/examples/sketch.rs

@@ -1,43 +1,160 @@
 use bumpalo::Bump;
-use dioxus_core::prelude::{Context, VNode};
-use std::{any::Any, cell::RefCell, rc::Rc};
+use dioxus_core::{
+    prelude::{Context, VElement, VNode, FC},
+    virtual_dom::{Properties, Scope},
+};
+use std::{
+    any::Any,
+    borrow::BorrowMut,
+    cell::RefCell,
+    mem::swap,
+    ops::{Deref, DerefMut},
+    rc::Rc,
+};
 use std::{borrow::Borrow, sync::atomic::AtomicUsize};
 use typed_arena::Arena;
 
 fn main() {
-    let ar = Arena::new();
+    let mut scope = Scope::new(component);
 
     (0..5).for_each(|f| {
-        // Create the temporary context obect
-        let c = Context {
-            _p: std::marker::PhantomData {},
-            props: (),
-            idx: 0.into(),
-            arena: &ar,
-            hooks: RefCell::new(Vec::new()),
-        };
-
-        component(c);
+        let ctx = scope.create_context();
+        component(ctx);
     });
 }
 
 // we need to do something about props and context being borrowed from different sources....
 // kinda anooying
 /// use_ref creates a new value when the component is created and then borrows that value on every render
-fn component(ctx: Context<()>) {
+fn component(ctx: Context<()>) -> VNode {
     (0..10).for_each(|f| {
         let r = use_ref(&ctx, move || f);
-        assert_eq!(*r, f);
     });
+    todo!()
 }
 
+pub struct UseRef<T: 'static> {
+    current: RefCell<T>,
+}
+impl<T: 'static> UseRef<T> {
+    fn new(val: T) -> Self {
+        Self {
+            current: RefCell::new(val),
+        }
+    }
+
+    fn modify(&self, modifier: impl FnOnce(&mut T)) {
+        let mut val = self.current.borrow_mut();
+        let val_as_ref = val.deref_mut();
+        modifier(val_as_ref);
+    }
+}
+
+/// Store a mutable value between renders!
+/// 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>,
     initial_state_fn: impl FnOnce() -> T + 'static,
-) -> &'a T {
+) -> &'a UseRef<T> {
     ctx.use_hook(
-        || initial_state_fn(), // initializer
-        |state| state,         // runner, borrows the internal value
-        |b| {},                // tear down
+        || UseRef::new(initial_state_fn()),
+        |state, _| &*state,
+        |_| {},
     )
 }
+
+struct UseState<T> {
+    new_val: Rc<RefCell<Option<T>>>,
+    current_val: T,
+    caller: Box<dyn Fn(T) + 'static>,
+}
+
+/// Store state between component renders!
+/// When called, this hook retrives a stored value and provides a setter to update that value.
+/// When the setter is called, the component is re-ran with the new value.
+///
+/// This is behaves almost exactly the same way as React's "use_state".
+///
+/// Usage:
+/// ```ignore
+/// static Example: FC<()> = |ctx| {
+///     let (counter, set_counter) = use_state(ctx, || 0);
+///     let increment = || set_couter(counter + 1);
+///     let decrement = || set_couter(counter + 1);
+///
+///     html! {
+///         <div>
+///             <h1>"Counter: {counter}" </h1>
+///             <button onclick={increment}> "Increment" </button>
+///             <button onclick={decrement}> "Decrement" </button>
+///         </div>  
+///     }
+/// }
+/// ```
+pub fn use_state<'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>(
+    ctx: &'a Context<'a, P>,
+    initial_state_fn: F,
+) -> (&T, &impl Fn(T)) {
+    ctx.use_hook(
+        move || UseState {
+            new_val: Rc::new(RefCell::new(None)),
+            current_val: initial_state_fn(),
+            caller: Box::new(|_| println!("setter called!")),
+        },
+        move |hook, updater| {
+            let inner = hook.new_val.clone();
+
+            // todo: swap out the caller with a subscription call and an internal update
+            hook.caller = Box::new(move |new_val| {
+                // update the setter with the new value
+                let mut new_inner = inner.as_ref().borrow_mut();
+                *new_inner = Some(new_val);
+            });
+
+            // box gets derefed into a ref which is then taken as ref with the hook
+            (&hook.current_val, &hook.caller)
+        },
+        |_| {},
+    )
+}
+
+fn test_use_state(ctx: Context<()>) -> VNode {
+    let (val, set_val) = use_state(&ctx, || 10);
+
+    // gloriousness!
+    // closures are taken with ref to ctx :)
+    // Can freely use hooks
+    let handler_0: _ = || set_val(val + 1);
+    let handler_1: _ = || set_val(val + 10);
+    let handler_2: _ = || set_val(val + 100);
+
+    // these fns are captured, boxed into the bump arena, and then attached to the listeners
+    // the vnodes share the lifetime of these closures (and the hook data)
+    // whenever a listener wakes up, we take the reference directly from the bump arena and, with a small bit
+    // of unsafe code, execute the associated closure / listener function
+    // Those vnodes are then tossed out and new ones are installed, meaning and old references (potentially bad)
+    // are removed and UB is prevented from affecting the program
+    {
+        VNode::Element(VElement::new("button"))
+    }
+}
+
+fn test_use_state_2(ctx: Context<()>) -> VNode {
+    let (val, set_val) = use_state(&ctx, || 0);
+
+    let incr = || set_val(val + 1);
+    let decr = || set_val(val - 1);
+
+    todo!()
+    // html! {
+    //     <div>
+    //         <nav class="menu">
+    //             <button onclick=incr> { "Increment" } </button>
+    //             <button onclick=decr> { "Decrement" } </button>
+    //         </nav>
+    //         <p> <b>{ "Current value: {val}" }</b> </p>
+    //     </div>
+    // }
+}

+ 3 - 5
packages/core/src/lib.rs

@@ -76,9 +76,6 @@ pub mod builder {
 
 /// Re-export common types for ease of development use.
 /// Essential when working with the html! macro
-///
-///
-///
 pub mod prelude {
     use crate::nodes;
     pub use crate::virtual_dom::{Context, VirtualDom};
@@ -86,7 +83,8 @@ 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<P> = for<'a> fn(&'a Context<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
     // hack the VNode type until VirtualNode is fixed in the macro crate
@@ -100,7 +98,7 @@ pub mod prelude {
 
     // Re-export the FC macro
     pub use dioxus_core_macro::fc;
-    pub use dioxus_html_macro::html;
+    pub use dioxus_html_2::html;
 
     pub use crate as dioxus;
 

+ 40 - 18
packages/core/src/virtual_dom.rs

@@ -55,6 +55,7 @@ use std::{
     any::TypeId,
     cell::{RefCell, UnsafeCell},
     future::Future,
+    marker::PhantomData,
     sync::atomic::AtomicUsize,
 };
 
@@ -245,15 +246,18 @@ mod fc_test {
         // g
     }
 
-    fn test_component<'a>(ctx: &'a Context<()>) -> VNode<'a> {
+    fn test_component(ctx: Context<()>) -> VNode {
         // todo: helper should be part of html! macro
         todo!()
         // ctx.view(|bump| html! {bump,  <div> </div> })
     }
 
-    fn test_component2<'a>(ctx: &'a Context<()>) -> VNode<'a> {
+    fn test_component2(ctx: Context<()>) -> VNode {
         ctx.view(|bump: &Bump| VNode::text("blah"))
     }
+    // fn test_component2<'a>(ctx: &'a Context<()>) -> VNode<'a> {
+    //     ctx.view(|bump: &Bump| VNode::text("blah"))
+    // }
 
     #[test]
     fn ensure_types_work() {
@@ -272,32 +276,41 @@ mod fc_test {
 /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components
 /// The actualy contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
 pub struct Scope {
-    hook_idx: i32,
-    hooks: Vec<OLDHookState>,
+    arena: typed_arena::Arena<Hook>,
+    hooks: RefCell<Vec<*mut Hook>>,
     props_type: TypeId,
 }
 
 impl Scope {
     // create a new scope from a function
-    fn new<T: 'static>(f: FC<T>) -> Self {
+    pub fn new<T: 'static>(f: FC<T>) -> Self {
         // Capture the props type
         let props_type = TypeId::of::<T>();
+        let arena = typed_arena::Arena::new();
+        let hooks = RefCell::new(Vec::new());
 
-        // Obscure the function
         Self {
-            hook_idx: 0,
-            hooks: vec![],
+            arena,
+            hooks,
             props_type,
         }
     }
 
+    pub fn create_context<T: Properties>(&mut self) -> Context<T> {
+        Context {
+            _p: PhantomData {},
+            arena: &self.arena,
+            hooks: &self.hooks,
+            idx: 0.into(),
+            props: T::new(),
+        }
+    }
+
     /// 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() {}
 }
 
-pub struct OLDHookState {}
-
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
 /// This lets components schedule updates, integrate hooks, and expose their context via the context api.
 ///
@@ -322,8 +335,13 @@ pub struct Context<'src, T> {
     /// Direct access to the properties used to create this component.
     pub props: T,
     pub idx: AtomicUsize,
-    pub arena: &'src typed_arena::Arena<Hook>,
-    pub hooks: RefCell<Vec<*mut Hook>>,
+
+    // Borrowed from scope
+    arena: &'src typed_arena::Arena<Hook>,
+    hooks: &'src RefCell<Vec<*mut Hook>>,
+
+    // holder for the src lifetime
+    // todo @jon remove this
     pub _p: std::marker::PhantomData<&'src ()>,
 }
 
@@ -370,16 +388,16 @@ 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: 'static>(
+    pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
         &'comp self,
         // The closure that builds the hook state
         initializer: impl FnOnce() -> InternalHookState,
         // The closure that takes the hookstate and returns some value
-        runner: impl for<'b> FnOnce(&'comp mut InternalHookState) -> &'comp Output,
+        runner: impl FnOnce(&'comp 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
-        tear_down: impl FnOnce(InternalHookState),
-    ) -> &'comp Output {
+        cleanup: impl FnOnce(InternalHookState),
+    ) -> Output {
         let raw_hook = {
             let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
 
@@ -408,7 +426,7 @@ impl<'a, T> Context<'a, T> {
         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 use by
+        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.
 
@@ -417,6 +435,7 @@ impl<'a, T> Context<'a, T> {
         - 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: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
 
@@ -425,7 +444,10 @@ impl<'a, T> Context<'a, T> {
             .downcast_mut::<InternalHookState>()
             .unwrap();
 
-        runner(internal_state)
+        // todo: set up an updater with the subscription API
+        let updater = ();
+
+        runner(internal_state, updater)
     }
 }
 

+ 6 - 2
packages/html-macro-2/Cargo.toml

@@ -1,7 +1,7 @@
 [package]
-name = "dodrio-derive"
+name = "dioxus-html-2"
 version = "0.1.0"
-authors = ["Richard Dodd <richard.o.dodd@gmail.com>"]
+authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
 edition = "2018"
 
 [lib]
@@ -15,3 +15,7 @@ quote = "1.0.3"
 proc-macro-hack = "0.5.15"
 proc-macro2 = "1.0.10"
 style-shared = { git = "https://github.com/derekdreery/style" }
+
+
+[dev-dependencies]
+bumpalo = "*"

+ 48 - 0
packages/html-macro-2/examples/proof.rs

@@ -0,0 +1,48 @@
+use bumpalo::Bump;
+use dioxus_html_2::html;
+
+mod dioxus {
+    pub use bumpalo;
+    pub mod builder {
+        use bumpalo::Bump;
+    }
+}
+
+fn main() {
+    /*
+    Th below code is not meant to compile, but it is meant to expand properly
+
+
+    */
+    // let l = html! {
+    //     <div>
+    //         <div>
+    //             <h1>"asdl"</h1>
+    //             <h1>"asdl"</h1>
+    //             <h1>"asdl"</h1>
+    //             <h1>"asdl"</h1>
+    //         </div>
+    //     </div>
+    // };
+
+    // let l = move |bump| {
+    //     dioxus::builder::div(bump)
+    //         .children([dioxus::builder::div(bump)
+    //             .children([
+    //                 dioxus::builder::h1(bump)
+    //                     .children([dioxus::builder::text("asdl")])
+    //                     .finish(),
+    //                 dioxus::builder::h1(bump)
+    //                     .children([dioxus::builder::text("asdl")])
+    //                     .finish(),
+    //                 dioxus::builder::h1(bump)
+    //                     .children([dioxus::builder::text("asdl")])
+    //                     .finish(),
+    //                 dioxus::builder::h1(bump)
+    //                     .children([dioxus::builder::text("asdl")])
+    //                     .finish(),
+    //             ])
+    //             .finish()])
+    //         .finish()
+    // };
+}

+ 36 - 26
packages/html-macro-2/src/lib.rs

@@ -21,14 +21,14 @@ pub fn html(s: TokenStream) -> TokenStream {
 }
 
 struct HtmlRender {
-    ctx: Ident,
+    // ctx: Ident,
     kind: NodeOrList,
 }
 
 impl Parse for HtmlRender {
     fn parse(s: ParseStream) -> Result<Self> {
-        let ctx: Ident = s.parse()?;
-        s.parse::<Token![,]>()?;
+        // let ctx: Ident = s.parse()?;
+        // s.parse::<Token![,]>()?;
         // if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
         let kind = if s.peek(token::Bracket) {
             let nodes_toks;
@@ -42,13 +42,20 @@ impl Parse for HtmlRender {
         } else {
             NodeOrList::Node(s.parse()?)
         };
-        Ok(HtmlRender { ctx, kind })
+        Ok(HtmlRender { kind })
     }
 }
 
 impl ToTokens for HtmlRender {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        ToToksCtx::new(&self.ctx, &self.kind).to_tokens(tokens)
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
+        let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
+
+        // create a lazy tree that accepts a bump allocator
+        let final_tokens = quote! {
+            move |bump| { #new_toks }
+        };
+
+        final_tokens.to_tokens(out_tokens);
     }
 }
 
@@ -57,7 +64,7 @@ enum NodeOrList {
     List(NodeList),
 }
 
-impl ToTokens for ToToksCtx<'_, &NodeOrList> {
+impl ToTokens for ToToksCtx<&NodeOrList> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self.inner {
             NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens),
@@ -68,12 +75,12 @@ impl ToTokens for ToToksCtx<'_, &NodeOrList> {
 
 struct NodeList(Vec<MaybeExpr<Node>>);
 
-impl ToTokens for ToToksCtx<'_, &NodeList> {
+impl ToTokens for ToToksCtx<&NodeList> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let ctx = &self.ctx;
+        // let ctx = &self.ctx;
         let nodes = self.inner.0.iter().map(|node| self.recurse(node));
         tokens.append_all(quote! {
-            dioxus::bumpalo::vec![in #ctx.bump;
+            dioxus::bumpalo::vec![in bump;
                 #(#nodes),*
             ]
         });
@@ -85,7 +92,7 @@ enum Node {
     Text(TextNode),
 }
 
-impl ToTokens for ToToksCtx<'_, &Node> {
+impl ToTokens for ToToksCtx<&Node> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match &self.inner {
             Node::Element(el) => self.recurse(el).to_tokens(tokens),
@@ -116,12 +123,12 @@ struct Element {
     children: MaybeExpr<Vec<Node>>,
 }
 
-impl ToTokens for ToToksCtx<'_, &Element> {
+impl ToTokens for ToToksCtx<&Element> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let ctx = self.ctx;
+        // let ctx = self.ctx;
         let name = &self.inner.name;
         tokens.append_all(quote! {
-            dioxus::builder::#name(&#ctx)
+            dioxus::builder::#name(bump)
         });
         for attr in self.inner.attrs.iter() {
             self.recurse(attr).to_tokens(tokens);
@@ -249,7 +256,7 @@ impl Parse for Attr {
     }
 }
 
-impl ToTokens for ToToksCtx<'_, &Attr> {
+impl ToTokens for ToToksCtx<&Attr> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = self.inner.name.to_string();
         let mut attr_stream = TokenStream2::new();
@@ -283,7 +290,7 @@ impl Parse for TextNode {
     }
 }
 
-impl ToTokens for ToToksCtx<'_, &TextNode> {
+impl ToTokens for ToToksCtx<&TextNode> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let mut token_stream = TokenStream2::new();
         self.recurse(&self.inner.0).to_tokens(&mut token_stream);
@@ -310,10 +317,10 @@ impl<T: Parse> Parse for MaybeExpr<T> {
     }
 }
 
-impl<'a, T> ToTokens for ToToksCtx<'a, &'a MaybeExpr<T>>
+impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
 where
     T: 'a,
-    ToToksCtx<'a, &'a T>: ToTokens,
+    ToToksCtx<&'a T>: ToTokens,
 {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match &self.inner {
@@ -324,25 +331,28 @@ where
 }
 
 /// ToTokens context
-struct ToToksCtx<'a, T> {
+struct ToToksCtx<T> {
+    // struct ToToksCtx<'a, T> {
     inner: T,
-    ctx: &'a Ident,
+    // ctx: &'a Ident,
 }
 
-impl<'a, T> ToToksCtx<'a, T> {
-    fn new(ctx: &'a Ident, inner: T) -> Self {
-        ToToksCtx { ctx, inner }
+impl<'a, T> ToToksCtx<T> {
+    fn new(inner: T) -> Self {
+        // fn new(ctx: &'a Ident, inner: T) -> Self {
+        ToToksCtx { inner }
     }
 
-    fn recurse<U>(&self, inner: U) -> ToToksCtx<'a, U> {
+    fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
+        // fn recurse<U>(&self, inner: U) -> ToToksCtx<'a, U> {
         ToToksCtx {
-            ctx: &self.ctx,
+            // ctx: &self.ctx,
             inner,
         }
     }
 }
 
-impl ToTokens for ToToksCtx<'_, &LitStr> {
+impl ToTokens for ToToksCtx<&LitStr> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         self.inner.to_tokens(tokens)
     }