mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
Feat: push new component API and context API
This commit is contained in:
parent
8329268d39
commit
125f5426a4
14 changed files with 432 additions and 264 deletions
2
packages/core/.vscode/spellright.dict
vendored
Normal file
2
packages/core/.vscode/spellright.dict
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Dodrio
|
||||||
|
VDoms
|
19
packages/core/examples/alternative.rs
Normal file
19
packages/core/examples/alternative.rs
Normal file
|
@ -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!()
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{marker::PhantomData, ops::Deref};
|
use std::{borrow::Borrow, marker::PhantomData, ops::Deref};
|
||||||
|
|
||||||
use builder::{button, div};
|
use builder::{button, div};
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
@ -7,34 +7,27 @@ fn main() {}
|
||||||
struct SomeContext {
|
struct SomeContext {
|
||||||
items: Vec<String>,
|
items: Vec<String>,
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
desired behavior:
|
|
||||||
|
|
||||||
free to move the context guard around
|
struct Props {
|
||||||
not free to move contents of context guard into closure
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
rules:
|
#[allow(unused)]
|
||||||
can deref in a function
|
static Example: FC<Props> = |ctx, props| {
|
||||||
cannot drag the refs into the closure w
|
let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap());
|
||||||
*/
|
|
||||||
|
|
||||||
static Example: FC<()> = |ctx| {
|
ctx.view(move |bump| {
|
||||||
// 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)
|
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()
|
.finish()
|
||||||
})
|
})
|
||||||
// ctx.view(html! {
|
// ctx.view(html! {
|
||||||
|
@ -48,24 +41,3 @@ static Example: FC<()> = |ctx| {
|
||||||
// </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!()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,101 @@
|
||||||
#![allow(unused, non_upper_case_globals)]
|
// #![allow(unused, non_upper_case_globals)]
|
||||||
use bumpalo::Bump;
|
// use bumpalo::Bump;
|
||||||
use dioxus_core::nodebuilder::*;
|
// use dioxus_core::nodebuilder::*;
|
||||||
use dioxus_core::prelude::VNode;
|
// use dioxus_core::prelude::VNode;
|
||||||
use dioxus_core::prelude::*;
|
// use dioxus_core::prelude::*;
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
// use once_cell::sync::{Lazy, OnceCell};
|
||||||
use std::{collections::HashMap, future::Future, marker::PhantomData};
|
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<T> {
|
||||||
|
_val: std::marker::PhantomData<T>,
|
||||||
|
}
|
||||||
|
impl<T> Clone for ContextGuard2<T> {
|
||||||
|
// we aren't cloning the underlying data so clone isn't necessary
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Copy for ContextGuard2<T> {}
|
||||||
|
|
||||||
|
impl<T> ContextGuard2<T> {
|
||||||
|
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<O> {
|
||||||
|
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() {}
|
fn main() {}
|
||||||
|
|
|
@ -4,19 +4,28 @@ use dioxus_core::prelude::*;
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
static Example: FC<()> = |ctx| {
|
static Example: FC<()> = |ctx, props| {
|
||||||
let (val1, set_val1) = use_state(&ctx, || "b1");
|
todo!()
|
||||||
|
// let (val1, set_val1) = use_state(&ctx, || "b1");
|
||||||
|
|
||||||
ctx.view(html! {
|
// ctx.view(|bump| {
|
||||||
<div>
|
// builder::button(bump)
|
||||||
<button onclick={move |_| set_val1("b1")}> "Set value to b1" </button>
|
// .on("click", move |c| {
|
||||||
<button onclick={move |_| set_val1("b2")}> "Set value to b2" </button>
|
// //
|
||||||
<button onclick={move |_| set_val1("b3")}> "Set value to b3" </button>
|
// println!("Value is {}", val1);
|
||||||
<div>
|
// })
|
||||||
<p> "Value is: {val1}" </p>
|
// .finish()
|
||||||
</div>
|
// })
|
||||||
</div>
|
// ctx.view(html! {
|
||||||
})
|
// <div>
|
||||||
|
// <button onclick={move |_| set_val1("b1")}> "Set value to b1" </button>
|
||||||
|
// <button onclick={move |_| set_val1("b2")}> "Set value to b2" </button>
|
||||||
|
// <button onclick={move |_| set_val1("b3")}> "Set value to b3" </button>
|
||||||
|
// <div>
|
||||||
|
// <p> "Value is: {val1}" </p>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// })
|
||||||
};
|
};
|
||||||
|
|
||||||
use use_state_def::use_state;
|
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>(
|
pub fn use_state<'b, 'a, T: 'static, F: FnOnce() -> T + 'static>(
|
||||||
ctx: &'b Context<'a, P>,
|
ctx: &'b Context<'a>,
|
||||||
initial_state_fn: F,
|
initial_state_fn: F,
|
||||||
) -> (&'a T, &'a impl Fn(T)) {
|
) -> (&'a T, &'a impl Fn(T)) {
|
||||||
ctx.use_hook(
|
ctx.use_hook(
|
||||||
|
@ -116,8 +125,8 @@ mod use_ref_def {
|
||||||
/// To read the value, borrow the ref.
|
/// To read the value, borrow the ref.
|
||||||
/// To change it, use modify.
|
/// To change it, use modify.
|
||||||
/// Modifications to this value do not cause updates to the component
|
/// Modifications to this value do not cause updates to the component
|
||||||
pub fn use_ref<'a, P, T: 'static>(
|
pub fn use_ref<'a, T: 'static>(
|
||||||
ctx: &'a Context<'a, P>,
|
ctx: &'a Context<'a>,
|
||||||
initial_state_fn: impl FnOnce() -> T + 'static,
|
initial_state_fn: impl FnOnce() -> T + 'static,
|
||||||
) -> &'a UseRef<T> {
|
) -> &'a UseRef<T> {
|
||||||
ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
|
ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl Properties for Props {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<Props> = |ctx| {
|
static Example: FC<Props> = |ctx, props| {
|
||||||
ctx.view(html! {
|
ctx.view(html! {
|
||||||
<div>
|
<div>
|
||||||
<h1> </h1>
|
<h1> </h1>
|
||||||
|
|
|
@ -43,20 +43,25 @@ mod tests {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_component(ctx: Context<()>) -> VNode {
|
static TestComponent: FC<()> = |ctx, props| {
|
||||||
ctx.view(html! {<div> </div> })
|
//
|
||||||
}
|
ctx.view(html! {
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
fn test_component2(ctx: Context<()>) -> VNode {
|
static TestComponent2: FC<()> = |ctx, props| {
|
||||||
|
//
|
||||||
ctx.view(|bump: &Bump| VNode::text("blah"))
|
ctx.view(|bump: &Bump| VNode::text("blah"))
|
||||||
}
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_types_work() {
|
fn ensure_types_work() {
|
||||||
let bump = Bump::new();
|
let bump = Bump::new();
|
||||||
|
|
||||||
// Happiness! The VNodes are now allocated onto the bump vdom
|
// Happiness! The VNodes are now allocated onto the bump vdom
|
||||||
let _ = test_static_fn(&bump, test_component);
|
let _ = test_static_fn(&bump, TestComponent);
|
||||||
let _ = test_static_fn(&bump, test_component2);
|
let _ = test_static_fn(&bump, TestComponent2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::scope::Hook;
|
|
||||||
use crate::{inner::Scope, nodes::VNode};
|
use crate::{inner::Scope, nodes::VNode};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use hooks::Hook;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
|
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
|
// todo: force lifetime of source into T as a valid lifetime too
|
||||||
// it's definitely possible, just needs some more messing around
|
// 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.
|
/// Direct access to the properties used to create this component.
|
||||||
pub props: PropType,
|
// pub props: &'src PropType,
|
||||||
pub idx: AtomicUsize,
|
pub idx: AtomicUsize,
|
||||||
|
|
||||||
// Borrowed from scope
|
// Borrowed from scope
|
||||||
|
@ -41,7 +42,8 @@ pub struct Context<'src, PropType> {
|
||||||
pub _p: std::marker::PhantomData<&'src ()>,
|
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
|
/// Access the children elements passed into the component
|
||||||
pub fn children(&self) -> Vec<VNode> {
|
pub fn children(&self) -> Vec<VNode> {
|
||||||
todo!("Children API not yet implemented for component Context")
|
todo!("Children API not yet implemented for component Context")
|
||||||
|
@ -73,10 +75,12 @@ impl<'a, PropType> Context<'a, PropType> {
|
||||||
/// ctx.view(lazy_tree)
|
/// 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!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn callback(&self, f: impl Fn(()) + 'static) {}
|
||||||
|
|
||||||
/// Create a suspended component from a future.
|
/// Create a suspended component from a future.
|
||||||
///
|
///
|
||||||
/// When the future completes, the component will be renderered
|
/// When the future completes, the component will be renderered
|
||||||
|
@ -86,7 +90,25 @@ impl<'a, PropType> Context<'a, PropType> {
|
||||||
) -> VNode<'a> {
|
) -> VNode<'a> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod hooks {
|
||||||
|
//! This module provides internal state management functionality for Dioxus components
|
||||||
|
//!
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct Hook(pub Box<dyn std::any::Any>);
|
||||||
|
|
||||||
|
impl Hook {
|
||||||
|
pub fn new(state: Box<dyn std::any::Any>) -> Self {
|
||||||
|
Self(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
/// 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
|
/// 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>(
|
pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>(
|
||||||
|
@ -144,4 +166,129 @@ impl<'a, PropType> Context<'a, PropType> {
|
||||||
|
|
||||||
runner(internal_state)
|
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<T> and RemoteLock<T> 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<T> {
|
||||||
|
inner: *const T,
|
||||||
|
}
|
||||||
|
impl<T> Copy for RemoteState<T> {}
|
||||||
|
|
||||||
|
impl<T> Clone for RemoteState<T> {
|
||||||
|
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<T> Deref for RemoteState<T> {
|
||||||
|
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<I, O>(&'a self, narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
|
||||||
|
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<T> 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() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<T> 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<C: AppContext>(&'a self) -> C {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn use_context_unchecked<C: AppContext>() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SafeContext<T> {
|
|
||||||
value: T,
|
|
||||||
|
|
||||||
// This context is pinned
|
|
||||||
_pinned: PhantomPinned,
|
|
||||||
}
|
|
|
@ -28,7 +28,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_creation() -> Result<(), ()> {
|
fn ensure_creation() -> Result<(), ()> {
|
||||||
let mut dom = VirtualDom::new(|ctx| ctx.view(html! { <div>"hello world" </div> }));
|
let mut dom = VirtualDom::new(|ctx, props| {
|
||||||
|
//
|
||||||
|
ctx.view(html! { <div>"hello world" </div> })
|
||||||
|
});
|
||||||
|
|
||||||
dom.progress()?;
|
dom.progress()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,8 @@
|
||||||
//! #[derive(Properties)]
|
//! #[derive(Properties)]
|
||||||
//! struct Props { name: String }
|
//! struct Props { name: String }
|
||||||
//!
|
//!
|
||||||
//! static Example: FC<Props> = |ctx| {
|
//! static Example: FC<Props> = |ctx, props| {
|
||||||
//! html! { <div> "Hello {ctx.props.name}!" </div> }
|
//! html! { <div> "Hello {props.name}!" </div> }
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
//! use dioxus_core::prelude::*;
|
//! use dioxus_core::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! #[functional_component]
|
//! #[fc]
|
||||||
//! static Example: FC = |ctx, name: String| {
|
//! static Example: FC = |ctx, name: String| {
|
||||||
//! html! { <div> "Hello {name}!" </div> }
|
//! html! { <div> "Hello {name}!" </div> }
|
||||||
//! }
|
//! }
|
||||||
|
@ -67,7 +67,6 @@
|
||||||
|
|
||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod contextapi;
|
|
||||||
pub mod debug_renderer;
|
pub mod debug_renderer;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod nodebuilder;
|
pub mod nodebuilder;
|
||||||
|
@ -83,15 +82,18 @@ pub mod builder {
|
||||||
// types used internally that are important
|
// types used internally that are important
|
||||||
pub(crate) mod inner {
|
pub(crate) mod inner {
|
||||||
pub use crate::component::{Component, Properties};
|
pub use crate::component::{Component, Properties};
|
||||||
|
use crate::context::hooks::Hook;
|
||||||
pub use crate::context::Context;
|
pub use crate::context::Context;
|
||||||
use crate::nodes;
|
use crate::nodes;
|
||||||
pub use crate::scope::{Hook, Scope};
|
pub use crate::scope::Scope;
|
||||||
pub use crate::virtual_dom::VirtualDom;
|
pub use crate::virtual_dom::VirtualDom;
|
||||||
pub use nodes::*;
|
pub use nodes::*;
|
||||||
|
|
||||||
// pub use nodes::iterables::IterableNodes;
|
// pub use nodes::iterables::IterableNodes;
|
||||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||||
pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
|
||||||
|
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> VNode<'a>;
|
||||||
|
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
||||||
|
|
||||||
// TODO @Jon, fix this
|
// TODO @Jon, fix this
|
||||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||||
|
@ -115,7 +117,7 @@ pub mod prelude {
|
||||||
|
|
||||||
// pub use nodes::iterables::IterableNodes;
|
// pub use nodes::iterables::IterableNodes;
|
||||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||||
pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
pub use crate::inner::FC;
|
||||||
|
|
||||||
// TODO @Jon, fix this
|
// TODO @Jon, fix this
|
||||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||||
|
|
|
@ -334,14 +334,15 @@ where
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
|
pub fn on(mut self, event: &'a str, callback: impl Fn(()) + 'a) -> Self
|
||||||
where
|
// pub fn on<F>(mut self, event: &'a str, callback: impl Fn(()) -> () + 'static) -> Self
|
||||||
F: Fn(()) + 'a,
|
// F: Fn(()) + 'static,
|
||||||
|
// F: Fn(()) + 'a,
|
||||||
{
|
{
|
||||||
self.listeners.push(Listener {
|
// self.listeners.push(Listener {
|
||||||
event,
|
// event,
|
||||||
callback: self.bump.alloc(callback),
|
// callback: self.bump.alloc(callback),
|
||||||
});
|
// });
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1068,10 +1069,12 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
|
||||||
/// // do something when a click happens...
|
/// // do something when a click happens...
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn on<'a, 'b, F: 'b>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
|
pub fn on<'a, 'b, F: 'static>(
|
||||||
where
|
bump: &'a Bump,
|
||||||
'b: 'a + 'static,
|
event: &'a str,
|
||||||
F: Fn(()) + 'b,
|
callback: impl Fn(()) + 'static,
|
||||||
|
) -> Listener<'a>
|
||||||
|
// F: Fn(()) + 'b,
|
||||||
{
|
{
|
||||||
Listener {
|
Listener {
|
||||||
event,
|
event,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
use crate::context::hooks::Hook;
|
||||||
|
use crate::inner::*;
|
||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
use crate::prelude::*;
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::Index;
|
use generational_arena::Index;
|
||||||
use std::{
|
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`.
|
/// Every component in Dioxus is represented by a `Scope`.
|
||||||
|
@ -48,13 +50,15 @@ impl Scope {
|
||||||
pub fn create_context<'runner, T: Properties>(
|
pub fn create_context<'runner, T: Properties>(
|
||||||
&'runner mut self,
|
&'runner mut self,
|
||||||
components: &'runner generational_arena::Arena<Scope>,
|
components: &'runner generational_arena::Arena<Scope>,
|
||||||
) -> Context<T> {
|
props: &'runner T,
|
||||||
|
) -> Context {
|
||||||
|
// ) -> Context<T> {
|
||||||
Context {
|
Context {
|
||||||
_p: PhantomData {},
|
_p: PhantomData {},
|
||||||
arena: &self.hook_arena,
|
arena: &self.hook_arena,
|
||||||
hooks: &self.hooks,
|
hooks: &self.hooks,
|
||||||
idx: 0.into(),
|
idx: 0.into(),
|
||||||
props: T::new(),
|
// props,
|
||||||
components,
|
components,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,32 +66,27 @@ impl Scope {
|
||||||
/// Create a new context and run the component with references from the Virtual Dom
|
/// 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
|
/// This function downcasts the function pointer based on the stored props_type
|
||||||
fn run<T: 'static>(&self, f: FC<T>) {}
|
fn run<T: 'static>(&self, f: FC<T>) {}
|
||||||
|
|
||||||
fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
|
|
||||||
if self.props_type == TypeId::of::<T>() {
|
|
||||||
/*
|
|
||||||
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<T>>(self.caller) };
|
|
||||||
// let ctx = self.create_context::<T>();
|
|
||||||
// // 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<dyn std::any::Any>);
|
mod bad_unsafety {
|
||||||
|
// todo
|
||||||
|
// fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
|
||||||
|
// if self.props_type == TypeId::of::<T>() {
|
||||||
|
// /*
|
||||||
|
// SAFETY ALERT
|
||||||
|
|
||||||
impl Hook {
|
// This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
|
||||||
pub fn new(state: Box<dyn std::any::Any>) -> Self {
|
// We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
||||||
Self(state)
|
// 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<T>>(self.caller) };
|
||||||
|
// // let ctx = self.create_context::<T>();
|
||||||
|
// // // 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")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,8 @@ impl<P: Properties + 'static> VirtualDom<P> {
|
||||||
pub async fn progess_completely() {}
|
pub async fn progess_completely() {}
|
||||||
|
|
||||||
/// Create a new context object for a given component and scope
|
/// Create a new context object for a given component and scope
|
||||||
fn new_context<T: Properties>(&self) -> Context<T> {
|
fn new_context<T: Properties>(&self) -> Context {
|
||||||
|
// fn new_context<T: Properties>(&self) -> Context<T> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue