Feat: update solved problems with context API

This commit is contained in:
Jonathan Kelley 2021-02-10 12:48:02 -05:00
parent 24e8bcb028
commit c97a9647e3
7 changed files with 209 additions and 9 deletions

View file

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

View file

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

View file

@ -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.
This can be enforced by us or by implementers.
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
## Concurrency
I don't even know yet

View file

@ -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 {

View file

@ -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!()
}

View file

@ -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,

View file

@ -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.