wip: recoil

This commit is contained in:
Jonathan Kelley 2021-05-26 01:40:30 -04:00
parent 3cfa1fe125
commit ee67654f58
31 changed files with 1369 additions and 235 deletions

View file

@ -35,11 +35,3 @@ split-debuginfo = "unpacked"
# "packages/cli",
# "examples",
# "packages/html-macro",
# "packages/html-macro-2",
#
#
#
# Pulled from percy
# "packages/html-macro-test",
# "packages/virtual-dom-rs",
# "packages/virtual-node",

View file

@ -16,7 +16,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
- [x] (Core) Implement lifecycle
- [x] (Core) Implement an event system
- [x] (Core) Implement child nodes, scope creation
- [ ] (Core) Implement dirty tagging and compression
- [x] (Core) Implement dirty tagging and compression
## Project: QOL
> Make it easier to write components

View file

@ -1,7 +1,7 @@
#![allow(unused)]
//! Example of components in
use std::borrow::Borrow;
use std::{borrow::Borrow, marker::PhantomData};
use dioxus_core::prelude::*;
@ -10,14 +10,13 @@ fn main() {}
static Header: FC<()> = |ctx, props| {
let inner = use_ref(&ctx, || 0);
let handler1 = move || println!("Value is {}", inner.current());
let handler1 = move || println!("Value is {}", inner.borrow());
ctx.render(dioxus::prelude::LazyNodes::new(|c| {
builder::ElementBuilder::new(c, "div")
ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| {
builder::ElementBuilder::new(nodectx, "div")
.child(VNode::Component(VComponent::new(
Bottom,
//
c.bump().alloc(()),
nodectx.bump().alloc(()),
None,
)))
.finish()
@ -32,3 +31,137 @@ static Bottom: FC<()> = |ctx, props| {
</div>
})
};
fn Top(ctx: Context, a: &str, b: &i32, c: &impl Fn()) -> DomTree {
ctx.render(html! {
<div>
<h1> "bruh 1" </h1>
<h1> "bruh 2" </h1>
</div>
})
}
struct Callback<T>(Box<T>);
// impl<O, T: Fn() -> O> From<T> for Callback<T> {
// fn from(_: T) -> Self {
// todo!()
// }
// }
impl<O, A> From<&dyn Fn(A) -> O> for Callback<&dyn Fn(A) -> O> {
fn from(_: &dyn Fn(A) -> O) -> Self {
todo!()
}
}
impl<O, A, B> From<&dyn Fn(A, B) -> O> for Callback<&dyn Fn(A, B) -> O> {
fn from(_: &dyn Fn(A, B) -> O) -> Self {
todo!()
}
}
// compile time reordering of arguments
// Allows for transparently calling
#[derive(Default)]
pub struct Args<A, B, C> {
pub a: CuOpt<A>,
pub b: CuOpt<B>,
pub c: CuOpt<C>,
}
pub enum CuOpt<T> {
Some(T),
None,
}
impl<T> Default for CuOpt<T> {
fn default() -> Self {
CuOpt::None
}
}
impl<T> CuOpt<T> {
fn unwrap(self) -> T {
match self {
CuOpt::Some(t) => t,
CuOpt::None => panic!(""),
}
}
}
trait IsMemo {
fn memo(&self, other: &Self) -> bool {
false
}
}
impl<T: PartialEq> IsMemo for CuOpt<T> {
fn memo(&self, other: &Self) -> bool {
self == other
}
}
impl<T: PartialEq> PartialEq for CuOpt<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(CuOpt::Some(a), CuOpt::Some(b)) => a == b,
(CuOpt::Some(_), CuOpt::None) => false,
(CuOpt::None, CuOpt::Some(_)) => false,
(CuOpt::None, CuOpt::None) => true,
}
}
}
impl<T> IsMemo for &CuOpt<T> {
fn memo(&self, other: &Self) -> bool {
false
}
}
// #[test]
#[test]
fn try_test() {
// test_poc()
}
fn test_poc(ctx: Context) {
let b = Bump::new();
let h = Args {
a: CuOpt::Some("ASD"),
b: CuOpt::Some(123),
c: CuOpt::Some(|| {}),
// c: CuOpt::Some(b.alloc(|| {})),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
};
let h2 = Args {
a: CuOpt::Some("ASD"),
b: CuOpt::Some(123),
c: CuOpt::Some(|| {}),
// c: CuOpt::Some(b.alloc(|| {})),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
};
// dbg!((&h.a).memo((&&h2.a)));
// dbg!((&h.b).memo((&&h2.b)));
// dbg!((&h.c).memo((&&h2.c)));
//
// ctx: Context
Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap());
}
fn test_realzies() {
let h = Args {
a: CuOpt::Some("ASD"),
b: CuOpt::Some(123),
c: CuOpt::Some(|| {}),
};
let g = |ctx: Context| {
//
Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
};
}

View file

@ -32,3 +32,109 @@ impl Properties for () {
pub fn fc_to_builder<T: Properties>(_f: FC<T>) -> T::Builder {
T::builder()
}
mod testing {
use std::any::Any;
// trait PossibleProps {
// type POut: PartialEq;
// fn as_partial_eq(&self) -> Option<&Self::POut> {
// None
// }
// }
// impl<T: PartialEq> PossibleProps for T {
// type POut = Self;
// }
// struct SomeProps2<'a> {
// inner: &'a str,
// }
// Fallback trait for to all types to default to `false`.
trait NotEq {
const IS_EQ: bool = false;
}
impl<T> NotEq for T {}
// Concrete wrapper type where `IS_COPY` becomes `true` if `T: Copy`.
struct IsEq<G, T>(std::marker::PhantomData<(G, T)>);
impl<G: PartialEq, T: PartialEq<G>> IsEq<G, T> {
// Because this is implemented directly on `IsCopy`, it has priority over
// the `NotCopy` trait impl.
//
// Note: this is a *totally different* associated constant from that in
// `NotCopy`. This does not specialize the `NotCopy` trait impl on `IsCopy`.
const IS_EQ: bool = true;
}
#[derive(PartialEq)]
struct SomeProps {
inner: &'static str,
}
struct SomeProps2 {
inner: &'static str,
}
#[test]
fn test() {
let g = IsEq::<SomeProps, SomeProps>::IS_EQ;
// let g = IsEq::<Vec<u32>>::IS_COPY;
// let g = IsEq::<u32>::IS_COPY;
// dbg!(g);
// let props = SomeProps { inner: "asd" };
// let intermediate: Box<dyn PartialEq<SomeProps>> = Box::new(props);
// let as_any: Box<dyn Any> = Box::new(intermediate);
// let as_partialeq = as_any
// .downcast_ref::<Box<dyn PartialEq<SomeProps>>>()
// .unwrap();
}
// struct blah {}
// #[reorder_args]
pub fn blah(a: i32, b: &str, c: &str) {}
// pub mod blah {
// pub const a: u8 = 0;
// pub const b: u8 = 1;
// }
trait Eat {}
impl Eat for fn() {}
impl<T> Eat for fn(T) {}
impl<T, K> Eat for fn(T, K) {}
mod other {
use super::blah;
fn test2() {
// rsx!{
// div {
// Ele {
// a: 10,
// b: "asd"
// c: impl Fn() -> ()
// }
// }
// }
// becomes
// const reorder: fn(_, _) = |a, b| {};
// blah::META;
// let a = 10;
// let b = "asd";
// let g = [10, 10.0];
// let c = g.a;
// blah(10, "asd");
}
}
}
mod style {}

View file

@ -10,6 +10,8 @@ pub use use_reducer_def::use_reducer;
pub use use_ref_def::use_ref;
pub use use_state_def::use_state;
use crate::innerlude::Context;
mod use_state_def {
use crate::innerlude::*;
use std::{
@ -213,26 +215,31 @@ mod use_ref_def {
use crate::innerlude::*;
use std::{cell::RefCell, ops::DerefMut};
pub struct UseRef<T: 'static> {
_current: RefCell<T>,
}
impl<T: 'static> UseRef<T> {
fn new(val: T) -> Self {
Self {
_current: RefCell::new(val),
}
}
// pub struct UseRef<T: 'static> {
// _current: RefCell<T>,
// }
// impl<T: 'static> UseRef<T> {
// fn new(val: T) -> Self {
// Self {
// _current: RefCell::new(val),
// }
// }
pub 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);
}
// pub fn set(&self, new: T) {
// let mut val = self._current.borrow_mut();
// *val = new;
// }
pub fn current(&self) -> std::cell::Ref<'_, T> {
self._current.borrow()
}
}
// pub 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);
// }
// pub fn current(&self) -> std::cell::Ref<'_, T> {
// self._current.borrow()
// }
// }
/// Store a mutable value between renders!
/// To read the value, borrow the ref.
@ -240,10 +247,10 @@ mod use_ref_def {
/// Modifications to this value do not cause updates to the component
/// Attach to inner context reference, so context can be consumed
pub fn use_ref<'a, T: 'static>(
ctx: &'_ Context<'a>,
ctx: Context<'a>,
initial_state_fn: impl FnOnce() -> T + 'static,
) -> &'a UseRef<T> {
ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
) -> &'a RefCell<T> {
ctx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
}
}
@ -339,3 +346,15 @@ mod use_reducer_def {
// };
}
}
pub fn use_is_initialized(ctx: Context) -> bool {
let val = use_ref(ctx, || false);
match *val.borrow() {
true => true,
false => {
//
*val.borrow_mut() = true;
false
}
}
}

View file

@ -142,5 +142,15 @@ pub mod prelude {
pub use crate::diff::DiffMachine;
pub use crate::debug_renderer::DebugRenderer;
pub use crate::dioxus_main;
pub use crate::hooks::*;
}
#[macro_export]
macro_rules! dioxus_main {
($i:ident) => {
fn main() {
todo!("this macro is a placeholder for launching a dioxus app on different platforms. \nYou probably don't want to use this, but it's okay for small apps.")
}
};
}

View file

@ -0,0 +1,83 @@
//! Dedicated styling system for Components
//! ---------------------------------------
//!
//!
//!
//!
//!
//!
//!
enum Styles {
background,
backgroundAttachment,
backgroundColor,
backgroundImage,
backgroundPosition,
backgroundRepeat,
border,
borderBottom,
borderBottomColor,
borderBottomStyle,
borderBottomWidth,
borderColor,
borderLeft,
borderLeftColor,
borderLeftStyle,
borderLeftWidth,
borderRight,
borderRightColor,
borderRightStyle,
borderRightWidth,
borderStyle,
borderTop,
borderTopColor,
borderTopStyle,
borderTopWidth,
borderWidth,
clear,
clip,
color,
cursor,
display,
filter,
cssFloat,
font,
fontFamily,
fontSize,
fontVariant,
fontWeight,
height,
left,
letterSpacing,
lineHeight,
listStyle,
listStyleImage,
listStylePosition,
listStyleType,
margin,
marginBottom,
marginLeft,
marginRight,
marginTop,
overflow,
padding,
paddingBottom,
paddingLeft,
paddingRight,
paddingTop,
pageBreakAfter,
pageBreakBefore,
position,
strokeDasharray,
strokeDashoffset,
textAlign,
textDecoration,
textIndent,
textTransform,
top,
verticalAlign,
visibility,
width,
zIndex,
}

View file

@ -24,7 +24,6 @@ use bumpalo::Bump;
use generational_arena::Arena;
use std::{
any::{Any, TypeId},
borrow::{Borrow, BorrowMut},
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
fmt::Debug,
@ -130,7 +129,7 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = ScopeArena::new(Arena::new());
let components = ScopeArena::new(Arena::new());
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
@ -522,7 +521,7 @@ impl Scope {
// Therefore, their lifetimes are connected exclusively to the virtual dom
fn new<'creator_node>(
caller: Weak<OpaqueComponent<'creator_node>>,
myidx: ScopeIdx,
arena_idx: ScopeIdx,
parent: Option<ScopeIdx>,
height: u32,
event_queue: EventQueue,
@ -531,7 +530,7 @@ impl Scope {
log::debug!(
"New scope created, height is {}, idx is {:?}",
height,
myidx
arena_idx
);
// The function to run this scope is actually located in the parent's bump arena.
@ -544,7 +543,7 @@ impl Scope {
// this is a bit of a hack, but will remain this way until we've figured out a cleaner solution.
//
// Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
let broken_caller = unsafe {
let caller = unsafe {
std::mem::transmute::<
Weak<OpaqueComponent<'creator_node>>,
Weak<OpaqueComponent<'static>>,
@ -552,18 +551,18 @@ impl Scope {
};
Self {
shared_contexts: Default::default(),
caller: broken_caller,
hooks: RefCell::new(Vec::new()),
frames: ActiveFrame::new(),
listeners: Default::default(),
hookidx: Default::default(),
children: Default::default(),
caller,
parent,
arena_idx: myidx,
arena_idx,
height,
event_queue,
arena_link,
frames: ActiveFrame::new(),
hooks: Default::default(),
shared_contexts: Default::default(),
listeners: Default::default(),
hookidx: Default::default(),
children: Default::default(),
}
}
@ -594,8 +593,8 @@ impl Scope {
.upgrade()
.ok_or(Error::FatalInternal("Failed to get caller"))?;
// Cast the caller ptr from static to one with our own reference
let new_head = unsafe {
// Cast the caller ptr from static to one with our own reference
std::mem::transmute::<&OpaqueComponent<'static>, &OpaqueComponent<'sel>>(
caller.as_ref(),
)
@ -621,7 +620,7 @@ impl Scope {
.listeners
.try_borrow()
.ok()
.ok_or(Error::FatalInternal("Borrowing listener failed "))?
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
.get(listener_id as usize)
.ok_or(Error::FatalInternal("Event should exist if triggered"))?
.as_ref()
@ -797,10 +796,11 @@ impl Scope {
let internal_state = pinned_state.downcast_mut::<InternalHookState>().expect(
r###"
Unable to retrive the hook that was initialized in this index.
Consult the `rules of hooks` to understand how to use hooks properly.
Unable to retrive the hook that was initialized in this index.
Consult the `rules of hooks` to understand how to use hooks properly.
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Any function prefixed with "use" should not be called conditionally.
"###,
);
@ -813,7 +813,19 @@ impl Scope {
// Context API Implementation for Components
// ================================================
impl Scope {
pub fn create_context<T: 'static>(&self, init: impl Fn() -> T) {
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
///
/// This is a hook, so it may not be called conditionally!
///
/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
/// so don't put it in a conditional.
///
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
/// the context via Rc/Weak.
///
///
///
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
let mut ctxs = self.shared_contexts.borrow_mut();
let ty = TypeId::of::<T>();
@ -837,27 +849,56 @@ impl Scope {
ctxs.insert(ty, Rc::new(init()));
}
(false, true) => panic!("Cannot initialize two contexts of the same type"),
(true, false) => panic!("Implementation failure resulted in missing context"),
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
}
}
/// There are hooks going on here!
pub fn use_context<T: 'static>(&self) -> Rc<T> {
self.try_use_context().unwrap()
}
///
pub fn try_use_context<T: 'static>(&self) -> Result<Rc<T>> {
let ty = TypeId::of::<T>();
let mut scope = Some(self);
let cached_root = use_ref(self, || None as Option<Weak<T>>);
// Try to provide the cached version without having to re-climb the tree
if let Some(ptr) = cached_root.borrow().as_ref() {
if let Some(pt) = ptr.clone().upgrade() {
return Ok(pt);
} else {
/*
failed to upgrade the weak is strange
this means the root dropped the context (the scope was killed)
The main idea here is to prevent memory leaks where parents should be cleaning up their own memory.
However, this behavior allows receivers/providers to move around in the hierarchy.
This works because we climb the tree if upgrading the Rc failed.
*/
}
}
while let Some(inner) = scope {
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
let shared_contexts = inner.shared_contexts.borrow();
if let Some(shared_ctx) = shared_contexts.get(&ty) {
return Ok(shared_ctx.clone().downcast().unwrap());
let rc = shared_ctx
.clone()
.downcast()
.expect("Should not fail, already validated the type from the hashmap");
*cached_root.borrow_mut() = Some(Rc::downgrade(&rc));
return Ok(rc);
} else {
match inner.parent {
Some(parid) => {
Some(parent_id) => {
let parent = inner
.arena_link
.try_get(parid)
.try_get(parent_id)
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
scope = Some(parent);
@ -869,10 +910,6 @@ impl Scope {
Err(Error::MissingSharedContext)
}
pub fn use_context<T: 'static>(&self) -> Rc<T> {
self.try_use_context().unwrap()
}
}
// ==================================================================================

View file

@ -1,7 +1,50 @@
# Dioxus
This crate serves as the thin frontend over dioxus-core and dioxus-app. Here, we support docs, feature flags, and incorporate some extra tools and utilities into a consistent API.
This crate provides all the batteries required to build Dioxus apps.
Ideally, we can prevent cfg creep into the core crate by moving it all into this frontend crate.
Included in this crate is:
- Dioxus core
- Essential hooks (use_state, use_ref, use_reducer, etc)
- rsx! and html! macros
For now, see dioxus-core for all docs and details
You'll still need to pull in a renderer to render the Dioxus VDOM. Any one of:
- dioxus-web (to run in WASM)
- dioxus-ssr (to run on the server or for static sites)
- dioxus-webview (to run on the desktop)
- dioxus-mobile (to run on iOS/Android)
Make sure dioxus and its renderer share the same major version; the renderers themselves rely on dioxus.
```toml
[dependencies]
dioxus = "0.2"
dioxus-web = "0.2"
```
```rust
use dioxus::*;
fn main() {
dioxus_web::start(|ctx, _| {
rsx!{in ctx, div { "Hello world" }}
})
}
```
Additionally, you'll want to look at other projects for more batteries
- essential-hooks (use_router, use_storage, use_cache, use_channel)
- Recoil.rs or Reducer.rs for state management
- 3D renderer (ThreeD), charts (Sciviz), game engine (Bevy)
Extra resources:
- The guide is available at:
- The crate docs are at:
- Video tutorials are at:
- Examples are at:
- Full clones are at:
- Community at: [www.reddit.com/r/dioxus]()
Happy building!

3
packages/recoil/.vscode/settings.json vendored Normal file
View file

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

View file

@ -9,3 +9,7 @@ edition = "2018"
[dependencies]
dioxus-core = { path = "../core" }
uuid = "0.8.2"
[dev-dependencies]
dioxus-web = { path = "../web" }
wasm-bindgen-futures = "*"

View file

@ -1,6 +1,6 @@
# Recoil.rs - Official global state management solution for Dioxus Apps
Recoil.rs provides a global state management API for Dioxus apps built on the concept of "atomic state." Instead of grouping state together into a single bundle ALA Redux, Recoil provides individual building blocks of state called Atoms. These atoms can be set/get anywhere in the app and combined to craft complex state. Recoil should be easier to learn and more efficient than Redux. Recoil.rs is modeled after the Recoil.JS project and pulls in
Recoil.rs provides a global state management API for Dioxus apps built on the concept of "atomic state." Instead of grouping state together into a single bundle ALA Redux, Recoil provides individual building blocks of state called Atoms. These atoms can be set/get anywhere in the app and combined to craft complex state. Recoil should be easier to learn and more efficient than Redux. Recoil.rs is modeled after the Recoil.JS project.
Recoil.rs is officially supported by the Dioxus team. By doing so, are are "planting our flag in the stand" for atomic state management instead of bundled (Redux-style) state management. Atomic state management fits well with the internals of Dioxus, meaning Recoil.rs state management will be faster, more efficient, and less sensitive to data races than Redux-style apps.
@ -8,10 +8,10 @@ Internally, Dioxus uses batching to speed up linear-style operations. Recoil.rs
## Guide
A simple atom of state is defined globally as a static:
A simple atom of state is defined globally as a const:
```rust
const Light: Atom<&'static str> = atom(|_| "Green");
const Light: Atom<&'static str> = |_| "Green";
```
This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App.
@ -20,7 +20,7 @@ This is then later used in components like so:
```rust
fn App(ctx: Context, props: &()) -> DomTree {
// The recoil root must be initialized at the top of the application before any use_recoils
// The recoil root must be initialized at the top of the application before any use_recoil hooks
recoil::init_recoil_root(&ctx, |_| {});
let color = use_recoil(&ctx, &TITLE);

View file

@ -0,0 +1,45 @@
//! RecoilAPI Pattern
//! ----------------
//! This example demonstrates how the use_recoil_callback hook can be used to build view controllers.
//! These view controllers are cheap to make and can be easily shared across the app to provide global
//! state logic to many components.
//!
//! This pattern is meant to replace the typical use_dispatch pattern used in Redux apps.
use dioxus_core::prelude::*;
use recoil::*;
const TITLE: Atom<String> = |_| format!("Heading");
const SUBTITLE: Atom<String> = |_| format!("Subheading");
struct TitleController(RecoilApi);
impl TitleController {
fn new(api: RecoilApi) -> Self {
Self(api)
}
fn uppercase(&self) {
self.0.modify(&TITLE, |f| *f = f.to_uppercase());
self.0.modify(&SUBTITLE, |f| *f = f.to_uppercase());
}
fn lowercase(&self) {
self.0.modify(&TITLE, |f| *f = f.to_lowercase());
self.0.modify(&SUBTITLE, |f| *f = f.to_lowercase());
}
}
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx, _| {
let title = use_recoil_value(ctx, &TITLE);
let subtitle = use_recoil_value(ctx, &SUBTITLE);
let controller = use_recoil_callback(ctx, TitleController::new);
rsx! { in ctx,
div {
"{title}"
"{subtitle}"
button { onclick: move |_| controller.uppercase(), "Uppercase" }
button { onclick: move |_| controller.lowercase(), "Lowercase" }
}
}
}))
}

View file

@ -0,0 +1,36 @@
//! Example: RecoilCallback
//! ------------------------
//! This example shows how use_recoil_callback can be used to abstract over sets/gets.
//! This hook provides a way to capture the RecoilApi object. In this case, we capture
//! it in a closure an abstract the set/get functionality behind the update_title function.
//!
//! It should be straightforward to build a complex app with recoil_callback.
use dioxus_core::prelude::*;
use recoil::*;
const TITLE: Atom<&str> = |_| "red";
fn update_title(api: &RecoilApi) {
match *api.get(&TITLE) {
"green" => api.set(&TITLE, "yellow"),
"yellow" => api.set(&TITLE, "red"),
"red" => api.set(&TITLE, "green"),
_ => {}
}
}
static App: FC<()> = |ctx, _| {
let title = use_recoil_value(ctx, &TITLE);
let next_light = use_recoil_callback(ctx, |api| move |_| update_title(&api));
rsx! { in ctx,
div {
"{title}"
button { onclick: {next_light}, "Next light" }
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}

View file

@ -0,0 +1,48 @@
//! Example: ECS Architecture for very list selectors
//! --------------------------------------------------
//! Sometimes, you need *peak* performance. Cloning and Rc might simply be too much overhead for your app.
//! If you're building CPU intense apps like graphics editors, simulations, or advanced visualizations,
//! slicing up your state beyond atoms might be desirable.
//!
//! Instead of storing groups of entities in a collection of structs, the ECS Architecture instead stores
//! an array for each entity in the collection. This tends to improve performance for batch operations on
//! individual fields at the cost of complexity. Fortunately, this ECS model is built right into Recoil,
//! making it easier than ever to enable sharded datastructures in your app.
//!
//! Instead of defining a struct for our primary datastructure, we'll instead use a type tuple, and then later
//! index that tuple to get the value we care about. Unfortunately, we lose name information wrt to each
//! type in the type tuple. This can be solved with an associated module, the derive EcsMacro, or just
//! by good documentation.
//!
//! This approach is best suited for applications where individual entries in families are very large
//! and updates to neighbors are costly in terms of Clone or field comparisons for memoization.
use dioxus::prelude::*;
use dioxus_core as dioxus;
use recoil::*;
type TodoModel = (
bool, // checked
String, // name
String, // contents
);
const TODOS: EcsModel<u32, TodoModel> = |builder| {};
// const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(0).select(k);
// const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).select(k);
static App: FC<()> = |ctx, _| {
// let title = use_recoil_value(ctx, &C_SELECTOR);
dbg!(TODOS);
let title = "";
rsx! { in ctx,
div {
"{title}"
// button { onclick: {next_light}, "Next light" }
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}

View file

@ -0,0 +1,33 @@
use std::collections::HashMap;
use dioxus_core::prelude::*;
use recoil::*;
const TODOS: AtomFamily<&str, Todo> = |_| HashMap::new();
#[derive(PartialEq)]
struct Todo {
checked: bool,
contents: String,
}
static App: FC<()> = |ctx, _| {
rsx! { in ctx,
div {
"Basic Todolist with AtomFamilies in Recoil.rs"
}
}
};
static Child: FC<()> = |ctx, _| {
// let todo = use_recoil_value(ctx, &TODOS);
rsx! { in ctx,
div {
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}

View file

@ -0,0 +1,20 @@
use dioxus_core::prelude::*;
use recoil::*;
const COUNT: Atom<i32> = |_| 0;
static App: FC<()> = |ctx, _| {
let (count, set_count) = use_recoil_state(ctx, &COUNT);
rsx! { in ctx,
div {
"Count: {count}"
button { onclick: move |_| set_count(count + 1), "Incr" }
button { onclick: move |_| set_count(count - 1), "Decr" }
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}

View file

@ -0,0 +1,34 @@
use std::collections::HashMap;
use dioxus_core::prelude::*;
use recoil::*;
const A_ITEMS: AtomFamily<i32, i32> = |_| HashMap::new();
const B_ITEMS: AtomFamily<i32, i32> = |_| HashMap::new();
const C_SELECTOR: SelectorFamily<i32, i32> = |api, key| {
let a = api.get(&A_ITEMS.select(&key));
let b = api.get(&B_ITEMS.select(&key));
a + b
};
const D_SELECTOR: SelectorFamilyBorrowed<i32, i32> = |api, key| -> &i32 {
let a = api.get(&A_ITEMS.select(&key));
a
};
static App: FC<()> = |ctx, _| {
let title = use_recoil_value(ctx, &C_SELECTOR);
let title = "";
rsx! { in ctx,
div {
"{title}"
// button { onclick: {next_light}, "Next light" }
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}

View file

@ -0,0 +1,46 @@
use dioxus_core::prelude::*;
use recoil::*;
const A: Atom<i32> = |_| 0;
const B: Atom<i32> = |_| 0;
const C: Selector<i32> = |api| api.get(&A) + api.get(&B);
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx);
rsx! { in ctx,
div {
Banner {}
BtnA {}
BtnB {}
}
}
};
static Banner: FC<()> = |ctx, _| {
let count = use_recoil_value(ctx, &C);
ctx.render(rsx! { h1 { "Count: {count}" } })
};
static BtnA: FC<()> = |ctx, _| {
let (a, set) = use_recoil_state(ctx, &A);
rsx! { in ctx,
div { "a"
button { "+", onclick: move |_| set(a + 1) }
button { "-", onclick: move |_| set(a - 1) }
}
}
};
static BtnB: FC<()> = |ctx, _| {
let (b, set) = use_recoil_state(ctx, &B);
rsx! { in ctx,
div { "b"
button { "+", onclick: move |_| set(b + 1) }
button { "-", onclick: move |_| set(b - 1) }
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}

View file

@ -0,0 +1,65 @@
# Architecture
## ECS
It's often ideal to represent list-y state as an SoA (struct of arrays) instead of an AoS (array of structs). In 99% of apps, normal clone-y performance is fine. If you need more performance on top of cloning on update, IM.rs will provide fast immutable data structures.
But, if you need **extreme performance** consider the ECS (SoA) model. With ECS model, we can modify fields of an entry without invalidating selectors on neighboring fields.
This approach is for that 0.1% of apps that need peak performance. Our philosophy is that these tools should be available when needed, but you shouldn't need to reach for them often. An example use case might be a graphics editor or simulation engine where thousands of entities with many fields are rendered to the screen in realtime.
Recoil will even help you:
```rust
type TodoModel = (
String, // title
String // subtitle
);
const TODOS: RecoilEcs<u32, TodoModel> = |builder| {
builder.push("SomeTitle".to_string(), "SomeSubtitle".to_string());
};
const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(0).select(k);
const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).select(k);
```
Or with a custom derive macro to take care of some boilerplate and maintain readability. This macro simply generates the type tuple from the model fields and then some associated constants for indexing them.
```rust
#[derive(EcsModel)]
struct TodoModel {
title: String,
subtitle: String
}
// derives these impl (you don't need to write this yourself, but can if you want):
mod TodoModel {
type Layout = (String, String);
const title: u8 = 1;
const subtitle: u8 = 2;
}
const TODOS: RecoilEcs<u32, TodoModel::Layout> = |builder| {};
const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(TodoModel::title).select(k);
const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(TodoModel::subtitle).select(k);
```
## Optimization
Selectors and references.
----
Because new values are inserted without touching the original, we can keep old values around. As such, it makes sense to allow borrowed data since we can continue to reference old data if the data itself hasn't changed.
However, this does lead to a greater memory overhead, so occasionally we'll want to sacrifice an a component render in order to evict old values.

View file

@ -0,0 +1,26 @@
use crate::{AtomValue, Readable, RecoilItem};
pub type Atom<T: PartialEq> = fn(&mut AtomBuilder<T>) -> T;
impl<T: AtomValue + 'static> Readable<T> for Atom<T> {
fn load(&'static self) -> RecoilItem {
todo!()
// RecoilItem::Atom(self as *const _ as _)
}
}
pub struct AtomBuilder<T: PartialEq> {
pub key: String,
_never: std::marker::PhantomData<T>,
}
impl<T: PartialEq> AtomBuilder<T> {
pub fn new() -> Self {
Self {
key: "".to_string(),
_never: std::marker::PhantomData {},
}
}
pub fn set_key(&mut self, _key: &'static str) {}
}

View file

@ -0,0 +1,32 @@
use std::rc::Rc;
use crate::Atom;
pub struct RecoilApi {}
impl RecoilApi {
/// Get the value of an atom. Returns a reference to the underlying data.
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> Rc<T> {
todo!()
}
/// Replace an existing value with a new value
///
/// This does not replace the value instantly.
/// All calls to "get" will return the old value until the component is rendered.
pub fn set<T: PartialEq>(&self, t: &'static Atom<T>, new: T) {
self.modify(t, move |old| *old = new);
}
/// Modify lets you modify the value in place. However, because there's no previous value around to compare
/// the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
/// be updated, causing all subsrcibed components to re-render.
///
/// This is fine for most values, but might not be performant when dealing with collections. For collections,
/// use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
///
/// Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will the update occur
pub fn modify<T: PartialEq, O>(&self, t: &'static Atom<T>, f: impl FnOnce(&mut T) -> O) -> O {
todo!()
}
}

View file

@ -0,0 +1,26 @@
//! Provide a memoized wrapper around collections for efficient updates.
//! --------------------------------------------------------------------
//!
use crate::{AtomValue, FamilyKey, Readable, RecoilItem};
#[allow(non_camel_case_types)]
pub struct atom_family<K: FamilyKey, V: AtomValue>(pub fn(&mut AtomFamilyBuilder<K, V>));
pub type AtomFamily<K, V> = atom_family<K, V>;
// impl<K: FamilyKey, V: AtomValue> Readable for &'static AtomFamily<K, V> {
// fn load(&self) -> RecoilItem {
// RecoilItem::Atom(*self as *const _ as _)
// }
// }
pub struct AtomFamilyBuilder<K, V> {
_never: std::marker::PhantomData<(K, V)>,
}
impl<K: FamilyKey, V: AtomValue> atom_family<K, V> {
fn select(&'static self, key: &K) -> FamilySelected {
todo!()
}
}
struct FamilySelected {}

View file

@ -0,0 +1,110 @@
use std::rc::Rc;
use dioxus_core::{hooks::use_ref, prelude::Context};
use crate::{Atom, AtomValue, Readable, RecoilApi, RecoilRoot};
/// This hook initializes the Recoil Context - should only be placed once per app.
///
///
/// ```ignore
///
///
///
/// ```
pub fn init_root(ctx: Context) {
ctx.use_create_context(move || RecoilRoot::new())
}
/// Use an atom and its setter
///
/// This hook subscribes the component to any updates to the atom.
///
/// ```rust
/// const TITLE: Atom<&str> = atom(|_| "default");
///
/// static App: FC<()> = |ctx, props| {
/// let (title, set_title) = recoil::use_state(ctx, &TITLE);
/// ctx.render(rsx!{
/// div {
/// "{title}"
/// button {"on", onclick: move |_| set_title("on")}
/// button {"off", onclick: move |_| set_title("off")}
/// }
/// })
/// }
///
/// ```
pub fn use_recoil_state<'a, T: PartialEq + 'static>(
ctx: Context<'a>,
readable: &'static impl Readable<T>,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
struct RecoilStateInner<G: AtomValue> {
root: Rc<RecoilRoot>,
value: Rc<G>,
setter: Rc<dyn Fn(G)>,
}
let root = ctx.use_context::<RecoilRoot>();
let (subscriber_id, value) = root.subscribe_consumer(readable, ctx.schedule_update());
ctx.use_hook(
move || RecoilStateInner {
value,
root: root.clone(),
setter: Rc::new(move |new_val| root.update_atom(readable, new_val)),
},
move |hook| {
hook.value = hook.root.load_value(readable);
(hook.value.as_ref(), &hook.setter)
},
// Make sure we unsubscribe
// It's not *wrong* for a dead component to receive updates, but it is less performant
move |hook| hook.root.drop_consumer(subscriber_id),
)
}
///
///
///
/// ```ignore
/// let (title, set_title) = recoil::use_state()
///
///
///
/// ```
pub fn use_recoil_value<'a, T: PartialEq>(ctx: Context<'a>, t: &'static impl Readable<T>) -> &'a T {
todo!()
}
/// Update an atom's value without
///
/// Enable the ability to set a value without subscribing the componet
///
///
///
/// ```ignore
/// let (title, set_title) = recoil::use_state()
///
///
///
/// ```
pub fn use_set_state<'a, T: PartialEq>(c: Context<'a>, t: &'static Atom<T>) -> &'a Rc<dyn Fn(T)> {
todo!()
}
///
///
///
/// ```ignore
/// let (title, set_title) = recoil::use_state()
///
///
///
/// ```
pub fn use_recoil_callback<'a, F: 'a>(
ctx: Context<'a>,
f: impl Fn(RecoilApi) -> F + 'static,
) -> &F {
todo!()
}

View file

@ -0,0 +1,15 @@
mod atom;
mod callback;
mod family;
mod hooks;
mod root;
mod selector;
mod traits;
pub use atom::*;
pub use callback::*;
pub use family::*;
pub use hooks::*;
pub use root::*;
pub use selector::*;
pub use traits::*;

148
packages/recoil/old/root.rs Normal file
View file

@ -0,0 +1,148 @@
use std::{
any::Any,
cell::RefCell,
collections::HashMap,
rc::Rc,
sync::atomic::{AtomicU32, AtomicUsize},
};
use crate::{Atom, AtomBuilder, AtomValue, Readable};
pub type OpaqueConsumerCallback = Box<dyn Any>;
struct Consumer {
callback: OpaqueConsumerCallback,
}
static SUBSCRIBER_ID: AtomicU32 = AtomicU32::new(0);
fn next_id() -> u32 {
SUBSCRIBER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
type AtomId = u32;
type ConsumerId = u32;
pub struct RecoilRoot {
consumers: RefCell<HashMap<AtomId, Box<dyn RecoilSlot>>>,
}
impl RecoilRoot {
pub(crate) fn new() -> Self {
Self {
consumers: Default::default(),
}
}
// This is run by hooks when they register as a listener for a given atom
// Their ID is stored in the `atom_consumers` map which lets us know which consumers to update
// When the hook is dropped, they are unregistered from this map
//
// If the Atom they're registering as a listener for doesn't exist, then the atom
// is initialized. Otherwise, the most recent value of the atom is returned
// pub fn register_readable<T: AtomValue>(&self, atom: &impl Readable<T>) -> Rc<T> {
// todo!()
// }
/// This registers the updater fn to any updates to the readable
/// Whenever the readable changes, the updater Fn will be called.
///
/// This also back-propogates changes, meaning components that update an atom
/// will be updated from their subscription instead of directly within their own hook.
pub fn subscribe_consumer<T: AtomValue>(
&self,
atom: &'static impl Readable<T>,
updater: impl Fn(),
// return the value and the consumer's ID
// the consumer needs to store its ID so it can properly unsubscribe from the value
) -> (u32, Rc<T>) {
let id = next_id();
// Get the raw static reference of the atom
let atom_ptr = get_atom_raw_ref(atom);
let mut consumers = self.consumers.borrow_mut();
if !consumers.contains_key(&atom_ptr) {
// Run the atom's initialization
// let mut b = AtomBuilder::<T>::new();
// let inital_value = atom.0(&mut b);
consumers.insert(atom_ptr, HashMap::new());
}
todo!()
// Rc::new(inital_value)
// todo!()
// // Get the raw static reference of the atom
// let atom_ptr = get_atom_raw_ref(atom);
// // Subcribe this hook_id to this atom
// let mut consumers = self.consumers.borrow_mut();
// let hooks = consumers
// .get_mut(&atom_ptr)
// .expect("Atom must be initialized before being subscribed to");
// // Coerce into any by wrapping the updater in a box
// // (yes it's some weird indirection)
// let any_updater: OpaqueConsumerCallback = Box::new(updater);
// let consumer = Consumer {
// callback: any_updater,
// };
// Insert into the map, booting out the old consumer
// TODO @Jon, make the "Consumer" more efficient, patching its update mechanism in-place
// hooks.insert(hook_id, consumer);
}
pub fn drop_consumer(&self, subscriber_id: u32) {
// let mut consumers = self.consumers.borrow_mut();
// let atom_ptr = get_atom_raw_ref(atom);
// let atoms = consumers.get_mut(&atom_ptr);
// if let Some(consumers) = atoms {
// let entry = consumers.remove_entry(&hook_id);
// if let Some(_) = entry {
// log::debug!("successfully unsubscribed listener");
// } else {
// log::debug!("Failure to unsubscribe");
// }
// } else {
// log::debug!("Strange error, atoms should be registed if the consumer is being dropped");
// }
}
pub fn update_atom<T: AtomValue>(&self, atom: &impl Readable<T>, new_val: T) {
// Get the raw static reference of the atom
// let atom_ptr = get_atom_raw_ref(atom);
// let mut consumers = self.consumers.borrow_mut();
// let hooks = consumers
// .get_mut(&atom_ptr)
// .expect("Atom needs to be registered before trying to update it");
// let new_val = Rc::new(new_val);
// for hook in hooks.values_mut() {
// let callback: &mut Rc<ConsumerCallback<T>> = hook
// .callback
// .downcast_mut::<_>()
// .expect("Wrong type of atom stored, internal error");
// callback(UpdateAction::Regenerate(new_val.clone()));
// }
}
pub fn load_value<T: AtomValue>(&self, atom: &impl Readable<T>) -> Rc<T> {
todo!()
}
}
trait RecoilSlot {}
// struct RecoilSlot {
// consumers: HashMap<u32, Box<dyn Fn()>>,
// }
fn get_atom_raw_ref<T: AtomValue>(atom: &'static impl Readable<T>) -> u32 {
let atom_ptr = atom as *const _;
let atom_ptr = atom_ptr as *const u32;
atom_ptr as u32
}

View file

@ -0,0 +1,25 @@
use crate::Atom;
// =====================================
// Selectors
// =====================================
pub struct SelectorApi {}
impl SelectorApi {
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> &T {
todo!()
}
}
// pub struct SelectorBuilder<Out, const Built: bool> {
// _p: std::marker::PhantomData<Out>,
// }
// impl<O> SelectorBuilder<O, false> {
// pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
// todo!()
// // std::rc::Rc::pin(value)
// // todo!()
// }
// }
pub struct selector<O>(pub fn(&SelectorApi) -> O);
// pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
pub type Selector<O> = selector<O>;

View file

@ -0,0 +1,13 @@
use crate::{Atom, AtomFamily};
use std::hash::Hash;
pub trait FamilyKey: PartialEq + Hash {}
impl<T: PartialEq + Hash> FamilyKey for T {}
pub trait AtomValue: PartialEq {}
impl<T: PartialEq> AtomValue for T {}
pub trait Readable<T>: 'static {
fn load(&'static self) -> RecoilItem;
}

View file

@ -1,176 +1,162 @@
// ========================
// Important hooks
// ========================
use std::{
cell::{Ref, RefCell},
collections::HashMap,
hash::Hash,
marker::PhantomData,
rc::Rc,
};
pub fn init_recoil_root(ctx: Context) {
let res = ctx.try_use_context::<RecoilRoot>();
ctx.create_context(move || {
//
if res.is_ok() {
panic!(
"Cannot initialize a recoil root inside of a recoil root.\n An app may only have one recoil root per virtual dom instance"
);
}
RecoilRoot {}
})
}
pub trait FamilyKey: PartialEq + Hash {}
impl<T: PartialEq + Hash> FamilyKey for T {}
pub fn use_atom<'a, T: PartialEq>(c: Context<'a>, t: &'static Atom<T>) -> &'a T {
todo!()
}
pub trait AtomValue: PartialEq + Clone {}
impl<T: PartialEq + Clone> AtomValue for T {}
pub fn use_set_atom<'a, T: PartialEq>(c: Context<'a>, t: &'static Atom<T>) -> Rc<dyn Fn(T)> {
todo!()
}
// Atoms, selectors, and their family variants are readable
pub trait Readable<T> {}
use std::rc::Rc;
// Only atoms and atom families are writable
// Selectors are not
pub trait Writeable<T>: Readable<T> {}
use dioxus_core::virtual_dom::Context;
// =================
// Atoms
// =================
pub struct RecoilRoot {}
pub struct AtomBuilder {}
pub type Atom<T> = fn(&mut AtomBuilder) -> T;
impl<T> Readable<T> for Atom<T> {}
impl<T> Writeable<T> for Atom<T> {}
pub struct RecoilContext {}
pub type AtomFamily<K, V, F = HashMap<K, V>> = fn((K, V)) -> F;
impl RecoilContext {
/// Get the value of an atom. Returns a reference to the underlying data.
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> &T {
pub trait SelectionSelector<K, V> {
fn select(&self, k: &K) -> CollectionSelection<V> {
todo!()
}
}
impl<K, V, F> SelectionSelector<K, V> for AtomFamily<K, V, F> {}
/// Replace an existing value with a new value
///
/// This does not replace the value instantly.
/// All calls to "get" will return the old value until the component is rendered.
pub fn set<T: PartialEq>(&self, t: &'static Atom<T>, new: T) {
self.modify(t, move |old| *old = new);
pub trait FamilyCollection<K, V> {}
impl<K, V> FamilyCollection<K, V> for HashMap<K, V> {}
pub struct CollectionSelection<T> {
_never: PhantomData<T>,
}
impl<T> Readable<T> for CollectionSelection<T> {}
// =================
// Selectors
// =================
pub struct SelectorBuilder {}
impl SelectorBuilder {
pub fn get<T: PartialEq>(&self, t: &impl Readable<T>) -> &T {
todo!()
}
}
pub type Selector<T> = fn(&mut SelectorBuilder) -> T;
impl<T> Readable<T> for Selector<T> {}
/// Modify lets you modify the value in place. However, because there's no previous value around to compare
/// the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
/// be updated, causing all subsrcibed components to re-render.
///
/// This is fine for most values, but might not be performant when dealing with collections. For collections,
/// use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
///
/// Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will the update occur
pub struct SelectorFamilyBuilder {}
impl SelectorFamilyBuilder {
pub fn get<T: PartialEq>(&self, t: &impl Readable<T>) -> &T {
todo!()
}
}
/// Create a new value as a result of a combination of previous values
/// If you need to return borrowed data, check out [`SelectorFamilyBorrowed`]
pub type SelectorFamily<Key, Value> = fn(&mut SelectorFamilyBuilder, Key) -> Value;
impl<K, V> Readable<V> for SelectorFamily<K, V> {}
/// Borrowed selector families are surprisingly - discouraged.
/// This is because it's not possible safely memoize these values without keeping old versions around.
///
/// However, it does come in handy to borrow the contents of an item without re-rendering child components.
pub type SelectorFamilyBorrowed<Key, Value> =
for<'a> fn(&'a mut SelectorFamilyBuilder, Key) -> &'a Value;
impl<'a, K, V: 'a> SelectionSelector<K, V> for fn(&'a mut SelectorFamilyBuilder, K) -> V {}
// =================
// API
// =================
pub struct RecoilApi {}
impl RecoilApi {
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> Rc<T> {
todo!()
}
pub fn modify<T: PartialEq, O>(&self, t: &'static Atom<T>, f: impl FnOnce(&mut T) -> O) -> O {
todo!()
}
pub fn set<T: PartialEq>(&self, t: &'static Atom<T>, new: T) {
self.modify(t, move |old| *old = new);
}
}
pub fn use_recoil_context<T>(c: Context) -> &T {
todo!()
// ================
// Root
// ================
type AtomId = u32;
type ConsumerId = u32;
pub struct RecoilRoot {
consumers: RefCell<HashMap<AtomId, Box<dyn RecoilSlot>>>,
}
// pub fn use_callback<'a>(c: &Context<'a>, f: impl Fn() -> G) -> &'a RecoilContext {
// todo!()
// }
trait RecoilSlot {}
pub trait Readable {}
impl<T: PartialEq> Readable for &'static Atom<T> {}
impl<K: PartialEq, V: PartialEq> Readable for &'static AtomFamily<K, V> {}
pub fn use_atom_family<'a, K: PartialEq, V: PartialEq>(
c: &Context<'a>,
t: &'static AtomFamily<K, V>,
g: K,
) -> &'a V {
todo!()
}
pub struct AtomBuilder<T: PartialEq> {
pub key: String,
pub manual_init: Option<Box<dyn Fn() -> T>>,
_never: std::marker::PhantomData<T>,
}
impl<T: PartialEq> AtomBuilder<T> {
pub fn new() -> Self {
impl RecoilRoot {
pub(crate) fn new() -> Self {
Self {
key: "".to_string(),
manual_init: None,
_never: std::marker::PhantomData {},
consumers: Default::default(),
}
}
}
pub fn init<A: Fn() -> T + 'static>(&mut self, f: A) {
self.manual_init = Some(Box::new(f));
pub use hooks::*;
mod hooks {
use super::*;
use dioxus_core::prelude::Context;
pub fn use_init_recoil_root(ctx: Context) {
ctx.use_create_context(move || RecoilRoot::new())
}
pub fn set_key(&mut self, _key: &'static str) {}
}
pub fn use_set_state<'a, T: PartialEq>(
c: Context<'a>,
t: &'static impl Writeable<T>,
) -> &'a Rc<dyn Fn(T)> {
todo!()
}
// =====================================
// Atom
// =====================================
pub struct atom<T: PartialEq>(pub fn(&mut AtomBuilder<T>) -> T);
pub type Atom<T: PartialEq> = atom<T>;
pub fn use_recoil_state<'a, T: PartialEq + 'static>(
ctx: Context<'a>,
readable: &'static impl Writeable<T>,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
todo!()
}
// =====================================
// Atom Family
// =====================================
pub struct AtomFamilyBuilder<K, V> {
_never: std::marker::PhantomData<(K, V)>,
}
pub fn use_recoil_value<'a, T: PartialEq>(
ctx: Context<'a>,
t: &'static impl Readable<T>,
) -> &'a T {
todo!()
}
pub struct atom_family<K: PartialEq, V: PartialEq>(pub fn(&mut AtomFamilyBuilder<K, V>));
pub type AtomFamily<K: PartialEq, V: PartialEq> = atom_family<K, V>;
// =====================================
// Selectors
// =====================================
pub struct SelectorApi {}
impl SelectorApi {
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> &T {
pub fn use_recoil_callback<'a, F: 'a>(
ctx: Context<'a>,
f: impl Fn(RecoilApi) -> F + 'static,
) -> &F {
todo!()
}
}
// pub struct SelectorBuilder<Out, const Built: bool> {
// _p: std::marker::PhantomData<Out>,
// }
// impl<O> SelectorBuilder<O, false> {
// pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
// todo!()
// // std::rc::Rc::pin(value)
// // todo!()
// }
// }
pub struct selector<O>(pub fn(&SelectorApi) -> O);
// pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
pub type Selector<O> = selector<O>;
pub fn use_selector<'a, O>(c: Context<'a>, s: &'static Selector<O>) -> &'a O {
todo!()
}
pub fn callback_example(ctx: Context) {
let set_user = use_recoil_callback(ctx, |api| {
|a: String, b: ()| {
//
}
});
let set_user = use_recoil_callback(ctx, |api| {
//
});
}
fn use_recoil_callback<F>(ctx: Context, f: impl Fn(RecoilContext) -> F) -> F {
todo!()
}
mod analyzer {
pub use ecs::*;
mod ecs {
use super::*;
const TITLE: Atom<&'static str> = atom(|_| Default::default());
// pub fn use_add_todo(ctx: Context) -> impl Fn(&'static str) + '_ {
// use_recoil_callback(ctx, move |api| move |a: &'static str| api.set(&TITLE, a))
// }
struct Analyzer(RecoilContext);
fn use_analyzer(ctx: Context) -> Analyzer {
use_recoil_callback(ctx, |api| Analyzer(api))
pub struct Blah<K, V> {
_p: PhantomData<(K, V)>,
}
pub type EcsModel<K, Ty> = fn(Blah<K, Ty>);
}

View file

@ -1,32 +1,31 @@
//! Example: Context API
//! --------------------
//! This example demonstrates how to use the raw context api for sharing state throughout the VirtualDOM Tree.
//! A custom context must be its own unique type - otherwise use_context will fail. A context may be c
//!
//!
//!
//!
//!
//!
//!
//!
use std::fmt::Display;
use dioxus::{events::on::MouseEvent, prelude::*};
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(async {
WebsysRenderer::new_with_props(Example, ())
.run()
.await
.unwrap()
});
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
#[derive(Debug)]
struct CustomContext([&'static str; 3]);
static Example: FC<()> = |ctx, props| {
ctx.create_context(|| CustomContext(["Jack", "Jill", "Bob"]));
let names = ctx.use_context::<CustomContext>();
// let name = names.0[props.id as usize];
ctx.use_create_context(|| CustomContext(["Jack", "Jill", "Bob"]));
ctx.render(rsx! {
div {
@ -53,7 +52,7 @@ struct ButtonProps {
id: u8,
}
fn CustomButton<'b, 'a,>(ctx: Context<'a>, props: &'b ButtonProps) -> DomTree {
fn CustomButton(ctx: Context, props: &ButtonProps) -> DomTree {
let names = ctx.use_context::<CustomContext>();
let name = names.0[props.id as usize];

View file

@ -25,7 +25,7 @@ const FILTER: Atom<FilterState> = atom(|_| FilterState::All);
const TODOS_LEFT: selector<usize> = selector(|api| api.get(&TODO_LIST).len());
// Implement a simple abstraction over sets/gets of multiple atoms
struct TodoManager(RecoilContext);
struct TodoManager(RecoilApi);
impl TodoManager {
fn add_todo(&self, contents: String) {
let item = TodoItem {
@ -55,8 +55,6 @@ impl TodoManager {
});
}
fn clear_completed(&self) {
TODO_LIST.with(api).get()
self.0.modify(&TODO_LIST, move |list| {
*list = list.drain().filter(|(_, item)| !item.checked).collect();
})
@ -81,7 +79,7 @@ pub struct TodoItem {
}
fn App(ctx: Context, props: &()) -> DomTree {
init_recoil_root(ctx);
use_init_recoil_root(ctx);
rsx! { in ctx,
div { id: "app", style { "{APP_STYLE}" }
@ -93,8 +91,8 @@ fn App(ctx: Context, props: &()) -> DomTree {
pub fn TodoList(ctx: Context, props: &()) -> DomTree {
let draft = use_state_new(&ctx, || "".to_string());
let todos = use_atom(&ctx, &TODO_LIST);
let filter = use_atom(&ctx, &FILTER);
let todos = use_recoil_value(&ctx, &TODO_LIST);
let filter = use_recoil_value(&ctx, &FILTER);
let todolist = todos
.values()
@ -137,8 +135,7 @@ pub struct TodoEntryProps {
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
let (is_editing, set_is_editing) = use_state(&ctx, || false);
let todo = use_atom(&ctx, &TODO_LIST).get(&props.id).unwrap();
// let todo = use_atom_family(&ctx, &TODOS, props.id);
let todo = use_recoil_value(&ctx, &TODO_LIST).get(&props.id).unwrap();
ctx.render(rsx! (
li {