mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Feat: update solved problems with context API
This commit is contained in:
parent
24e8bcb028
commit
c97a9647e3
7 changed files with 209 additions and 9 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.inlayHints.enable": false
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
}
|
4
.vscode/spellright.dict
vendored
4
.vscode/spellright.dict
vendored
|
@ -18,3 +18,7 @@ Jemalloc
|
|||
Cloudfare
|
||||
webapps
|
||||
downcasting
|
||||
vec
|
||||
deref
|
||||
derefs
|
||||
Derefing
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
packages/core/examples/contextapi.rs
Normal file
71
packages/core/examples/contextapi.rs
Normal 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!()
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue