diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 718d3b64d..caa5af54c 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -1,4 +1,4 @@ -use crate::{nodebuilder::IntoDomTree, prelude::*}; +use crate::{nodebuilder::IntoDomTree, prelude::*, scope::Scope}; use crate::{nodebuilder::LazyNodes, nodes::VNode}; use bumpalo::Bump; use hooks::Hook; @@ -27,15 +27,12 @@ use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::ato pub struct Context<'src> { pub idx: RefCell, - pub scope: ScopeIdx, - - // Borrowed from scope - // pub(crate) arena: &'src typed_arena::Arena, - pub(crate) hooks: &'src RefCell>, - // pub(crate) hooks: &'src RefCell>, - pub(crate) bump: &'src Bump, - - pub listeners: &'src RefCell>, + // pub scope: ScopeIdx, + pub scope: &'src Scope, + // // Borrowed from scope + // pub(crate) hooks: &'src RefCell>, + // pub(crate) bump: &'src Bump, + // pub listeners: &'src RefCell>, // holder for the src lifetime // todo @jon remove this @@ -101,10 +98,10 @@ impl<'a> Context<'a> { lazy_nodes: LazyNodes<'a, F>, ) -> DomTree { let ctx = NodeCtx { - bump: self.bump, - scope: self.scope, + bump: &self.scope.cur_frame().bump, + scope: self.scope.myidx, idx: 0.into(), - listeners: self.listeners, + listeners: &self.scope.listeners, }; let safe_nodes = lazy_nodes.into_vnode(&ctx); @@ -145,7 +142,7 @@ pub mod hooks { let idx = *self.idx.borrow(); // Mutate hook list if necessary - let mut hooks = self.hooks.borrow_mut(); + let mut hooks = self.scope.hooks.borrow_mut(); // Initialize the hook by allocating it in the typed arena. // We get a reference from the arena which is owned by the component scope diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index c5e4e6c63..5dcde5a2e 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -44,7 +44,7 @@ pub struct Scope { // - is self-refenrential and therefore needs to point into the bump // Stores references into the listeners attached to the vnodes // NEEDS TO BE PRIVATE - listeners: RefCell>, + pub(crate) listeners: RefCell>, } impl Scope { @@ -85,21 +85,26 @@ impl Scope { /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) pub fn run_scope<'b>(&'b mut self) -> Result<()> { // pub fn run_scope<'bump>(&'bump mut self) -> Result<()> { - let frame = { + // let frame = { + { let frame = self.frames.next(); frame.bump.reset(); - frame - }; + } + // frame + // }; + // self.new_frame() - let ctx: Context<'b> = Context { + let ctx = Context { // arena: &self.hook_arena, - hooks: &self.hooks, - bump: &frame.bump, + // hooks: &self.hooks, + // bump: &frame.bump, idx: 0.into(), _p: PhantomData {}, - scope: self.myidx, - listeners: &self.listeners, + scope: self, + // scope: self.myidx, + // listeners: &self.listeners, }; + let caller = self.caller.upgrade().expect("Failed to get caller"); /* @@ -114,12 +119,16 @@ impl Scope { - Public API cannot drop or destructure VNode */ - frame.head_node = unsafe { - let caller2: Rc> = std::mem::transmute(caller); - let tree = (caller2.as_ref())(ctx); - tree.root + let new_head = unsafe { + // frame.head_node = unsafe { + // // use the same type, just manipulate the lifetime + type ComComp<'c> = Rc>; + let caller = std::mem::transmute::, ComComp<'b>>(caller); + let r: DomTree = (caller.as_ref())(ctx); + r }; + self.frames.cur_frame_mut().head_node = new_head.root; Ok(()) } @@ -161,6 +170,10 @@ impl Scope { pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.prev_head_node() } + + pub fn cur_frame(&self) -> &BumpFrame { + self.frames.cur_frame() + } } // ========================== @@ -203,6 +216,19 @@ impl ActiveFrame { } } + fn cur_frame(&self) -> &BumpFrame { + match *self.idx.borrow() & 1 == 0 { + true => &self.frames[0], + false => &self.frames[1], + } + } + fn cur_frame_mut(&mut self) -> &mut BumpFrame { + match *self.idx.borrow() & 1 == 0 { + true => &mut self.frames[0], + false => &mut self.frames[1], + } + } + pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { let raw_node = match *self.idx.borrow() & 1 == 0 { true => &self.frames[0], diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a328d3a41..a0cc77c78 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -27,7 +27,7 @@ pub struct VirtualDom { /// Will not be ready if the dom is fresh pub base_scope: ScopeIdx, - pub(crate) update_schedule: Rc>>, + pub(crate) update_schedule: UpdateFunnel, // a strong allocation to the "caller" for the original props #[doc(hidden)] @@ -74,7 +74,7 @@ impl VirtualDom { components, _root_caller: root_caller, base_scope, - update_schedule: Rc::new(RefCell::new(Vec::new())), + update_schedule: UpdateFunnel::default(), _root_prop_type: TypeId::of::

(), } } @@ -211,5 +211,21 @@ impl VirtualDom { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HierarchyMarker { - height: u32, + source: ScopeIdx, +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct UpdateFunnel(Rc>>); + +impl UpdateFunnel { + fn schedule_update(&self, source: ScopeIdx) -> impl Fn() { + let inner = self.clone(); + move || { + inner + .0 + .as_ref() + .borrow_mut() + .push(HierarchyMarker { source }) + } + } } diff --git a/packages/web/examples/todomvc.rs b/packages/web/examples/todomvc.rs deleted file mode 100644 index 916887ec9..000000000 --- a/packages/web/examples/todomvc.rs +++ /dev/null @@ -1,218 +0,0 @@ -use dioxus_core::prelude::*; -use dioxus_web::WebsysRenderer; -use recoil::{use_recoil_callback, RecoilContext}; -use uuid::Uuid; - -static TODOS: AtomFamily = atom_family(|_| {}); - -fn main() { - wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, props| { - let global_reducer = use_recoil_callback(|| ()); - - let todos = use_atom(TODOS).iter().map(|(order, item)| { - rsx!(TodoItem { - key: "{order}", - id: item.id, - }) - }); - - ctx.render(rsx! { - div { - {todos} - Footer {} - } - }) - })) -} - -#[derive(Debug, PartialEq, Clone)] -pub struct TodoItem { - pub id: Uuid, - pub checked: bool, - pub contents: String, -} - -// build a global context for the app -// as we scale the app, we can create separate, stateful impls -impl RecoilContext<()> { - fn add_todo(&self) {} - fn remove_todo(&self) {} - fn select_all_todos(&self) {} -} - -mod hooks { - use super::*; - fn use_keyboard_shortcuts(ctx: &Context) {} -} - -// The data model that the todo mvc uses -mod model { - use std::{borrow::BorrowMut, future::Future}; - - use super::*; - - #[derive(Debug, PartialEq, Clone)] - pub struct TodoItem { - pub id: Uuid, - pub checked: bool, - pub contents: String, - } - - fn atom() {} - - // struct Dispatcher {} - - struct AppContext { - _t: std::rc::Rc, - } - - // pub fn use_appcontext(ctx: &Context, f: impl FnOnce() -> T) -> AppContext { - // todo!() - // } - - // static TodoList: ContextFamily = context_family(); - - // struct TodoBoss<'a> {} - - // fn use_recoil_todos() -> TodoBoss {} - - // pub fn use_context_family(ctx: &Context) {} - - impl AppContext { - fn dispatch(&self, f: impl FnOnce(&mut T)) {} - fn async_dispatch(&self, f: impl Future) {} - fn get(&self, f: impl Fn(&T) -> &G) -> &G { - f(&self._t) - } - fn set(&self, orig: &mut std::borrow::Cow) { - let r = orig.to_mut(); - } - } - - // // use im-rc if your contexts are too large to clone! - // // or, dangerously mutate and update subscriptions manually - // #[derive(Clone, Debug, PartialEq)] - // pub struct TodoManager { - // items: Vec, - // } - - // // App context is an ergonomic way of sharing data models through a tall tree - // // Because it holds onto the source data with Rc, it's cheap to clone through props and allows advanced memoization - // // It's particularly useful when moving through tall trees, or iterating through complex data models. - // // By wrapping the source type, we can forward any mutation through "dispatch", making it clear when clones occur. - // // This also enables traditional method-style - // impl AppContext { - // fn get_todos(&self, ctx: &Context) {} - - // fn remove_todo(&self, id: Uuid) { - // self.dispatch(|f| { - // // todos... remove - // }) - // } - - // async fn push_todo(&self, todo: TodoItem) { - // self.dispatch(|f| { - // // - // f.items.push(10); - // }); - // } - - // fn add_todo(&self) { - // // self.dispatch(|f| {}); - // // let items = self.get(|f| &f.items); - // } - // } - - // pub enum TodoActions {} - // impl TodoManager { - // pub fn reduce(s: &mut Rc, action: TodoActions) { - // match action { - // _ => {} - // } - // } - - // pub fn new() -> Rc { - // todo!() - // } - - // pub fn get_todo(&self, id: Uuid) -> &TodoItem { - // todo!() - // } - - // pub fn get_todos(&self) -> &BTreeMap { - // todo!() - // } - // } - - // pub struct TodoHandle {} - // impl TodoHandle { - // fn get_todo(&self, id: Uuid) -> &TodoItem { - // todo!() - // } - - // fn add_todo(&self, todo: TodoItem) {} - // } - - // // use_reducer, but exposes the reducer and context to children - // fn use_reducer_context() {} - // fn use_context_selector() {} - - // fn use_context<'b, 'c, Root: 'static, Item: 'c>( - // ctx: &'b Context<'c>, - // f: impl Fn(Root) -> &'c Item, - // ) -> &'c Item { - // todo!() - // } - - // pub fn use_todo_item<'b, 'c>(ctx: &'b Context<'c>, item: Uuid) -> &'c TodoItem { - // todo!() - // // ctx.use_hook(|| TodoManager::new(), |hook| {}, cleanup) - // } - // fn use_todos(ctx: &Context) -> TodoHandle { - // todo!() - // } - - // fn use_todo_context(ctx: &Context) -> AppContext { - // todo!() - // } - - // fn test(ctx: Context) { - // let todos = use_todos(&ctx); - // let todo = todos.get_todo(Uuid::new_v4()); - - // let c = use_todo_context(&ctx); - // // todos.add_todo(); - // } -} - -mod recoil { - - pub struct RecoilContext { - _inner: T, - } - - impl RecoilContext { - /// Get the value of an atom. Returns a reference to the underlying data. - - pub fn get(&self) {} - - /// Replace an existing value with a new value - /// - /// This does not replace the value instantly, and all calls to "get" within the current scope will return - pub fn set(&self) {} - - // 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 we run the - pub fn modify(&self) {} - } - - pub fn use_recoil_callback(f: impl Fn() -> G) -> RecoilContext { - todo!() - } -} diff --git a/packages/web/examples/todomvc/recoil.rs b/packages/web/examples/todomvc/recoil.rs index e1067acd1..e2fe67754 100644 --- a/packages/web/examples/todomvc/recoil.rs +++ b/packages/web/examples/todomvc/recoil.rs @@ -93,8 +93,9 @@ mod selectors { } impl SelectorBuilder { pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder { - std::rc::Rc::pin(value) todo!() + // std::rc::Rc::pin(value) + // todo!() } } pub struct selector(pub fn(SelectorBuilder) -> SelectorBuilder); diff --git a/packages/web/examples/todomvc/state.rs b/packages/web/examples/todomvc/state.rs index 571cdb812..b7b86033c 100644 --- a/packages/web/examples/todomvc/state.rs +++ b/packages/web/examples/todomvc/state.rs @@ -1,13 +1,9 @@ use crate::recoil::*; -use dioxus_core::prelude::Context; pub static TODOS: AtomFamily = atom_family(|_| {}); pub static FILTER: Atom = atom(|_| FilterState::All); pub static SHOW_ALL_TODOS: selector = selector(|g| g.getter(|f| false)); -// an atomfamily is just a HashMap>> that pins the Rc and exposes the values by reference -// we could do a more advanced management, but this is fine too - #[derive(PartialEq)] pub enum FilterState { All, @@ -22,30 +18,26 @@ pub struct TodoItem { pub contents: String, } -pub fn add_todo(ctx: &Context, contents: String) {} +impl RecoilContext<()> { + pub fn add_todo(&self, contents: String) {} -pub fn remove_todo(ctx: &Context, id: &uuid::Uuid) { - TODOS.with(&ctx).remove(id) + pub fn remove_todo(&self, id: &uuid::Uuid) { + // TODOS.with().remove(id) + } + + pub fn select_all_todos(&self) {} + + pub fn toggle_todo(&self, id: &uuid::Uuid) {} + + pub fn clear_completed(&self) { + // let (set, get) = (self.set, self.get); + + // TOODS + // .get(&ctx) + // .iter() + // .filter(|(k, v)| v.checked) + // .map(|(k, v)| TODOS.remove(&ctx, k)); + } + + pub fn set_filter(&self, filter: &FilterState) {} } - -pub fn select_all_todos(ctx: &Context) {} - -pub fn toggle_todo(ctx: &Context, id: &uuid::Uuid) {} - -pub fn clear_completed(ctx: &Context) { - let (set, get) = (self.set, self.get); - - TOODS - .get(&ctx) - .iter() - .filter(|(k, v)| v.checked) - .map(|(k, v)| TODOS.remove(&ctx, k)); -} - -pub fn set_filter(ctx: &Context, filter: &FilterState) {} - -struct TodoManager<'a> {} -fn use_todos(ctx: &Context) {} - -#[test] -fn test() {} diff --git a/packages/web/examples/todomvc/todolist.rs b/packages/web/examples/todomvc/todolist.rs index 96841d85c..7966c523a 100644 --- a/packages/web/examples/todomvc/todolist.rs +++ b/packages/web/examples/todomvc/todolist.rs @@ -1,7 +1,9 @@ -use super::state::{FilterState, TodoItem, FILTER, TODOS}; -use crate::filtertoggles; -use crate::recoil::use_atom; -use crate::todoitem::TodoEntry; +use crate::{ + filtertoggles, + recoil::use_atom, + state::{FilterState, TodoItem, FILTER, TODOS}, + todoitem::TodoEntry, +}; use dioxus_core::prelude::*; pub fn TodoList(ctx: Context, props: &()) -> DomTree { @@ -9,20 +11,6 @@ pub fn TodoList(ctx: Context, props: &()) -> DomTree { let (todos, _) = use_state(&ctx, || Vec::::new()); let filter = use_atom(&ctx, &FILTER); - let list = todos - .iter() - .filter(|item| match filter { - FilterState::All => true, - FilterState::Active => !item.checked, - FilterState::Completed => item.checked, - }) - .map(|item| { - rsx!(TodoEntry { - key: "{order}", - id: item.id, - }) - }); - ctx.render(rsx! { div { header { @@ -36,7 +24,21 @@ pub fn TodoList(ctx: Context, props: &()) -> DomTree { } } - {list} + { // list + todos + .iter() + .filter(|item| match filter { + FilterState::All => true, + FilterState::Active => !item.checked, + FilterState::Completed => item.checked, + }) + .map(|item| { + rsx!(TodoEntry { + key: "{order}", + id: item.id, + }) + }) + } // filter toggle (show only if the list isn't empty) {(!todos.is_empty()).then(|| diff --git a/packages/web/examples/todomvc_simple.rs b/packages/web/examples/todomvc_simple.rs new file mode 100644 index 000000000..053a81348 --- /dev/null +++ b/packages/web/examples/todomvc_simple.rs @@ -0,0 +1,159 @@ +use std::{collections::HashMap, rc::Rc}; + +use dioxus_core::prelude::*; +use dioxus_web::WebsysRenderer; + +static APP_STYLE: &'static str = include_str!("./todomvc/style.css"); + +fn main() { + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} + +#[derive(PartialEq)] +pub enum FilterState { + All, + Active, + Completed, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct TodoItem { + pub id: uuid::Uuid, + pub checked: bool, + pub contents: String, +} + +// ======================= +// Components +// ======================= +pub fn App(ctx: Context, props: &()) -> DomTree { + ctx.render(rsx! { + div { + id: "app" + style { "{APP_STYLE}" } + + // list + TodoList {} + + // footer + footer { + class: "info" + p {"Double-click to edit a todo"} + p { + "Created by " + a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" } + } + p { + "Part of " + a { "TodoMVC", href: "http://todomvc.com" } + } + } + } + }) +} + +pub fn TodoList(ctx: Context, props: &()) -> DomTree { + let (draft, set_draft) = use_state(&ctx, || "".to_string()); + let (todos, set_todos) = use_state(&ctx, || HashMap::>::new()); + let (filter, set_filter) = use_state(&ctx, || FilterState::All); + + ctx.render(rsx! { + div { + header { + class: "header" + h1 {"todos"} + input { + class: "new-todo" + placeholder: "What needs to be done?" + value: "{draft}" + oninput: move |evt| set_draft(evt.value) + } + } + + { // list + todos + .iter() + .filter(|(id, item)| match filter { + FilterState::All => true, + FilterState::Active => !item.checked, + FilterState::Completed => item.checked, + }) + .map(|(id, item)| { + rsx!(TodoEntry { + key: "{order}", + item: item.clone() + }) + }) + } + + // filter toggle (show only if the list isn't empty) + {(!todos.is_empty()).then(|| + rsx!( FilterToggles {}) + )} + } + }) +} + +#[derive(PartialEq, Props)] +pub struct TodoEntryProps { + item: Rc, +} + +pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree { + let (is_editing, set_is_editing) = use_state(&ctx, || false); + let todo = &props.item; + + ctx.render(rsx! ( + li { + "{todo.id}" + input { + class: "toggle" + type: "checkbox" + "{todo.checked}" + } + {is_editing.then(|| rsx!( + input { + value: "{contents}" + } + ))} + } + )) +} + +pub fn FilterToggles(ctx: Context, props: &()) -> DomTree { + let toggles = [ + ("All", "", FilterState::All), + ("Active", "active", FilterState::Active), + ("Completed", "completed", FilterState::Completed), + ] + .iter() + .map(|(name, path, filter)| { + rsx!( + li { + class: "{name}" + a { + href: "{path}" + // onclick: move |_| reducer.set_filter(&filter) + "{name}" + } + } + ) + }); + + // todo + let item_text = ""; + let items_left = ""; + + ctx.render(rsx! { + footer { + span { + strong {"{items_left}"} + span {"{item_text} left"} + } + ul { + class: "filters" + {toggles} + } + } + }) +} diff --git a/packages/web/examples/todomvcsingle.rs b/packages/web/examples/todomvcsingle.rs new file mode 100644 index 000000000..733e62dbc --- /dev/null +++ b/packages/web/examples/todomvcsingle.rs @@ -0,0 +1,281 @@ +use dioxus_core::prelude::*; +use dioxus_web::WebsysRenderer; + +static APP_STYLE: &'static str = include_str!("./todomvc/style.css"); + +fn main() { + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} +// ======================= +// state-related items +// ======================= +pub static TODOS: AtomFamily = atom_family(|_| {}); +pub static FILTER: Atom = atom(|_| FilterState::All); +pub static SHOW_ALL_TODOS: selector = selector(|g| g.getter(|f| false)); + +#[derive(PartialEq)] +pub enum FilterState { + All, + Active, + Completed, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct TodoItem { + pub id: uuid::Uuid, + pub checked: bool, + pub contents: String, +} + +impl RecoilContext<()> { + pub fn add_todo(&self, contents: String) {} + pub fn remove_todo(&self, id: &uuid::Uuid) {} + pub fn select_all_todos(&self) {} + pub fn toggle_todo(&self, id: &uuid::Uuid) {} + pub fn clear_completed(&self) {} + pub fn set_filter(&self, filter: &FilterState) {} +} + +// ======================= +// Components +// ======================= +pub fn App(ctx: Context, props: &()) -> DomTree { + ctx.render(rsx! { + div { + id: "app" + style { "{APP_STYLE}" } + + // list + TodoList {} + + // footer + footer { + class: "info" + p {"Double-click to edit a todo"} + p { + "Created by " + a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" } + } + p { + "Part of " + a { "TodoMVC", href: "http://todomvc.com" } + } + } + } + }) +} + +pub fn TodoList(ctx: Context, props: &()) -> DomTree { + let (draft, set_draft) = use_state(&ctx, || "".to_string()); + let (todos, _) = use_state(&ctx, || Vec::::new()); + let filter = use_atom(&ctx, &FILTER); + + ctx.render(rsx! { + div { + header { + class: "header" + h1 {"todos"} + input { + class: "new-todo" + placeholder: "What needs to be done?" + value: "{draft}" + oninput: move |evt| set_draft(evt.value) + } + } + + { // list + todos + .iter() + .filter(|item| match filter { + FilterState::All => true, + FilterState::Active => !item.checked, + FilterState::Completed => item.checked, + }) + .map(|item| { + rsx!(TodoEntry { + key: "{order}", + id: item.id, + }) + }) + } + + // filter toggle (show only if the list isn't empty) + {(!todos.is_empty()).then(|| + rsx!( FilterToggles {}) + )} + } + }) +} + +#[derive(PartialEq, Props)] +pub struct TodoEntryProps { + id: uuid::Uuid, +} + +pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree { + let (is_editing, set_is_editing) = use_state(&ctx, || false); + let todo = use_atom_family(&ctx, &TODOS, props.id); + + ctx.render(rsx! ( + li { + "{todo.id}" + input { + class: "toggle" + type: "checkbox" + "{todo.checked}" + } + {is_editing.then(|| rsx!( + input { + value: "{contents}" + } + ))} + } + )) +} + +pub fn FilterToggles(ctx: Context, props: &()) -> DomTree { + let reducer = recoil::use_callback(&ctx, || ()); + let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4()); + + let toggles = [ + ("All", "", FilterState::All), + ("Active", "active", FilterState::Active), + ("Completed", "completed", FilterState::Completed), + ] + .iter() + .map(|(name, path, filter)| { + rsx!( + li { + class: "{name}" + a { + href: "{path}" + onclick: move |_| reducer.set_filter(&filter) + "{name}" + } + } + ) + }); + + // todo + let item_text = ""; + let items_left = ""; + + ctx.render(rsx! { + footer { + span { + strong {"{items_left}"} + span {"{item_text} left"} + } + ul { + class: "filters" + {toggles} + } + } + }) +} + +pub use recoil::*; +mod recoil { + use dioxus_core::context::Context; + + pub struct RecoilContext { + _inner: T, + } + + impl RecoilContext { + /// Get the value of an atom. Returns a reference to the underlying data. + + pub fn get(&self) {} + + /// Replace an existing value with a new value + /// + /// This does not replace the value instantly, and all calls to "get" within the current scope will return + pub fn set(&self) {} + + // 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 we run the + pub fn modify(&self) {} + } + + pub fn use_callback<'a, G>(c: &Context<'a>, f: impl Fn() -> G) -> &'a RecoilContext { + todo!() + } + + pub fn use_atom(c: &Context, t: &'static Atom) -> O { + todo!() + } + pub fn use_batom(c: &Context, t: impl Readable) -> O { + todo!() + } + + pub trait Readable {} + impl Readable for &'static Atom {} + impl Readable for &'static AtomFamily {} + + pub fn use_atom_family<'a, K: PartialEq, V: PartialEq>( + c: &Context<'a>, + t: &'static AtomFamily, + g: K, + ) -> &'a V { + todo!() + } + + pub use atoms::{atom, Atom}; + pub use atoms::{atom_family, AtomFamily}; + mod atoms { + + use super::*; + pub struct AtomBuilder { + pub key: String, + pub manual_init: Option T>>, + _never: std::marker::PhantomData, + } + + impl AtomBuilder { + pub fn new() -> Self { + Self { + key: uuid::Uuid::new_v4().to_string(), + manual_init: None, + _never: std::marker::PhantomData {}, + } + } + + pub fn init T + 'static>(&mut self, f: A) { + self.manual_init = Some(Box::new(f)); + } + + pub fn set_key(&mut self, _key: &'static str) {} + } + + pub struct atom(pub fn(&mut AtomBuilder) -> T); + pub type Atom = atom; + + pub struct AtomFamilyBuilder { + _never: std::marker::PhantomData<(K, V)>, + } + + pub struct atom_family(pub fn(&mut AtomFamilyBuilder)); + pub type AtomFamily = atom_family; + } + + pub use selectors::selector; + mod selectors { + pub struct SelectorBuilder { + _p: std::marker::PhantomData, + } + impl SelectorBuilder { + pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder { + todo!() + // std::rc::Rc::pin(value) + // todo!() + } + } + pub struct selector(pub fn(SelectorBuilder) -> SelectorBuilder); + } +}