wip: recoil

This commit is contained in:
Jonathan Kelley committed 2021-05-26 01:40:30 -04:00
1 parent 3cfa1fe125
commit ee67654f58
31 files changed
+1369 -235

No files matched your search

+26
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) {}
}
+32
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!()
}
}
+26
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 {}
+110
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!()
}
+15
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
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
}
+25
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>;
+13
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;
}