Jonathan Kelley преди 3 години
родител
ревизия
d461ffc011

+ 3 - 15
Cargo.toml

@@ -15,6 +15,7 @@ dioxus-core = { path = "./packages/core", version = "^0.1.9" }
 dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
 dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
 dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
+fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true }
 
 dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
 dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
@@ -36,20 +37,6 @@ web = ["dioxus-web"]
 desktop = ["dioxus-desktop"]
 router = ["dioxus-router"]
 
-devtool = ["dioxus-desktop/devtool"]
-fullscreen = ["dioxus-desktop/fullscreen"]
-transparent = ["dioxus-desktop/transparent"]
-
-tray = ["dioxus-desktop/tray"]
-ayatana = ["dioxus-desktop/ayatana"]
-
-# "dioxus-router/web"
-# "dioxus-router/desktop"
-# desktop = ["dioxus-desktop", "dioxus-router/desktop"]
-# mobile = ["dioxus-mobile"]
-# liveview = ["dioxus-liveview"]
-
-
 [workspace]
 members = [
     "packages/core",
@@ -61,6 +48,7 @@ members = [
     "packages/desktop",
     "packages/mobile",
     "packages/interpreter",
+    "packages/fermi",
 ]
 
 [dev-dependencies]
@@ -75,4 +63,4 @@ serde_json = "1.0.79"
 rand = { version = "0.8.4", features = ["small_rng"] }
 tokio = { version = "1.16.1", features = ["full"] }
 reqwest = { version = "0.11.9", features = ["json"] }
-dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
+dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi"] }

+ 1 - 0
docs/fermi/.gitignore

@@ -0,0 +1 @@
+book

+ 6 - 0
docs/fermi/book.toml

@@ -0,0 +1,6 @@
+[book]
+authors = ["Jonathan Kelley"]
+language = "en"
+multilingual = false
+src = "src"
+title = "Fermi Guide"

+ 3 - 0
docs/fermi/src/SUMMARY.md

@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)

+ 1 - 0
docs/fermi/src/chapter_1.md

@@ -0,0 +1 @@
+# Chapter 1

+ 30 - 0
examples/fermi.rs

@@ -0,0 +1,30 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use fermi::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app)
+}
+
+static NAME: Atom<String> = |_| "world".to_string();
+
+fn app(cx: Scope) -> Element {
+    let name = use_read(&cx, NAME);
+
+    cx.render(rsx! {
+        div { "hello {name}!" }
+        Child {}
+    })
+}
+
+fn Child(cx: Scope) -> Element {
+    let set_name = use_set(&cx, NAME);
+
+    cx.render(rsx! {
+        button {
+            onclick: move |_| set_name("dioxus".to_string()),
+            "reset name"
+        }
+    })
+}

+ 14 - 0
packages/fermi/Cargo.toml

@@ -0,0 +1,14 @@
+[package]
+name = "fermi"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus-core = { path = "../core" }
+im-rc = { version = "15.0.0", features = ["serde"] }
+log = "0.4.14"
+
+[dev-dependencies]
+closure = "0.3.0"

+ 92 - 0
packages/fermi/README.md

@@ -0,0 +1,92 @@
+
+<div align="center">
+  <h1>Fermi ⚛</h1>
+  <p>
+    <strong>Atom-based global state management solution for Dioxus</strong>
+  </p>
+</div>
+
+
+<div align="center">
+  <!-- Crates version -->
+  <a href="https://crates.io/crates/dioxus">
+    <img src="https://img.shields.io/crates/v/dioxus.svg?style=flat-square"
+    alt="Crates.io version" />
+  </a>
+  <!-- Downloads -->
+  <a href="https://crates.io/crates/dioxus">
+    <img src="https://img.shields.io/crates/d/dioxus.svg?style=flat-square"
+      alt="Download" />
+  </a>
+  <!-- docs -->
+  <a href="https://docs.rs/dioxus">
+    <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
+      alt="docs.rs docs" />
+  </a>
+  <!-- CI -->
+  <a href="https://github.com/jkelleyrtp/dioxus/actions">
+    <img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
+      alt="CI status" />
+  </a>
+</div>
+
+-----
+
+Fermi is a global state management solution for Dioxus that's as easy as `use_state`.
+
+Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
+
+```rust
+static NAME: Atom<&str> = |_| "Dioxus";
+```
+
+From anywhere in our app, we can read our the value of our atom:
+
+```rust
+fn NameCard(cx: Scope) -> Element {      
+    let name = use_read(&cx, NAME);
+    cx.render(rsx!{ h1 { "Hello, {name}"} })
+}
+```
+
+We can also set the value of our atom, also from anywhere in our app:
+
+```rust
+fn NameCard(cx: Scope) -> Element {      
+    let set_name = use_set(&cx, NAME);
+    cx.render(rsx!{
+        button {
+            onclick: move |_| set_name("Fermi"),
+            "Set name to fermi"
+        }
+    })
+}
+```
+
+It's that simple!
+
+## Installation
+Fermi is currently under construction, so you have to use the `master` branch to get started.
+
+```rust
+[depdencies]
+fermi = { git = "https://github.com/dioxuslabs/fermi" }
+```
+
+
+## Running examples
+
+The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
+```
+$ cargo run --example EXAMPLE
+```
+
+## Features
+
+Broadly our feature set to required to be released includes:
+- [x] Support for Atoms
+- [x] Support for AtomRef (for values that aren't clone)
+- [ ] Support for Atom Families
+- [ ] Support for memoized Selectors
+- [ ] Support for memoized SelectorFamilies
+- [ ] Support for UseFermiCallback for access to fermi from async 

+ 28 - 0
packages/fermi/src/atoms/atom.rs

@@ -0,0 +1,28 @@
+use crate::{AtomId, AtomRoot, Readable, Writable};
+
+pub type Atom<T> = fn(AtomBuilder) -> T;
+pub struct AtomBuilder;
+
+impl<V> Readable<V> for Atom<V> {
+    fn read(&self, _root: AtomRoot) -> Option<V> {
+        todo!()
+    }
+    fn init(&self) -> V {
+        (*self)(AtomBuilder)
+    }
+    fn unique_id(&self) -> AtomId {
+        *self as *const ()
+    }
+}
+
+impl<V> Writable<V> for Atom<V> {
+    fn write(&self, _root: AtomRoot, _value: V) {
+        todo!()
+    }
+}
+
+#[test]
+fn atom_compiles() {
+    static TEST_ATOM: Atom<&str> = |_| "hello";
+    dbg!(TEST_ATOM.init());
+}

+ 25 - 0
packages/fermi/src/atoms/atomfamily.rs

@@ -0,0 +1,25 @@
+use crate::{AtomId, AtomRoot, Readable, Writable};
+use im_rc::HashMap as ImMap;
+
+pub struct AtomFamilyBuilder;
+pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
+
+impl<K, V> Readable<ImMap<K, V>> for AtomFamily<K, V> {
+    fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
+        todo!()
+    }
+
+    fn init(&self) -> ImMap<K, V> {
+        (*self)(AtomFamilyBuilder)
+    }
+
+    fn unique_id(&self) -> AtomId {
+        *self as *const ()
+    }
+}
+
+impl<K, V> Writable<ImMap<K, V>> for AtomFamily<K, V> {
+    fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
+        todo!()
+    }
+}

+ 25 - 0
packages/fermi/src/atoms/atomref.rs

@@ -0,0 +1,25 @@
+use crate::{AtomId, AtomRoot, Readable};
+use std::cell::RefCell;
+
+pub struct AtomRefBuilder;
+pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
+
+impl<V> Readable<RefCell<V>> for AtomRef<V> {
+    fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
+        todo!()
+    }
+
+    fn init(&self) -> RefCell<V> {
+        RefCell::new((*self)(AtomRefBuilder))
+    }
+
+    fn unique_id(&self) -> AtomId {
+        *self as *const ()
+    }
+}
+
+#[test]
+fn atom_compiles() {
+    static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
+    dbg!(TEST_ATOM.init());
+}

+ 0 - 0
packages/fermi/src/atoms/selector.rs


+ 0 - 0
packages/fermi/src/atoms/selectorfamily.rs


+ 51 - 0
packages/fermi/src/callback.rs

@@ -0,0 +1,51 @@
+use std::{future::Future, rc::Rc};
+
+use dioxus_core::prelude::*;
+
+use crate::{Atom, AtomRoot, Readable, Writable};
+
+#[derive(Clone)]
+pub struct CallbackApi {
+    root: Rc<AtomRoot>,
+}
+
+impl CallbackApi {
+    // get the current value of the atom
+    pub fn get<V>(&self, atom: impl Readable<V>) -> &V {
+        todo!()
+    }
+
+    // get the current value of the atom in its RC container
+    pub fn get_rc<V>(&self, atom: impl Readable<V>) -> &Rc<V> {
+        todo!()
+    }
+
+    // set the current value of the atom
+    pub fn set<V>(&self, atom: impl Writable<V>, value: V) {
+        todo!()
+    }
+}
+
+pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi {
+    todo!()
+}
+
+macro_rules! use_callback {
+    (&$cx:ident, [$($cap:ident),*],  move || $body:expr) => {
+        move || {
+            $(
+                #[allow(unused_mut)]
+                let mut $cap = $cap.to_owned();
+            )*
+            $cx.spawn($body);
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! to_owned {
+    ($($es:ident),+) => {$(
+        #[allow(unused_mut)]
+        let mut $es = $es.to_owned();
+    )*}
+}

+ 62 - 0
packages/fermi/src/hooks/atom_ref.rs

@@ -0,0 +1,62 @@
+use crate::{use_atom_root, AtomId, AtomRef, AtomRoot, Readable};
+use dioxus_core::{ScopeId, ScopeState};
+use std::{
+    cell::{Ref, RefCell, RefMut},
+    rc::Rc,
+};
+
+///
+///
+///
+///
+///
+///
+///
+///
+pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
+    let root = use_atom_root(cx);
+
+    cx.use_hook(|_| {
+        root.initialize(atom);
+        UseAtomRef {
+            ptr: atom.unique_id(),
+            root: root.clone(),
+            scope_id: cx.scope_id(),
+            value: root.register(atom, cx.scope_id()),
+        }
+    })
+}
+
+pub struct UseAtomRef<T> {
+    ptr: AtomId,
+    value: Rc<RefCell<T>>,
+    root: Rc<AtomRoot>,
+    scope_id: ScopeId,
+}
+
+impl<T: 'static> UseAtomRef<T> {
+    pub fn read(&self) -> Ref<T> {
+        self.value.borrow()
+    }
+
+    pub fn write(&self) -> RefMut<T> {
+        self.root.force_update(self.ptr);
+        self.value.borrow_mut()
+    }
+
+    pub fn write_silent(&self) -> RefMut<T> {
+        self.root.force_update(self.ptr);
+        self.value.borrow_mut()
+    }
+
+    pub fn set(&self, new: T) {
+        self.root.force_update(self.ptr);
+        self.root.set(self.ptr, new);
+    }
+}
+
+impl<T> Drop for UseAtomRef<T> {
+    fn drop(&mut self) {
+        self.root.unsubscribe(self.ptr, self.scope_id)
+    }
+}

+ 11 - 0
packages/fermi/src/hooks/atom_root.rs

@@ -0,0 +1,11 @@
+use crate::AtomRoot;
+use dioxus_core::ScopeState;
+use std::rc::Rc;
+
+// Returns the atom root, initiaizing it at the root of the app if it does not exist.
+pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
+    cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
+        Some(root) => root,
+        None => cx.provide_root_context(AtomRoot::new(cx.schedule_update_any())),
+    })
+}

+ 11 - 0
packages/fermi/src/hooks/init_atom_root.rs

@@ -0,0 +1,11 @@
+use crate::AtomRoot;
+use dioxus_core::ScopeState;
+use std::rc::Rc;
+
+// Initializes the atom root and retuns it;
+pub fn use_init_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
+    cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
+        Some(ctx) => ctx,
+        None => cx.provide_context(AtomRoot::new(cx.schedule_update_any())),
+    })
+}

+ 36 - 0
packages/fermi/src/hooks/read.rs

@@ -0,0 +1,36 @@
+use crate::{use_atom_root, AtomId, AtomRoot, Readable};
+use dioxus_core::{ScopeId, ScopeState};
+use std::rc::Rc;
+
+pub fn use_read<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a V {
+    use_read_rc(cx, f).as_ref()
+}
+
+pub fn use_read_rc<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a Rc<V> {
+    let root = use_atom_root(cx);
+
+    struct UseReadInner<V> {
+        root: Rc<AtomRoot>,
+        id: AtomId,
+        scope_id: ScopeId,
+        value: Option<Rc<V>>,
+    }
+
+    impl<V> Drop for UseReadInner<V> {
+        fn drop(&mut self) {
+            self.root.unsubscribe(self.id, self.scope_id)
+        }
+    }
+
+    let inner = cx.use_hook(|_| UseReadInner {
+        value: None,
+        root: root.clone(),
+        scope_id: cx.scope_id(),
+        id: f.unique_id(),
+    });
+
+    let value = inner.root.register(f, cx.scope_id());
+
+    inner.value = Some(value);
+    inner.value.as_ref().unwrap()
+}

+ 13 - 0
packages/fermi/src/hooks/set.rs

@@ -0,0 +1,13 @@
+use crate::{use_atom_root, Writable};
+use dioxus_core::ScopeState;
+use std::rc::Rc;
+
+pub fn use_set<'a, T: 'static>(cx: &'a ScopeState, f: impl Writable<T>) -> &'a Rc<dyn Fn(T)> {
+    let root = use_atom_root(cx);
+    cx.use_hook(|_| {
+        let id = f.unique_id();
+        let root = root.clone();
+        root.initialize(f);
+        Rc::new(move |new| root.set(id, new)) as Rc<dyn Fn(T)>
+    })
+}

+ 59 - 0
packages/fermi/src/lib.rs

@@ -0,0 +1,59 @@
+#![doc = include_str!("../README.md")]
+
+pub mod prelude {
+    pub use crate::*;
+}
+
+mod root;
+mod callback;
+
+
+pub use atoms::*;
+pub use hooks::*;
+pub use root::*;
+pub use callback::*;
+
+mod atoms {
+    mod atom;
+    mod atomfamily;
+    mod atomref;
+    mod selector;
+    mod selectorfamily;
+
+    pub use atom::*;
+    pub use atomfamily::*;
+    pub use atomref::*;
+    pub use selector::*;
+    pub use selectorfamily::*;
+}
+
+pub mod hooks {
+    mod atom_ref;
+    mod atom_root;
+    mod init_atom_root;
+    mod read;
+    mod set;
+    pub use atom_ref::*;
+    pub use atom_root::*;
+    pub use init_atom_root::*;
+    pub use read::*;
+    pub use set::*;
+}
+
+/// All Atoms are `Readable` - they support reading their value.
+///
+/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors.
+/// It is not very useful for your own code, but could be used to build new Atom primitives.
+pub trait Readable<V> {
+    fn read(&self, root: AtomRoot) -> Option<V>;
+    fn init(&self) -> V;
+    fn unique_id(&self) -> AtomId;
+}
+
+/// All Atoms are `Writable` - they support writing their value.
+///
+/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors.
+/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors
+pub trait Writable<V>: Readable<V> {
+    fn write(&self, root: AtomRoot, value: V);
+}

+ 103 - 0
packages/fermi/src/root.rs

@@ -0,0 +1,103 @@
+use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc};
+
+use dioxus_core::ScopeId;
+use im_rc::HashSet;
+
+use crate::Readable;
+
+pub type AtomId = *const ();
+
+pub struct AtomRoot {
+    pub atoms: RefCell<HashMap<AtomId, Slot>>,
+    pub update_any: Rc<dyn Fn(ScopeId)>,
+}
+
+pub struct Slot {
+    pub value: Rc<dyn Any>,
+    pub subscribers: HashSet<ScopeId>,
+}
+
+impl AtomRoot {
+    pub fn new(update_any: Rc<dyn Fn(ScopeId)>) -> Self {
+        Self {
+            update_any,
+            atoms: RefCell::new(HashMap::new()),
+        }
+    }
+
+    pub fn initialize<V: 'static>(&self, f: impl Readable<V>) {
+        let id = f.unique_id();
+        if self.atoms.borrow().get(&id).is_none() {
+            self.atoms.borrow_mut().insert(
+                id,
+                Slot {
+                    value: Rc::new(f.init()),
+                    subscribers: HashSet::new(),
+                },
+            );
+        }
+    }
+
+    pub fn register<V: 'static>(&self, f: impl Readable<V>, scope: ScopeId) -> Rc<V> {
+        log::trace!("registering atom {:?}", f.unique_id());
+
+        let mut atoms = self.atoms.borrow_mut();
+
+        // initialize the value if it's not already initialized
+        if let Some(slot) = atoms.get_mut(&f.unique_id()) {
+            slot.subscribers.insert(scope);
+            slot.value.clone().downcast().unwrap()
+        } else {
+            let value = Rc::new(f.init());
+            let mut subscribers = HashSet::new();
+            subscribers.insert(scope);
+
+            atoms.insert(
+                f.unique_id(),
+                Slot {
+                    value: value.clone(),
+                    subscribers,
+                },
+            );
+            value
+        }
+    }
+
+    pub fn set<V: 'static>(&self, ptr: AtomId, value: V) {
+        let mut atoms = self.atoms.borrow_mut();
+
+        if let Some(slot) = atoms.get_mut(&ptr) {
+            slot.value = Rc::new(value);
+            log::trace!("found item with subscribers {:?}", slot.subscribers);
+
+            for scope in &slot.subscribers {
+                log::trace!("updating subcsriber");
+                (self.update_any)(*scope);
+            }
+        } else {
+            log::trace!("no atoms found for {:?}", ptr);
+        }
+    }
+
+    pub fn unsubscribe(&self, ptr: AtomId, scope: ScopeId) {
+        let mut atoms = self.atoms.borrow_mut();
+
+        if let Some(slot) = atoms.get_mut(&ptr) {
+            slot.subscribers.remove(&scope);
+        }
+    }
+
+    // force update of all subscribers
+    pub fn force_update(&self, ptr: AtomId) {
+        if let Some(slot) = self.atoms.borrow_mut().get(&ptr) {
+            for scope in slot.subscribers.iter() {
+                log::trace!("updating subcsriber");
+                (self.update_any)(*scope);
+            }
+        }
+    }
+
+    pub fn read<V>(&self, _f: impl Readable<V>) -> &V {
+        todo!()
+    }
+}

+ 3 - 0
src/lib.rs

@@ -340,6 +340,9 @@ pub use dioxus_web as web;
 #[cfg(feature = "desktop")]
 pub use dioxus_desktop as desktop;
 
+#[cfg(feature = "fermi")]
+pub use fermi;
+
 // #[cfg(feature = "mobile")]
 // pub use dioxus_mobile as mobile;
 

+ 53 - 0
tests/fermi.rs

@@ -0,0 +1,53 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use fermi::*;
+
+#[test]
+fn test_fermi() {
+    let mut app = VirtualDom::new(App);
+    app.rebuild();
+}
+
+static TITLE: Atom<String> = |_| "".to_string();
+static USERS: AtomFamily<u32, String> = |_| Default::default();
+
+fn App(cx: Scope) -> Element {
+    cx.render(rsx!(
+        Leaf { id: 0 }
+        Leaf { id: 1 }
+        Leaf { id: 2 }
+    ))
+}
+
+#[derive(Debug, PartialEq, Props)]
+struct LeafProps {
+    id: u32,
+}
+
+fn Leaf(cx: Scope<LeafProps>) -> Element {
+    let user = use_read(&cx, TITLE);
+    let user = use_read(&cx, USERS);
+
+    use_coroutine(&cx, || {
+        //
+        async move {
+            //
+        }
+    });
+
+    rsx!(cx, div {
+        button {
+            onclick: move |_| {},
+            "Start"
+        }
+        button {
+            onclick: move |_| {},
+            "Stop"
+        }
+        button {
+            onclick: move |_| {},
+            "Reverse"
+        }
+    })
+}