Pārlūkot izejas kodu

Feat: update solved problems with context API

Jonathan Kelley 4 gadi atpakaļ
vecāks
revīzija
c97a9647e3

+ 1 - 1
.vscode/settings.json

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

+ 4 - 0
.vscode/spellright.dict

@@ -18,3 +18,7 @@ Jemalloc
 Cloudfare
 webapps
 downcasting
+vec
+deref
+derefs
+Derefing

+ 78 - 4
SOLVEDPROBLEMS.md

@@ -1,5 +1,16 @@
 # Solved problems while building Dioxus
 
+focuses:
+- ergonomics
+- render agnostic
+- remote coupling
+- memory efficient
+- concurrent 
+- global context
+- scheduled updates
+- 
+
+
 ## FC Macro for more elegant components
 Originally the syntax of the FC macro was meant to look like:
 
@@ -160,14 +171,77 @@ At runtime, the new closure is created that captures references to `ctx`. Theref
 
 
 ## Context and lifetimes
-> SAFETY hole
 
 We want components to be able to fearlessly "use_context" for use in state management solutions.
 
 However, we cannot provide these guarantees without compromising the references. If a context mutates, it cannot lend out references.
 
-Functionally, this can be solved with UnsafeCell and runtime dynamics. Essentially, if a context mutates, then any affected components would need
-to be updated, even if they themselves aren't updated. Otherwise, a handler would be pointing to 
+Functionally, this can be solved with UnsafeCell and runtime dynamics. Essentially, if a context mutates, then any affected components would need to be updated, even if they themselves aren't updated. Otherwise, a reference would be pointing at data that could have potentially been moved. 
+
+To do this safely is a pretty big challenge. We need to provide a method of sharing data that is safe, ergonomic, and that fits the abstraction model.
+
+Enter, the "ContextGuard".
+
+The "ContextGuard" is very similar to a Ref/RefMut from the RefCell implementation, but one that derefs into actual underlying value. 
+
+However, derefs of the ContextGuard are a bit more sophisticated than the Ref model. 
+
+For RefCell, when a Ref is taken, the RefCell is now "locked." This means you cannot take another `borrow_mut` instance while the Ref is still active. For our purposes, our modification phase is very limited, so we can make more assumptions about what is safe.
+
+1. We can pass out ContextGuards from any use of use_context. These don't actually lock the value until used.
+2. The ContextGuards only lock the data while the component is executing and when a callback is running.
+3. Modifications of the underlying context occur after a component is rendered and after the event has been run.
+   
+With the knowledge that usage of ContextGuard can only be achieved in a component context and the above assumptions, we can design a guard that prevents any poor usage but also is ergonomic.
+
+As such, the design of the ContextGuard must:
+- be /copy/ for easy moves into closures
+- never point to invalid data (no dereferencing of raw pointers after movable data has been changed (IE a vec has been resized))
+- not allow references of underlying data to leak into closures
+
+To solve this, we can be clever with lifetimes to ensure that any data is protected, but context is still ergonomic.
+
+1. As such, deref context guard returns an element with a lifetime bound to the borrow of the guard. 
+2. Because we cannot return locally borrowed data AND we consume context, this borrow cannot be moved into a closure. 
+3. ContextGuard is *copy* so the guard itself can be moved into closures
+4. ContextGuard derefs with its unique lifetime *inside* closures
+5. Derefing a ContextGuard evaluates the underlying selector to ensure safe temporary access to underlying data
+
+```rust
+struct ExampleContext {
+    // unpinnable objects with dynamic sizing
+    items: Vec<String>
+}
+
+fn Example<'src>(ctx: Context<'src, ()>) -> VNode<'src> {
+    let val: &'b ContextGuard<ExampleContext> = (&'b ctx).use_context(|context: &'other ExampleContext| {
+        // always select the last element
+        context.items.last()
+    });
+
+    let handler1 = move |_| println!("Value is {}", val); // deref coercion performed here for printing
+    let handler2 = move |_| println!("Value is {}", val); // deref coercion performed here for printing
+
+    ctx.view(html! {
+        <div>
+            <button onclick={handler1}> "Echo value with h1" </button>
+            <button onclick={handler2}> "Echo value with h2" </button>
+            <div>
+                <p> "Value is: {val}" </p>
+            </div>
+        </div>
+    })
+}
+```
+
+A few notes:
+- this does *not* protect you from data races!!!
+- this does *not* force rendering of components
+- this *does* protect you from invalid + UB use of data
+- this approach leaves lots of room for fancy state management libraries
+- this approach is fairly quick, especially if borrows can be cached during usage phases
+
 
-This can be enforced by us or by implementers.
+## Concurrency
 
+I don't even know yet

+ 47 - 1
examples/example_app.rs

@@ -6,8 +6,54 @@
 //!
 //! cargo run --features dioxus/static
 
+use std::u32;
+
+use dioxus::prelude::Context;
+
+// #[allow(unused_lifetimes)]
+#[derive(Debug, PartialEq, Hash)]
+struct Pool<'a> {
+    a: u32,
+    b: &'a str,
+}
+
+struct UserData {}
+type Query<In, Out> = fn(&Pool, In) -> Result<Out, ()>;
+// type Query<In, Out> = fn(&Pool, In) -> Out;
+
+static GET_USER: Query<String, Vec<UserData>> = |pool, name| {
+    //
+    let b = Ok(())?;
+    let b = Ok(())?;
+    let b = Ok(())?;
+    let b = Ok(())?;
+    let b = Ok(())?;
+    let b = Ok(())?;
+    todo!()
+};
+
+static SET_USER: Query<String, Vec<UserData>> = |pool, name| {
+    //
+    todo!()
+};
+
 fn main() {
-    // DioxusApp::new(launch)
+    //     // returns a future
+    //     let user_data = use_db(&ctx, GET_USER, || "Bill");
+
+    //     use_try_suspense(&ctx, async move {
+    //         match user_data.await? {
+    //             Ok() => {}
+    //             Err(err) => {}
+    //         }
+    //     })
+    // }
+
+    // fn use_try_suspense(ctx: &Context<()>) {
+    //     let c: Result<(), ()> = {
+    //         // let b = Ok(());
+    //         // b?
+    //     };
 }
 
 mod launches {

+ 71 - 0
packages/core/examples/contextapi.rs

@@ -0,0 +1,71 @@
+use std::{marker::PhantomData, ops::Deref};
+
+use builder::{button, div};
+use dioxus_core::prelude::*;
+
+fn main() {}
+struct SomeContext {
+    items: Vec<String>,
+}
+/*
+desired behavior:
+
+free to move the context guard around
+not free to move contents of context guard into closure
+
+rules:
+can deref in a function
+cannot drag the refs into the closure w
+*/
+
+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| {
+        button(bump)
+            .listeners([builder::on(bump, "click", cb)])
+            .finish()
+    })
+    // ctx.view(html! {
+    //     <div>
+    //         <button onclick={move |_| println!("Value is {}", value)} />
+    //         <button onclick={move |_| println!("Value is {}", value)} />
+    //         <button onclick={move |_| println!("Value is {}", value)} />
+    //         <div>
+    //             <p> "Value is: {val}" </p>
+    //         </div>
+    //     </div>
+    // })
+};
+
+#[derive(Clone, Copy)]
+struct ContextGuard<T> {
+    val: PhantomData<T>,
+}
+
+impl<'a, T> Deref for ContextGuard<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        todo!()
+    }
+}
+
+fn use_context<'scope, 'dope, 'a, P: Properties, I, O: 'a>(
+    ctx: &'scope Context<P>,
+    s: fn(&'a I) -> O,
+) -> &'scope ContextGuard<O> {
+    // ) -> &'scope ContextGuard<O> {
+    todo!()
+}

+ 3 - 2
packages/core/src/nodebuilder.rs

@@ -1068,9 +1068,10 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
 ///     // do something when a click happens...
 /// });
 /// ```
-pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
+pub fn on<'a, 'b, F: 'b>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
 where
-    F: Fn(()) + 'a,
+    'b: 'a + 'static,
+    F: Fn(()) + 'b,
 {
     Listener {
         event,

+ 5 - 1
packages/core/src/nodes.rs

@@ -174,7 +174,11 @@ mod velement {
     }
 
     /// An event listener.
-    pub struct Listener<'a> {
+    pub struct Listener<'a>
+// pub struct Listener<'a, 'b>
+    // where
+    //     'b: 'a + 'static,
+    {
         /// The type of event to listen for.
         pub(crate) event: &'a str,
         /// The callback to invoke when the event happens.