WIP: move to static props

This commit is contained in:
Jonathan Kelley committed 2021-05-31 18:55:56 -04:00
1 parent c5089ba3c5
commit c1fd848f89
37 files changed
+1014 -339

No files matched your search

+6 -2
View File
@@ -9,10 +9,14 @@ edition = "2018"
[dependencies]
anyhow = "1.0.40"
dioxus-core = { path = "../core" }
generational-arena = "0.2.8"
thiserror = "1.0.24"
log = "0.4.14"
im-rc = "15.0.0"
[dev-dependencies]
uuid = { version = "0.8.2", features = ["v4"] }
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
dioxus-web = { path = "../web" }
wasm-bindgen-futures = "*"
wasm-logger = "0.2.0"
console_error_panic_hook = "0.1.6"
+107 -2
View File
@@ -6,12 +6,14 @@ Recoil.rs is officially supported by the Dioxus team. By doing so, are are "plan
Internally, Dioxus uses batching to speed up linear-style operations. Recoil.rs integrates with this batching optimization, making app-wide changes extremely fast. This way, Recoil.rs can be pushed significantly harder than Redux without the need to enable/disable debug flags to prevent performance slowdowns.
## Guide
# Guide
## Atoms
A simple atom of state is defined globally as a const:
```rust
const Light: Atom<&'static str> = |_| "Green";
const Light: Atom<&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.
@@ -30,3 +32,106 @@ fn App(ctx: Context, props: &()) -> DomTree {
})
}
```
Atoms are considered "Writable" objects since any consumer may also set the Atom's value with their own:
```rust
fn App(ctx: Context, props: &()) -> DomTree {
let color = recoil::use_read(ctx, Light);
let set_color = recoil::use_write(ctx, Light);
rsx!{in ctx,
div {
h1{"Color: {color}"}
button {onclick: move |_| set_color("red"), "red"}
button {onclick: move |_| set_color("yellow"), "yellow"}
button {onclick: move |_| set_color("green"), "green"}
}
}
}
```
"Reading" a value with use_read subscribes that component to any changes to that value while "Writing" a value does not. It's a good idea to use `write-only` whenever it makes sense to prevent unnecessary evaluations. Both `read` and `write` may be combined together to provide a `use_state` style hook.
```rust
fn App(ctx: Context, props: &()) -> DomTree {
let (color, set_color) = recoil::use_read_write(ctx, Light);
rsx!{in ctx,
div {
h1{"Color: {color}"}
button {onclick: move |_| set_color("red"), "red"}
}
}
}
```
## Selectors
Selectors are a concept popular in the JS world as a way of narrowing down a selection of state outside the VDOM lifecycle. Selectors have two functions: 1) summarize/narrow down some complex state and 2) memoize calculations.
Selectors are only `readable` - they cannot be set. This differs from RecoilJS where selectors _are_ `writable`. Selectors, as you might've guessed, "select" some data from atoms and other selectors.
Selectors provide a `SelectorApi` which essentially exposes a read-only `RecoilApi`. They have the `get` method which allows any readable valued to be obtained for the purpose of generating a new value. A `Selector` may only return `'static` data, however `SelectorBorrowed` may return borrowed data.
returning static data:
```rust
const Light: Atom<&'static str> = |_| "Green";
const NumChars: Selector<usize> = |api| api.get(&Light).len();
```
Selectors will update their selection and notify subscribed components whenever their dependencies also update. The `get` calls in a selector will subscribe that selector to whatever `Readable` is being `get`-ted. Unlike hooks, you may use `get` in conditionals; an internal algorithm decides when a selector needs to be updated based on what it `get`-ted last time it was ran.
Selectors may also returned borrowed data:
```rust
const Light: Atom<&'static str> = |_| "Green";
const ThreeChars: SelectorBorrowed<str> = |api| api.get(&Light).chars().take(3).unwrap();
```
Unfortunately, Rust tries to coerce `T` (the type in the Selector generics) to the `'static` lifetime because we defined it as a static. `SelectorBorrowed` defines a type for a function that returns `&T` and provides the right lifetime for `T`. If you don't like having to use a dedicated `Borrowed` type, regular functions work too:
```rust
fn Light(api: &mut AtomBuilder) -> &'static str {
"Green"
}
fn ThreeChars(api: &mut SelectorApi) -> &'static str {
api.get(&Light).chars().take(3).unwrap()
}
```
Returning borrowed data is generally frowned upon, but may be useful when used wisely.
- If a selected value equals its previous selection (via PartialEq), the old value must be kept around to avoid evaluating subscribed components.
- It's unlikely that a change in a dependency's data will not change the selector's output.
In general, borrowed selectors introduce a slight memory overhead as they need to retain previous state to safely memoize downstream subscribers. The amount of state kept around scales with the amount of `gets` in a selector - though as the number of dependencies increase, the less likely the selector actually stays memoized. Recoil tries to optimize this behavior the best it can to balance component evaluations with memory overhead.
## Families
You might notice that collections will not be performant with just sets/gets. We don't want to clone our entire HashMap every time we want to insert or remove an entry! That's where `Families` come in. Families are memoized collections (HashMap and OrdMap) that wrap the immutable datastructures library `im-rc`. Families are defined by a function that takes the FamilyApi and returns a function that provides a default value given a key. In this example, we insert a value into the collection when initialized, and then return a function that takes a key and returns a default value.
```rust
const CloudRatings: AtomFamily<&str, i32> = |api| {
api.insert("Oracle", -1);
|key| match key {
"AWS" => 1,
"Azure" => 2,
"GCP" => 3,
_ => 0
}
}
```
Whenever you `select` on a `Family`, the ID of the entry is tracked. Other subscribers will only be updated if they too select the same family with the same key and that value is updated. Otherwise, these subscribers will never re-render on an "insert", "remove", or "update" of the collection. You could easily implement this yourself with Atoms, immutable datastructures, and selectors, but our implementation is more efficient and first-class.
```rust
fn App(ctx: Context, props: &()) -> DomTree {
let (rating, set_rating) = recoil::use_read_write(ctx, CloudRatings.select("AWS"));
rsx!{in ctx,
div {
h1{ "AWS rating: {rating}" }
button { onclick: move |_| set_rating((rating + 1) % 5), "incr" }
}
}
}
```
+19 -12
View File
@@ -1,27 +1,34 @@
use std::collections::HashMap;
use std::{collections::HashMap, rc::Rc};
use dioxus_core::prelude::*;
use recoil::*;
use uuid::Uuid;
const TODOS: AtomFamily<Uuid, Todo> = |_| HashMap::new();
const TODOS: AtomHashMap<Uuid, Rc<Todo>> = |map| {};
#[derive(PartialEq)]
struct Todo {
checked: bool,
title: String,
contents: String,
content: String,
}
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx, |_| {});
use_init_recoil_root(ctx, move |cfg| {});
let todos = use_read_family(ctx, &TODOS);
rsx! { in ctx,
div {
"Basic Todolist with AtomFamilies in Recoil.rs"
}
}
// rsx! { in ctx,
// div {
// h1 {"Basic Todolist with AtomFamilies in Recoil.rs"}
// ul { { todos.keys().map(|id| rsx! { Child { id: *id } }) } }
// }
// }
ctx.render(html! {
<a href="#" class="">
<img class="inline-block h-10 w-10 rounded-full object-cover ring-2 ring-white" src="/images/person/4.jpg" alt="Jade"/>
</a>
})
};
#[derive(Props, PartialEq)]
@@ -33,11 +40,11 @@ static Child: FC<ChildProps> = |ctx, props| {
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&props.id));
rsx! { in ctx,
div {
li {
h1 {"{todo.title}"}
input { type: "checkbox", name: "scales", checked: "{todo.checked}" }
label { "{todo.contents}", for: "scales" }
p {"{todo.contents}"}
label { "{todo.content}", for: "scales" }
p {"{todo.content}"}
}
}
};
+47
View File
@@ -0,0 +1,47 @@
use dioxus_core::prelude::*;
use im_rc::HashMap as ImMap;
use recoil::*;
use uuid::Uuid;
const TODOS: Atom<ImMap<Uuid, Todo>> = |_| ImMap::new();
#[derive(PartialEq)]
struct Todo {
checked: bool,
title: String,
contents: String,
}
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx, |_| {});
let todos = use_read(ctx, &TODOS);
rsx! { in ctx,
div {
"Basic Todolist with AtomFamilies in Recoil.rs"
}
}
};
#[derive(Props, PartialEq)]
struct ChildProps {
id: Uuid,
}
static Child: FC<ChildProps> = |ctx, props| {
let todo = use_read(ctx, &TODOS).get(&props.id).unwrap();
// let (todo, set_todo) = use_read_write(ctx, &TODOS);
rsx! { in ctx,
div {
h1 {"{todo.title}"}
input { type: "checkbox", name: "scales", checked: "{todo.checked}" }
label { "{todo.contents}", for: "scales" }
p {"{todo.contents}"}
}
}
};
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}
@@ -11,12 +11,19 @@ static App: FC<()> = |ctx, _| {
rsx! { in ctx,
div {
"Count: {count}"
button { onclick: move |_| set_count(count + 1), "Incr" }
button { onclick: move |_| set_count(count - 1), "Decr" }
br {}
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))
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
log::debug!("asd");
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App));
}
+2 -2
View File
@@ -3,8 +3,8 @@ 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 A_ITEMS: AtomHashMap<i32, i32> = |_| HashMap::new();
const B_ITEMS: AtomHashMap<i32, i32> = |_| HashMap::new();
const C_SELECTOR: SelectorFamily<i32, i32> = |api, key| {
let a = api.get(&A_ITEMS.select(&key));
+1
View File
@@ -0,0 +1 @@
println!("asd")
+2 -2
View File
@@ -95,8 +95,8 @@ const ContentCards: SelectorFamily<Uuid, ContentCard> = |api, key| api.on_get_as
static ContentCard: FC<()> = |ctx, props| {
let body = async match use_recoil_value()(props.id).await {
Ok(content) => rsx!{ p {"{content}"} }
Err(e) => rsx!{ p {"Failed to load"}}
Ok(content) => rsx!{in ctx, p {"{content}"} }
Err(e) => rsx!{in ctx, p {"Failed to load"}}
};
rsx!{
+172 -105
View File
@@ -6,7 +6,6 @@ use std::{
rc::Rc,
};
pub use api::*;
pub use atomfamily::*;
pub use atoms::*;
pub use ecs::*;
@@ -18,13 +17,14 @@ pub use selector::*;
pub use selectorfamily::*;
pub use traits::*;
pub use utils::*;
mod tracingimmap;
mod traits {
use dioxus_core::prelude::Context;
use super::*;
pub trait FamilyKey: PartialEq + Hash + 'static {}
impl<T: PartialEq + Hash + 'static> FamilyKey for T {}
pub trait MapKey: PartialEq + Hash + 'static {}
impl<T: PartialEq + Hash + 'static> MapKey for T {}
pub trait AtomValue: PartialEq + 'static {}
impl<T: PartialEq + 'static> AtomValue for T {}
@@ -32,8 +32,7 @@ mod traits {
// Atoms, selectors, and their family variants are readable
pub trait Readable<T: AtomValue>: Sized + Copy {
fn use_read<'a>(self, ctx: Context<'a>) -> &'a T {
hooks::use_read(ctx, self);
todo!()
hooks::use_read(ctx, self)
}
// This returns a future of the value
@@ -44,9 +43,7 @@ mod traits {
todo!()
}
fn initialize(self, api: &RecoilRoot) -> T {
todo!()
}
fn initialize(self, api: &RecoilRoot) -> T;
// We use the Raw Ptr to the atom
// TODO: Make sure atoms with the same definitions don't get merged together. I don't think they do, but double check
@@ -78,7 +75,13 @@ mod atoms {
// impl<T: AtomValue> Readable<T> for Atom<T> {}
impl<T: AtomValue> Readable<T> for &'static Atom<T> {
fn static_id(self) -> u32 {
todo!()
self as *const _ as u32
}
fn initialize(self, api: &RecoilRoot) -> T {
let mut builder = AtomBuilder {};
let p = self(&mut builder);
p
}
}
@@ -88,13 +91,13 @@ mod atoms {
use super::*;
use dioxus_core::prelude::Context;
const Example: Atom<i32> = |_| 10;
fn _test(ctx: Context) {
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
fn test(ctx: Context) {
// ensure that atoms are both read and write
let _ = use_read(ctx, &Example);
let _ = use_read_write(ctx, &Example);
let _ = use_write(ctx, &Example);
let _ = use_read(ctx, &EXAMPLE_ATOM);
let _ = use_read_write(ctx, &EXAMPLE_ATOM);
let _ = use_write(ctx, &EXAMPLE_ATOM);
}
}
}
@@ -104,38 +107,57 @@ mod atomfamily {
pub trait FamilyCollection<K, V> {}
impl<K, V> FamilyCollection<K, V> for HashMap<K, V> {}
pub type AtomFamily<K, V, F = HashMap<K, V>> = fn((&K, &V)) -> F;
use im_rc::HashMap as ImHashMap;
pub trait AtomFamilySelector<K: FamilyKey, V: AtomValue> {
fn select(&'static self, k: &K) -> AtomFamilySelection<K, V> {
/// AtomHashMaps provide an efficient way of maintaing collections of atoms.
///
/// Under the hood, AtomHashMaps uses [IM](https://www.rust-lang.org)'s immutable HashMap implementation to lazily
/// clone data as it is modified.
///
///
///
///
///
///
pub type AtomHashMap<K, V> = fn(&mut ImHashMap<K, V>);
pub trait AtomFamilySelector<K: MapKey, V: AtomValue + Clone> {
fn select(&'static self, k: &K) -> AtomMapSelection<K, V> {
todo!()
}
}
impl<K: FamilyKey, V: AtomValue> AtomFamilySelector<K, V> for AtomFamily<K, V> {
fn select(&'static self, k: &K) -> AtomFamilySelection<K, V> {
impl<K: MapKey, V: AtomValue + Clone> AtomFamilySelector<K, V> for AtomHashMap<K, V> {
fn select(&'static self, k: &K) -> AtomMapSelection<K, V> {
todo!()
}
}
pub struct AtomFamilySelection<'a, K: FamilyKey, V: AtomValue> {
root: &'static AtomFamily<K, V>,
pub struct AtomMapSelection<'a, K: MapKey, V: AtomValue> {
root: &'static AtomHashMap<K, V>,
key: &'a K,
}
impl<'a, K: FamilyKey, V: AtomValue> Readable<V> for &AtomFamilySelection<'a, K, V> {
impl<'a, K: MapKey, V: AtomValue> Readable<V> for &AtomMapSelection<'a, K, V> {
fn static_id(self) -> u32 {
todo!()
}
fn initialize(self, api: &RecoilRoot) -> V {
todo!()
// let mut builder = AtomBuilder {};
// let p = self(&mut builder);
// p
}
}
impl<'a, K: FamilyKey, T: AtomValue> Writable<T> for &AtomFamilySelection<'a, K, T> {}
impl<'a, K: MapKey, T: AtomValue> Writable<T> for &AtomMapSelection<'a, K, T> {}
mod compiletests {
use dioxus_core::prelude::Context;
use super::*;
const Titles: AtomFamily<u32, &str> = |_| HashMap::new();
const Titles: AtomHashMap<u32, &str> = |map| {};
fn test(ctx: Context) {
let title = Titles.select(&10).use_read(ctx);
@@ -157,6 +179,10 @@ mod selector {
fn static_id(self) -> u32 {
todo!()
}
fn initialize(self, api: &RecoilRoot) -> T {
todo!()
}
}
pub struct SelectorFamilyBuilder {}
@@ -184,6 +210,10 @@ mod selectorfamily {
fn static_id(self) -> u32 {
todo!()
}
fn initialize(self, api: &RecoilRoot) -> V {
todo!()
}
}
/// Borrowed selector families are – surprisingly - discouraged.
@@ -196,32 +226,12 @@ mod selectorfamily {
// impl<'a, K, V: 'a> SelectionSelector<K, V> for fn(&'a mut SelectorFamilyBuilder, K) -> V {}
}
mod api {
use super::*;
// pub struct RecoilApi {}
// impl RecoilApi {
// pub fn get<T: AtomValue>(&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: AtomValue>(&self, t: &'static Atom<T>, new: T) {
// self.modify(t, move |old| *old = new);
// }
// }
}
mod root {
use std::{
any::{Any, TypeId},
collections::{HashSet, VecDeque},
iter::FromIterator,
sync::atomic::{AtomicU32, AtomicUsize},
};
use super::*;
@@ -229,17 +239,7 @@ mod root {
type AtomId = u32;
type ConsumerId = u32;
pub struct RecoilContext {
pub(crate) inner: Rc<RefCell<RecoilRoot>>,
}
impl RecoilContext {
pub fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(RecoilRoot::new())),
}
}
}
pub type RecoilContext = RefCell<RecoilRoot>;
// Sometimes memoization means we don't need to re-render components that holds "correct values"
// IE we consider re-render more expensive than keeping the old value around.
@@ -248,7 +248,8 @@ mod root {
// Instead, we choose to let the hook itself hold onto the Rc<T> by not forcing a render when T is the same.
// Whenever the component needs to be re-rendered for other reasons, the "get" method will automatically update the Rc<T> to the most recent one.
pub struct RecoilRoot {
nodes: HashMap<AtomId, Slot>,
nodes: RefCell<HashMap<AtomId, Slot>>,
consumer_map: HashMap<ConsumerId, AtomId>,
}
struct Slot {
@@ -259,23 +260,40 @@ mod root {
dependents: HashSet<AtomId>,
}
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
fn next_consumer_id() -> u32 {
NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
impl RecoilRoot {
pub(crate) fn new() -> Self {
Self {
nodes: Default::default(),
consumer_map: Default::default(),
}
}
pub fn subscribe<T: AtomValue>(
&self,
&mut self,
readable: impl Readable<T>,
receiver_fn: Rc<dyn Fn()>,
) -> ConsumerId {
todo!()
let consumer_id = next_consumer_id();
let atom_id = readable.static_id();
log::debug!("Subscribing consumer to atom {} {}", consumer_id, atom_id);
let mut nodes = self.nodes.borrow_mut();
let slot = nodes.get_mut(&atom_id).unwrap();
slot.consumers.insert(consumer_id, receiver_fn);
self.consumer_map.insert(consumer_id, atom_id);
consumer_id
}
pub fn unsubscribe(&self, id: ConsumerId) {
todo!()
pub fn unsubscribe(&mut self, consumer_id: ConsumerId) {
let atom_id = self.consumer_map.get(&consumer_id).unwrap();
let mut nodes = self.nodes.borrow_mut();
let slot = nodes.get_mut(&atom_id).unwrap();
slot.consumers.remove(&consumer_id);
}
/// Directly get the *slot*
@@ -283,12 +301,28 @@ mod root {
///
///
pub fn try_get_raw<T: AtomValue>(&self, readable: impl Readable<T>) -> Result<Rc<T>> {
todo!()
}
let atom_id = readable.static_id();
let mut nodes = self.nodes.borrow_mut();
if !nodes.contains_key(&atom_id) {
let value = Slot {
type_id: TypeId::of::<T>(),
source: atom_id,
value: Rc::new(readable.initialize(self)),
consumers: Default::default(),
dependents: Default::default(),
};
nodes.insert(atom_id, value);
}
let out = nodes
.get(&atom_id)
.unwrap()
.value
.clone()
.downcast::<T>()
.unwrap();
// pub fn try_get<T: AtomValue>(&self, readable: impl Readable<T>) -> Result<&T> {
// self.try_get_raw(readable).map(|f| f.as_ref())
// }
Ok(out)
}
pub fn try_set<T: AtomValue>(
&mut self,
@@ -297,7 +331,15 @@ mod root {
) -> crate::error::Result<()> {
let atom_id = writable.static_id();
let consumers = match self.nodes.get_mut(&atom_id) {
self.set_by_id(atom_id, new_val);
Ok(())
}
// A slightly dangerous method to manually overwrite any slot given an AtomId
pub(crate) fn set_by_id<T: AtomValue>(&mut self, atom_id: AtomId, new_val: T) {
let mut nodes = self.nodes.borrow_mut();
let consumers = match nodes.get_mut(&atom_id) {
Some(slot) => {
slot.value = Rc::new(new_val);
&slot.consumers
@@ -306,44 +348,30 @@ mod root {
let value = Slot {
type_id: TypeId::of::<T>(),
source: atom_id,
value: Rc::new(writable.initialize(self)),
value: Rc::new(new_val),
// value: Rc::new(writable.initialize(self)),
consumers: Default::default(),
dependents: Default::default(),
};
self.nodes.insert(atom_id, value);
&self.nodes.get(&atom_id).unwrap().consumers
nodes.insert(atom_id, value);
&nodes.get(&atom_id).unwrap().consumers
}
};
for (_, consumer_fn) in consumers {
for (id, consumer_fn) in consumers {
log::debug!("triggering selector {}", id);
consumer_fn();
}
// if it's a an atom or selector, update all the dependents
Ok(())
}
pub fn get<T: AtomValue>(&self, readable: impl Readable<T>) -> Rc<T> {
todo!()
// self.try_get(readable).unwrap()
}
pub fn set<T: AtomValue>(&mut self, writable: impl Writable<T>, new_val: T) {
self.try_set(writable, new_val).unwrap();
}
/// A slightly dangerous method to manually overwrite any slot given an AtomId
pub(crate) fn set_by_id<T: AtomValue>(&self, id: AtomId, new_val: T) {}
}
}
mod hooks {
use super::*;
use dioxus_core::prelude::Context;
use dioxus_core::{hooks::use_ref, prelude::Context};
pub fn use_init_recoil_root(ctx: Context, cfg: impl Fn(())) {
ctx.use_create_context(move || RecoilRoot::new())
ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
}
/// Gain access to the recoil API directly - set, get, modify, everything
@@ -353,25 +381,28 @@ mod hooks {
///
/// You can use this method to create controllers that perform much more complex actions than set/get
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
pub fn use_recoil_api<'a, F: 'a>(
ctx: Context<'a>,
f: impl Fn(Rc<RecoilRoot>) -> F + 'static,
) -> &F {
let g = ctx.use_context::<RecoilContext>();
let api = g.inner.clone();
todo!()
pub fn use_recoil_api<'a>(ctx: Context<'a>) -> &Rc<RecoilContext> {
ctx.use_context::<RecoilContext>()
}
pub fn use_write<'a, T: AtomValue>(
ctx: Context<'a>,
// todo: this shouldn't need to be static
writable: impl Writable<T>,
) -> &'a Rc<dyn Fn(T)> {
let api = use_recoil_api(ctx, |f| f);
let api = use_recoil_api(ctx);
ctx.use_hook(
move || {
let api = api.clone();
let raw_id = writable.static_id();
Rc::new(move |new_val| api.set_by_id(raw_id, new_val)) as Rc<dyn Fn(T)>
Rc::new(move |new_val| {
//
log::debug!("setting new value ");
let mut api = api.as_ref().borrow_mut();
// api.try_set(writable, new_val).expect("failed to set");
api.set_by_id(raw_id, new_val);
}) as Rc<dyn Fn(T)>
},
move |hook| &*hook,
|hook| {},
@@ -387,9 +418,11 @@ mod hooks {
consumer_id: u32,
}
let api = use_recoil_api(ctx, |api| api);
let api = use_recoil_api(ctx);
ctx.use_hook(
move || {
let mut api = api.as_ref().borrow_mut();
let update = ctx.schedule_update();
let val = api.try_get_raw(readable).unwrap();
let id = api.subscribe(readable, Rc::new(update));
@@ -399,11 +432,14 @@ mod hooks {
}
},
move |hook| {
let api = api.as_ref().borrow();
let val = api.try_get_raw(readable).unwrap();
hook.value = val;
&hook.value
},
|hook| {
move |hook| {
let mut api = api.as_ref().borrow_mut();
api.unsubscribe(hook.consumer_id);
},
)
@@ -414,12 +450,16 @@ mod hooks {
use_read_raw(ctx, readable).as_ref()
}
/// Use an atom in both read and write modes - only available for atoms and family selections (not selectors)
/// # Use an atom in both read and write
///
/// This method is only available for atoms and family selections (not selectors).
///
/// This is equivalent to calling both `use_read` and `use_write`, but saves you the hassle and repitition
///
/// ## Example
///
/// ```
/// const Title: Atom<&str> = |_| "hello";
/// //...
/// let (title, set_title) = use_read_write(ctx, &Title);
///
/// // equivalent to:
@@ -432,13 +472,40 @@ mod hooks {
(use_read(ctx, writable), use_write(ctx, writable))
}
/// # Modify an atom without using `use_read`.
///
/// Occasionally, a component might want to write to an atom without subscribing to its changes. `use_write` does not
/// provide this functionality, so `use_modify` exists to gain access to the current atom value while setting it.
///
/// ## Notes
///
/// Do note that this hook can only be used with Atoms where T: Clone since we actually clone the current atom to make
/// it mutable.
///
/// Also note that you need to stack-borrow the closure since the modify closure expects an &dyn Fn. If we made it
/// a static type, it wouldn't be possible to use the `modify` closure more than once (two closures always are different)
///
/// ## Example
///
/// ```ignore
/// let modify_atom = use_modify(ctx, Atom);
///
/// modify_atom(&|a| *a += 1)
/// ```
pub fn use_modify<'a, T: AtomValue + 'static + Clone>(
ctx: Context<'a>,
writable: impl Writable<T>,
) -> impl Fn(&dyn Fn()) {
|_| {}
}
/// Use a family collection directly
/// !! Any changes to the family will cause this subscriber to update
/// Try not to put this at the very top-level of your app.
pub fn use_read_family<'a, K, V, C: FamilyCollection<K, V>>(
pub fn use_read_family<'a, K, V>(
ctx: Context<'a>,
t: &AtomFamily<K, V, C>,
) -> &'a C {
t: &AtomHashMap<K, V>,
) -> &'a im_rc::HashMap<K, V> {
todo!()
}
}
+29
View File
@@ -0,0 +1,29 @@
//! Tracing immap
//! Traces modifications since last generation
//! To reconstruct the history, you will need *all* the generations between the start and end points
use im_rc::HashMap as ImMap;
pub struct TracedHashMap<K, V> {
inner: ImMap<K, V>,
generation: u32,
mods_since_last_gen: Vec<K>,
}
impl<K: Clone, V: Clone> TracedHashMap<K, V> {
fn next_generation(&self) -> Self {
Self {
generation: self.generation + 1,
inner: self.inner.clone(),
mods_since_last_gen: vec![],
}
}
}
#[test]
fn compare_dos() {
let map1 = im_rc::hashmap! {3 => 2, 2 => 3};
let map2 = im_rc::hashmap! {2 => 3, 3 => 2};
assert_eq!(map1, map2);
}