Feat:
- integrate subscription service into context. - Update documentation
This commit is contained in:
1 parent
ee23ea6c3a
commit
204f0d9f16
13 files changed
+416
-358
No files matched your search
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
"rust-analyzer.inlayHints.enable": false
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#![allow(unused, non_upper_case_globals)]
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (val1, set_val1) = use_state(&ctx, || "b1");
|
||||
|
||||
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;
|
||||
mod use_state_def {
|
||||
use dioxus_core::prelude::*;
|
||||
use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
|
||||
|
||||
struct UseState<T: 'static> {
|
||||
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<'b, 'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>(
|
||||
ctx: &'b Context<'a, P>,
|
||||
initial_state_fn: F,
|
||||
) -> (&'a T, &'a 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| {
|
||||
let inner = hook.new_val.clone();
|
||||
let scheduled_update = ctx.schedule_update();
|
||||
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
|
||||
hook.current_val = new_val;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Ensure the component gets updated
|
||||
scheduled_update();
|
||||
});
|
||||
|
||||
// box gets derefed into a ref which is then taken as ref with the hook
|
||||
(&hook.current_val, &hook.caller)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod use_ref_def {
|
||||
use dioxus_core::prelude::*;
|
||||
use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
|
||||
|
||||
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 UseRef<T> {
|
||||
ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
|
||||
}
|
||||
}
|
||||
+118
-117
@@ -1,136 +1,137 @@
|
||||
#![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
use bumpalo::Bump;
|
||||
use dioxus_core::nodebuilder::*;
|
||||
use dioxus_core::prelude::*;
|
||||
use std::{collections::HashMap, future::Future, marker::PhantomData};
|
||||
// #![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
// use bumpalo::Bump;
|
||||
// use dioxus_core::nodebuilder::*;
|
||||
// use dioxus_core::prelude::*;
|
||||
// use std::{collections::HashMap, future::Future, marker::PhantomData};
|
||||
|
||||
fn main() {
|
||||
let mut vdom = VirtualDom::new_with_props(
|
||||
component,
|
||||
Props {
|
||||
blah: false,
|
||||
text: "blah".into(),
|
||||
},
|
||||
);
|
||||
fn main() {}
|
||||
// fn main() {
|
||||
// let mut vdom = VirtualDom::new_with_props(
|
||||
// component,
|
||||
// Props {
|
||||
// blah: false,
|
||||
// text: "blah".into(),
|
||||
// },
|
||||
// );
|
||||
|
||||
vdom.progress();
|
||||
// vdom.progress();
|
||||
|
||||
let somet = String::from("asd");
|
||||
let text = somet.as_str();
|
||||
// let somet = String::from("asd");
|
||||
// let text = somet.as_str();
|
||||
|
||||
/*
|
||||
this could be auto-generated via the macro
|
||||
this props is allocated in this
|
||||
but the component and props would like need to be cached
|
||||
we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
|
||||
How do we keep cached things around?
|
||||
Need some sort of caching mechanism
|
||||
// /*
|
||||
// this could be auto-generated via the macro
|
||||
// this props is allocated in this
|
||||
// but the component and props would like need to be cached
|
||||
// we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
|
||||
// How do we keep cached things around?
|
||||
// Need some sort of caching mechanism
|
||||
|
||||
how do we enter into a childscope from a parent scope?
|
||||
// how do we enter into a childscope from a parent scope?
|
||||
|
||||
Problems:
|
||||
1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
|
||||
2: Trees are not evaluated
|
||||
// Problems:
|
||||
// 1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
|
||||
// 2: Trees are not evaluated
|
||||
|
||||
*/
|
||||
let example_caller = move |ctx: &Bump| {
|
||||
todo!()
|
||||
// let p = Props { blah: true, text };
|
||||
// let c = Context { props: &p };
|
||||
// let r = component(&c);
|
||||
};
|
||||
// */
|
||||
// let example_caller = move |ctx: &Bump| {
|
||||
// todo!()
|
||||
// // let p = Props { blah: true, text };
|
||||
// // let c = Context { props: &p };
|
||||
// // let r = component(&c);
|
||||
// };
|
||||
|
||||
// check the edit list
|
||||
}
|
||||
// // check the edit list
|
||||
// }
|
||||
|
||||
// ~~~ Text shared between components via props can be done with lifetimes! ~~~
|
||||
// Super duper efficient :)
|
||||
struct Props {
|
||||
blah: bool,
|
||||
text: String,
|
||||
// text: &'src str,
|
||||
}
|
||||
impl Properties for Props {
|
||||
fn new() -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
// // ~~~ Text shared between components via props can be done with lifetimes! ~~~
|
||||
// // Super duper efficient :)
|
||||
// struct Props {
|
||||
// blah: bool,
|
||||
// text: String,
|
||||
// // text: &'src str,
|
||||
// }
|
||||
// impl Properties for Props {
|
||||
// fn new() -> Self {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
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> "success!" </div>},
|
||||
Err(_) => html! { <div> "failure :(" </div>},
|
||||
}
|
||||
});
|
||||
// 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> "success!" </div>},
|
||||
// Err(_) => html! { <div> "failure :(" </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
|
||||
// // 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>
|
||||
})
|
||||
}
|
||||
// // <p> { product_list } </p>
|
||||
// </div>
|
||||
// })
|
||||
// }
|
||||
|
||||
fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
|
||||
// VNodes can be constructed via a builder or the html! macro
|
||||
// However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// We can "view" them with Context for ultimate speed while inside components
|
||||
ctx.view(|bump| {
|
||||
div(bump)
|
||||
.attr("class", "edit")
|
||||
.child(text("Hello"))
|
||||
.child(text(ctx.props.text.as_ref()))
|
||||
.finish()
|
||||
})
|
||||
}
|
||||
// fn BuilderComp<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
|
||||
// // VNodes can be constructed via a builder or the html! macro
|
||||
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// // We can "view" them with Context for ultimate speed while inside components
|
||||
// ctx.view(|bump| {
|
||||
// div(bump)
|
||||
// .attr("class", "edit")
|
||||
// .child(text("Hello"))
|
||||
// .child(text(ctx.props.text.as_ref()))
|
||||
// .finish()
|
||||
// })
|
||||
// }
|
||||
|
||||
#[fc]
|
||||
fn EffcComp(ctx: &Context, name: &str) -> VNode {
|
||||
// VNodes can be constructed via a builder or the html! macro
|
||||
// However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// We can "view" them with Context for ultimate speed while inside components
|
||||
// use "phase" style allocation;
|
||||
// #[fc]
|
||||
// fn EffcComp(ctx: Context, name: &str) -> VNode {
|
||||
// // VNodes can be constructed via a builder or the html! macro
|
||||
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// // We can "view" them with Context for ultimate speed while inside components
|
||||
// // use "phase" style allocation;
|
||||
|
||||
ctx.view(html! {
|
||||
<div>
|
||||
// your template goes here
|
||||
// feel free to directly use "name"
|
||||
</div>
|
||||
})
|
||||
}
|
||||
// ctx.view(html! {
|
||||
// <div>
|
||||
// // your template goes here
|
||||
// // feel free to directly use "name"
|
||||
// </div>
|
||||
// })
|
||||
// }
|
||||
|
||||
fn FullySuspended<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
|
||||
ctx.suspend(async {
|
||||
let i: i32 = 0;
|
||||
// fn FullySuspended<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
|
||||
// ctx.suspend(async {
|
||||
// let i: i32 = 0;
|
||||
|
||||
// full suspended works great with just returning VNodes!
|
||||
// Feel free to capture the html! macro directly
|
||||
// Anything returned here is automatically viewed
|
||||
let tex = match i {
|
||||
1 => html! { <div> </div> },
|
||||
2 => html! { <div> </div> },
|
||||
_ => html! { <div> </div> },
|
||||
};
|
||||
// // full suspended works great with just returning VNodes!
|
||||
// // Feel free to capture the html! macro directly
|
||||
// // Anything returned here is automatically viewed
|
||||
// let tex = match i {
|
||||
// 1 => html! { <div> </div> },
|
||||
// 2 => html! { <div> </div> },
|
||||
// _ => html! { <div> </div> },
|
||||
// };
|
||||
|
||||
if ctx.props.blah {
|
||||
html! { <div> </div> }
|
||||
} else {
|
||||
tex
|
||||
}
|
||||
})
|
||||
}
|
||||
// if ctx.props.blah {
|
||||
// html! { <div> </div> }
|
||||
// } else {
|
||||
// tex
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
/// An example of a datafetching service
|
||||
async fn fetch_data() -> Result<String, ()> {
|
||||
todo!()
|
||||
}
|
||||
// /// An example of a datafetching service
|
||||
// async fn fetch_data() -> Result<String, ()> {
|
||||
// todo!()
|
||||
// }
|
||||
@@ -1,185 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::{
|
||||
prelude::{html, Context, Properties, VElement, VNode, FC},
|
||||
scope::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 mut scope = Scope::new(component);
|
||||
|
||||
(0..5).for_each(|f| {
|
||||
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<()>) -> VNode {
|
||||
(0..10).for_each(|f| {
|
||||
let r = use_ref(&ctx, move || 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 UseRef<T> {
|
||||
ctx.use_hook(
|
||||
|| 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::text("blah")
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused, non_upper_case_globals)]
|
||||
static UseStateBorrowedFc: FC<()> = |ctx| {
|
||||
let (val, set_val) = use_state(&ctx, || 0);
|
||||
|
||||
let incr = || set_val(val + 1);
|
||||
let decr = || set_val(val - 1);
|
||||
|
||||
ctx.view(html! {
|
||||
<div>
|
||||
<nav class="menu">
|
||||
<button> "Increment" </button>
|
||||
<button> "Decrement" </button>
|
||||
</nav>
|
||||
<p> "Current value: {val}" </p>
|
||||
</div>
|
||||
})
|
||||
};
|
||||
|
||||
/// A useful component
|
||||
/// With some great documentation
|
||||
/// it practically speaks for itself.
|
||||
/// It's also only a server component :o
|
||||
fn use_state_borrowed(ctx: Context<()>) -> VNode {
|
||||
let (val, set_val) = use_state(&ctx, || 0);
|
||||
|
||||
let incr = || set_val(val + 1);
|
||||
let decr = || set_val(val - 1);
|
||||
|
||||
ctx.view(html! {
|
||||
<div>
|
||||
<nav class="menu">
|
||||
<button> "Increment" </button>
|
||||
<button> "Decrement" </button>
|
||||
</nav>
|
||||
<p> "Current value: {val}" </p>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
|
||||
// <button onclick=incr> { "Increment" } </button>
|
||||
// <button onclick=decr> { "Decrement" } </button>
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::nodes::VNode;
|
||||
use crate::prelude::*;
|
||||
use crate::scope::Hook;
|
||||
use crate::{inner::Scope, nodes::VNode};
|
||||
use bumpalo::Bump;
|
||||
use std::{
|
||||
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
|
||||
@@ -26,21 +26,22 @@ use std::{
|
||||
/// ```
|
||||
// todo: force lifetime of source into T as a valid lifetime too
|
||||
// it's definitely possible, just needs some more messing around
|
||||
pub struct Context<'src, T> {
|
||||
pub struct Context<'src, PropType> {
|
||||
/// Direct access to the properties used to create this component.
|
||||
pub props: T,
|
||||
pub props: PropType,
|
||||
pub idx: AtomicUsize,
|
||||
|
||||
// Borrowed from scope
|
||||
pub(crate) arena: &'src typed_arena::Arena<Hook>,
|
||||
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
pub(crate) components: &'src generational_arena::Arena<Scope>,
|
||||
|
||||
// holder for the src lifetime
|
||||
// todo @jon remove this
|
||||
pub _p: std::marker::PhantomData<&'src ()>,
|
||||
}
|
||||
|
||||
impl<'a, T> Context<'a, T> {
|
||||
impl<'a, PropType> Context<'a, PropType> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
@@ -52,13 +53,17 @@ impl<'a, T> Context<'a, T> {
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn subscribe(&self) -> impl FnOnce() -> () {
|
||||
pub fn schedule_update(&self) -> impl Fn() -> () {
|
||||
todo!("Subscription API is not ready yet");
|
||||
|| {}
|
||||
}
|
||||
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context<Props>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
@@ -68,7 +73,7 @@ impl<'a, T> Context<'a, T> {
|
||||
/// 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>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -83,12 +88,13 @@ 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: 'comp>(
|
||||
&'comp self,
|
||||
/// 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>(
|
||||
&'scope self,
|
||||
// The closure that builds the hook state
|
||||
initializer: impl FnOnce() -> InternalHookState,
|
||||
// The closure that takes the hookstate and returns some value
|
||||
runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
|
||||
runner: impl FnOnce(&'internal 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
|
||||
cleanup: impl FnOnce(InternalHookState),
|
||||
@@ -132,13 +138,10 @@ impl<'a, T> Context<'a, T> {
|
||||
- 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() };
|
||||
let borrowed_hook: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||
|
||||
let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
|
||||
|
||||
// todo: set up an updater with the subscription API
|
||||
let updater = ();
|
||||
|
||||
runner(internal_state, updater)
|
||||
runner(internal_state)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//! 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,
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
|
||||
pub mod component;
|
||||
pub mod context;
|
||||
pub mod contextapi;
|
||||
pub mod debug_renderer;
|
||||
pub mod events;
|
||||
pub mod nodebuilder;
|
||||
|
||||
@@ -336,7 +336,7 @@ where
|
||||
#[inline]
|
||||
pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(),
|
||||
F: Fn(()) + 'a,
|
||||
{
|
||||
self.listeners.push(Listener {
|
||||
event,
|
||||
@@ -1052,28 +1052,28 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
|
||||
Attribute { name, value }
|
||||
}
|
||||
|
||||
// /// Create an event listener.
|
||||
// ///
|
||||
// /// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
|
||||
// /// is the function that will be invoked when the event occurs.
|
||||
// ///
|
||||
// /// # Example
|
||||
// ///
|
||||
// /// ```no_run
|
||||
// /// use dodrio::{builder::*, bumpalo::Bump};
|
||||
// ///
|
||||
// /// let b = Bump::new();
|
||||
// ///
|
||||
// /// let listener = on(&b, "click", |root, vdom, event| {
|
||||
// /// // do something when a click happens...
|
||||
// /// });
|
||||
// /// ```
|
||||
// pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
|
||||
// where
|
||||
// F: Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static,
|
||||
// {
|
||||
// Listener {
|
||||
// event,
|
||||
// callback: bump.alloc(callback),
|
||||
// }
|
||||
// }
|
||||
/// Create an event listener.
|
||||
///
|
||||
/// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
|
||||
/// is the function that will be invoked when the event occurs.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dodrio::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let listener = on(&b, "click", |root, vdom, event| {
|
||||
/// // do something when a click happens...
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
|
||||
where
|
||||
F: Fn(()) + 'a,
|
||||
{
|
||||
Listener {
|
||||
event,
|
||||
callback: bump.alloc(callback),
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ mod velement {
|
||||
/// The type of event to listen for.
|
||||
pub(crate) event: &'a str,
|
||||
/// The callback to invoke when the event happens.
|
||||
pub(crate) callback: &'a (dyn Fn()),
|
||||
pub(crate) callback: &'a (dyn Fn(())),
|
||||
}
|
||||
|
||||
/// The key for keyed children.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::nodes::VNode;
|
||||
use crate::prelude::*;
|
||||
use bumpalo::Bump;
|
||||
use generational_arena::Index;
|
||||
use std::{
|
||||
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
|
||||
};
|
||||
@@ -18,35 +19,43 @@ pub struct Scope {
|
||||
hooks: RefCell<Vec<*mut Hook>>,
|
||||
hook_arena: typed_arena::Arena<Hook>,
|
||||
|
||||
// Map to the parent
|
||||
parent: Option<Index>,
|
||||
|
||||
props_type: TypeId,
|
||||
caller: *const i32,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
// create a new scope from a function
|
||||
pub fn new<T: 'static>(f: FC<T>) -> Self {
|
||||
pub fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
|
||||
// Capture the props type
|
||||
let props_type = TypeId::of::<T>();
|
||||
let arena = typed_arena::Arena::new();
|
||||
let hook_arena = typed_arena::Arena::new();
|
||||
let hooks = RefCell::new(Vec::new());
|
||||
|
||||
let caller = f as *const i32;
|
||||
|
||||
Self {
|
||||
hook_arena: arena,
|
||||
hook_arena,
|
||||
hooks,
|
||||
props_type,
|
||||
caller,
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_context<T: Properties>(&mut self) -> Context<T> {
|
||||
pub fn create_context<'runner, T: Properties>(
|
||||
&'runner mut self,
|
||||
components: &'runner generational_arena::Arena<Scope>,
|
||||
) -> Context<T> {
|
||||
Context {
|
||||
_p: PhantomData {},
|
||||
arena: &self.hook_arena,
|
||||
hooks: &self.hooks,
|
||||
idx: 0.into(),
|
||||
props: T::new(),
|
||||
components,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +63,7 @@ impl Scope {
|
||||
/// This function downcasts the function pointer based on the stored props_type
|
||||
fn run<T: 'static>(&self, f: FC<T>) {}
|
||||
|
||||
fn call<T: Properties + 'static>(&mut self, val: T) {
|
||||
fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
|
||||
if self.props_type == TypeId::of::<T>() {
|
||||
/*
|
||||
SAFETY ALERT
|
||||
@@ -66,9 +75,9 @@ impl Scope {
|
||||
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);
|
||||
// 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")
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
|
||||
let mut components = Arena::new();
|
||||
|
||||
// Create a reference to the component in the arena
|
||||
let base_scope = components.insert(Scope::new(root));
|
||||
let base_scope = components.insert(Scope::new(root, None));
|
||||
|
||||
// Create a new mount event with no root container
|
||||
let first_event = LifecycleEvent::mount(base_scope, None, 0);
|
||||
|
||||
Reference in new issue
Block a user