diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index b80a7b1f5..eef04d521 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -33,4 +33,6 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: actions/checkout@v3 - - run: cargo test --all --tests + - run: | + cargo test --all --tests + cargo test --package fermi --release diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0dd8a1842..368df1630 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -85,4 +85,5 @@ jobs: set RUST_BACKTRACE=1 cargo build --all --tests --examples cargo test --all --tests + cargo test --package fermi --release shell: cmd diff --git a/docs/guide/src/en/__unused/advanced-guides/12-signals.md b/docs/guide/src/en/__unused/advanced-guides/12-signals.md index df13b91d8..7fbb56de7 100644 --- a/docs/guide/src/en/__unused/advanced-guides/12-signals.md +++ b/docs/guide/src/en/__unused/advanced-guides/12-signals.md @@ -94,8 +94,10 @@ Calling `deref` or `deref_mut` is actually more complex than it seems. When a va Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom. + ```rust, no_run -const TITLE: Atom = || "".to_string(); +const TITLE: Atom = Atom(|| "".to_string()); + const Provider: Component = |cx|{ let title = use_signal(cx, &TITLE); render!(input { value: title }) @@ -131,7 +133,8 @@ By default, Dioxus is limited when you use iter/map. With the `For` component, y Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster. ```rust, no_run -const DICT: AtomFamily = |_| {}; +const DICT: AtomFamily = AtomFamily(|_| {}); + const List: Component = |cx|{ let dict = use_signal(cx, &DICT); cx.render(rsx!( @@ -142,14 +145,6 @@ const List: Component = |cx|{ }; ``` -## Remote Signals - -Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering. - -```rust, no_run - -``` - ## How does it work? Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree. diff --git a/docs/guide/src/en/async/use_coroutine.md b/docs/guide/src/en/async/use_coroutine.md index 8d4b8f99a..d42905be5 100644 --- a/docs/guide/src/en/async/use_coroutine.md +++ b/docs/guide/src/en/async/use_coroutine.md @@ -143,7 +143,7 @@ async fn editor_service(rx: UnboundedReceiver) { We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state _within_ a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your _actual_ state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI. ```rust, no_run -static USERNAME: Atom = |_| "default".to_string(); +static USERNAME: Atom = Atom(|_| "default".to_string()); fn app(cx: Scope) -> Element { let atoms = use_atom_root(cx); @@ -156,7 +156,7 @@ fn app(cx: Scope) -> Element { } fn Banner(cx: Scope) -> Element { - let username = use_read(cx, USERNAME); + let username = use_read(cx, &USERNAME); cx.render(rsx!{ h1 { "Welcome back, {username}" } @@ -174,8 +174,8 @@ enum SyncAction { } async fn sync_service(mut rx: UnboundedReceiver, atoms: AtomRoot) { - let username = atoms.write(USERNAME); - let errors = atoms.write(ERRORS); + let username = atoms.write(&USERNAME); + let errors = atoms.write(&ERRORS); while let Ok(msg) = rx.next().await { match msg { diff --git a/docs/guide/src/en/best_practices/error_handling.md b/docs/guide/src/en/best_practices/error_handling.md index fad8f1c10..d16d04347 100644 --- a/docs/guide/src/en/best_practices/error_handling.md +++ b/docs/guide/src/en/best_practices/error_handling.md @@ -113,14 +113,14 @@ enum InputError { TooShort, } -static INPUT_ERROR: Atom = |_| InputError::None; +static INPUT_ERROR: Atom = Atom(|_| InputError::None); ``` Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree. ```rust, no_run fn TopLevel(cx: Scope) -> Element { - let error = use_read(cx, INPUT_ERROR); + let error = use_read(cx, &INPUT_ERROR); match error { TooLong => return cx.render(rsx!{ "FAILED: Too long!" }), @@ -134,7 +134,7 @@ Now, whenever a downstream component has an error in its actions, it can simply ```rust, no_run fn Commandline(cx: Scope) -> Element { - let set_error = use_set(cx, INPUT_ERROR); + let set_error = use_set(cx, &INPUT_ERROR); cx.render(rsx!{ input { diff --git a/docs/guide/src/pt-br/async/use_coroutine.md b/docs/guide/src/pt-br/async/use_coroutine.md index d7edbd8cc..cb9eda3a0 100644 --- a/docs/guide/src/pt-br/async/use_coroutine.md +++ b/docs/guide/src/pt-br/async/use_coroutine.md @@ -105,7 +105,7 @@ async fn editor_service(rx: UnboundedReceiver) { Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` – os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface. ```rust, no_run -static USERNAME: Atom = |_| "default".to_string(); +static USERNAME: Atom = Atom(|_| "default".to_string()); fn app(cx: Scope) -> Element { let atoms = use_atom_root(cx); @@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element { } fn Banner(cx: Scope) -> Element { - let username = use_read(cx, USERNAME); + let username = use_read(cx, &USERNAME); cx.render(rsx!{ h1 { "Welcome back, {username}" } @@ -134,8 +134,8 @@ enum SyncAction { } async fn sync_service(mut rx: UnboundedReceiver, atoms: AtomRoot) { - let username = atoms.write(USERNAME); - let errors = atoms.write(ERRORS); + let username = atoms.write(&USERNAME); + let errors = atoms.write(&ERRORS); while let Ok(msg) = rx.next().await { match msg { diff --git a/docs/guide/src/pt-br/best_practices/error_handling.md b/docs/guide/src/pt-br/best_practices/error_handling.md index 7e027e9d0..2fec8d88d 100644 --- a/docs/guide/src/pt-br/best_practices/error_handling.md +++ b/docs/guide/src/pt-br/best_practices/error_handling.md @@ -113,14 +113,14 @@ enum InputError { TooShort, } -static INPUT_ERROR: Atom = |_| InputError::None; +static INPUT_ERROR: Atom = Atom(|_| InputError::None); ``` Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore. ```rust, no_run fn TopLevel(cx: Scope) -> Element { - let error = use_read(cx, INPUT_ERROR); + let error = use_read(cx, &INPUT_ERROR); match error { TooLong => return cx.render(rsx!{ "FAILED: Too long!" }), @@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele ```rust, no_run fn Commandline(cx: Scope) -> Element { - let set_error = use_set(cx, INPUT_ERROR); + let set_error = use_set(cx, &INPUT_ERROR); cx.render(rsx!{ input { diff --git a/examples/fermi.rs b/examples/fermi.rs index 001890892..a15fc807f 100644 --- a/examples/fermi.rs +++ b/examples/fermi.rs @@ -7,11 +7,11 @@ fn main() { dioxus_desktop::launch(app) } -static NAME: Atom = |_| "world".to_string(); +static NAME: Atom = Atom(|_| "world".to_string()); fn app(cx: Scope) -> Element { use_init_atom_root(cx); - let name = use_read(cx, NAME); + let name = use_read(cx, &NAME); cx.render(rsx! { div { "hello {name}!" } @@ -21,7 +21,7 @@ fn app(cx: Scope) -> Element { } fn Child(cx: Scope) -> Element { - let set_name = use_set(cx, NAME); + let set_name = use_set(cx, &NAME); cx.render(rsx! { button { @@ -31,10 +31,10 @@ fn Child(cx: Scope) -> Element { }) } -static NAMES: AtomRef> = |_| vec!["world".to_string()]; +static NAMES: AtomRef> = AtomRef(|_| vec!["world".to_string()]); fn ChildWithRef(cx: Scope) -> Element { - let names = use_atom_ref(cx, NAMES); + let names = use_atom_ref(cx, &NAMES); cx.render(rsx! { div { diff --git a/packages/fermi/README.md b/packages/fermi/README.md index f276dbff5..ff6635b0f 100644 --- a/packages/fermi/README.md +++ b/packages/fermi/README.md @@ -1,4 +1,3 @@ -

Fermi ⚛

@@ -6,7 +5,6 @@

- ------ +--- 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, ignore -static NAME: Atom<&str> = |_| "Dioxus"; +static NAME: Atom<&str> = Atom(|_| "Dioxus"); ``` From anywhere in our app, we can read the value of our atom: -```rust, ignores +```rust, ignore fn NameCard(cx: Scope) -> Element { - let name = use_read(cx, NAME); + let name = use_read(cx, &NAME); cx.render(rsx!{ h1 { "Hello, {name}"} }) } ``` @@ -53,7 +51,7 @@ We can also set the value of our atom, also from anywhere in our app: ```rust, ignore fn NameCard(cx: Scope) -> Element { - let set_name = use_set(cx, NAME); + let set_name = use_set(cx, &NAME); cx.render(rsx!{ button { onclick: move |_| set_name("Fermi"), @@ -66,10 +64,10 @@ fn NameCard(cx: Scope) -> Element { If needed, we can update the atom's value, based on itself: ```rust, ignore -static COUNT: Atom = |_| 0; +static COUNT: Atom = Atom(|_| 0); fn Counter(cx: Scope) -> Element { - let mut count = use_atom_state(cx, COUNT); + let mut count = use_atom_state(cx, &COUNT); cx.render(rsx!{ p { @@ -86,6 +84,7 @@ fn Counter(cx: Scope) -> Element { It's that simple! ## Installation + Fermi is currently under construction, so you have to use the `master` branch to get started. ```toml @@ -93,10 +92,10 @@ Fermi is currently under construction, so you have to use the `master` branch to fermi = { git = "https://github.com/dioxuslabs/dioxus" } ``` - ## Running examples The examples here use Dioxus Desktop to showcase their functionality. To run an example, use + ```sh $ cargo run --example fermi ``` @@ -104,6 +103,7 @@ $ cargo run --example fermi ## Features Broadly our feature set required to be released includes: + - [x] Support for Atoms - [x] Support for AtomRef (for values that aren't `Clone`) - [ ] Support for Atom Families diff --git a/packages/fermi/src/atoms/atom.rs b/packages/fermi/src/atoms/atom.rs index 81e04ce0b..bba1f532f 100644 --- a/packages/fermi/src/atoms/atom.rs +++ b/packages/fermi/src/atoms/atom.rs @@ -1,24 +1,21 @@ use crate::{AtomId, AtomRoot, Readable, Writable}; -pub type Atom = fn(AtomBuilder) -> T; +pub struct Atom(pub fn(AtomBuilder) -> T); pub struct AtomBuilder; -impl Readable for Atom { +impl Readable for &'static Atom { fn read(&self, _root: AtomRoot) -> Option { todo!() } fn init(&self) -> V { - (*self)(AtomBuilder) + self.0(AtomBuilder) } fn unique_id(&self) -> AtomId { - AtomId { - ptr: *self as *const (), - type_id: std::any::TypeId::of::(), - } + *self as *const Atom as *const () } } -impl Writable for Atom { +impl Writable for &'static Atom { fn write(&self, _root: AtomRoot, _value: V) { todo!() } @@ -26,6 +23,22 @@ impl Writable for Atom { #[test] fn atom_compiles() { - static TEST_ATOM: Atom<&str> = |_| "hello"; - dbg!(TEST_ATOM.init()); + static TEST_ATOM: Atom<&str> = Atom(|_| "hello"); + dbg!((&TEST_ATOM).init()); +} + +#[test] +fn atom_is_unique() { + static TEST_ATOM_1: Atom<&str> = Atom(|_| "hello"); + static TEST_ATOM_2: Atom<&str> = Atom(|_| "hello"); + assert_eq!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_1).unique_id()); + assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id()); +} + +#[test] +fn atom_is_unique_2() { + struct S(String); + static TEST_ATOM_1: Atom> = Atom(|_| Vec::new()); + static TEST_ATOM_2: Atom> = Atom(|_| Vec::new()); + assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id()); } diff --git a/packages/fermi/src/atoms/atomfamily.rs b/packages/fermi/src/atoms/atomfamily.rs index 29cecd4b2..45f6b8feb 100644 --- a/packages/fermi/src/atoms/atomfamily.rs +++ b/packages/fermi/src/atoms/atomfamily.rs @@ -2,26 +2,23 @@ use crate::{AtomId, AtomRoot, Readable, Writable}; use im_rc::HashMap as ImMap; pub struct AtomFamilyBuilder; -pub type AtomFamily = fn(AtomFamilyBuilder) -> ImMap; +pub struct AtomFamily(pub fn(AtomFamilyBuilder) -> ImMap); -impl Readable> for AtomFamily { +impl Readable> for &'static AtomFamily { fn read(&self, _root: AtomRoot) -> Option> { todo!() } fn init(&self) -> ImMap { - (*self)(AtomFamilyBuilder) + self.0(AtomFamilyBuilder) } fn unique_id(&self) -> AtomId { - AtomId { - ptr: *self as *const (), - type_id: std::any::TypeId::of::(), - } + *self as *const AtomFamily as *const () } } -impl Writable> for AtomFamily { +impl Writable> for &'static AtomFamily { fn write(&self, _root: AtomRoot, _value: ImMap) { todo!() } diff --git a/packages/fermi/src/atoms/atomref.rs b/packages/fermi/src/atoms/atomref.rs index c103fa662..5e863ade2 100644 --- a/packages/fermi/src/atoms/atomref.rs +++ b/packages/fermi/src/atoms/atomref.rs @@ -2,27 +2,24 @@ use crate::{AtomId, AtomRoot, Readable}; use std::cell::RefCell; pub struct AtomRefBuilder; -pub type AtomRef = fn(AtomRefBuilder) -> T; +pub struct AtomRef(pub fn(AtomRefBuilder) -> T); -impl Readable> for AtomRef { +impl Readable> for &'static AtomRef { fn read(&self, _root: AtomRoot) -> Option> { todo!() } fn init(&self) -> RefCell { - RefCell::new((*self)(AtomRefBuilder)) + RefCell::new(self.0(AtomRefBuilder)) } fn unique_id(&self) -> AtomId { - AtomId { - ptr: *self as *const (), - type_id: std::any::TypeId::of::(), - } + *self as *const AtomRef as *const () } } #[test] fn atom_compiles() { - static TEST_ATOM: AtomRef> = |_| vec![]; - dbg!(TEST_ATOM.init()); + static TEST_ATOM: AtomRef> = AtomRef(|_| vec![]); + dbg!((&TEST_ATOM).init()); } diff --git a/packages/fermi/src/hooks/atom_ref.rs b/packages/fermi/src/hooks/atom_ref.rs index 95732c491..74dd9268b 100644 --- a/packages/fermi/src/hooks/atom_ref.rs +++ b/packages/fermi/src/hooks/atom_ref.rs @@ -13,7 +13,10 @@ use std::{ /// /// /// -pub fn use_atom_ref(cx: &ScopeState, atom: AtomRef) -> &UseAtomRef { +pub fn use_atom_ref<'a, T: 'static>( + cx: &'a ScopeState, + atom: &'static AtomRef, +) -> &'a UseAtomRef { let root = use_atom_root(cx); &cx.use_hook(|| { diff --git a/packages/fermi/src/hooks/state.rs b/packages/fermi/src/hooks/state.rs index 15310cff2..071b18059 100644 --- a/packages/fermi/src/hooks/state.rs +++ b/packages/fermi/src/hooks/state.rs @@ -19,7 +19,7 @@ use std::{ /// static COUNT: Atom = |_| 0; /// /// fn Example(cx: Scope) -> Element { -/// let mut count = use_atom_state(cx, COUNT); +/// let mut count = use_atom_state(cx, &COUNT); /// /// cx.render(rsx! { /// div { diff --git a/packages/fermi/src/root.rs b/packages/fermi/src/root.rs index 99e97bfbb..f77930c75 100644 --- a/packages/fermi/src/root.rs +++ b/packages/fermi/src/root.rs @@ -1,21 +1,11 @@ -use std::{ - any::{Any, TypeId}, - cell::RefCell, - collections::HashMap, - rc::Rc, - sync::Arc, -}; +use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use dioxus_core::ScopeId; use im_rc::HashSet; use crate::Readable; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AtomId { - pub ptr: *const (), - pub type_id: TypeId, -} +pub type AtomId = *const (); pub struct AtomRoot { pub atoms: RefCell>, @@ -54,15 +44,7 @@ impl AtomRoot { // initialize the value if it's not already initialized if let Some(slot) = atoms.get_mut(&f.unique_id()) { slot.subscribers.insert(scope); - match slot.value.clone().downcast() { - Ok(res) => res, - Err(e) => panic!( - "Downcasting atom failed: {:?}. Has typeid of {:?} but needs typeid of {:?}", - f.unique_id(), - e.type_id(), - TypeId::of::() - ), - } + slot.value.clone().downcast().unwrap() } else { let value = Rc::new(f.init()); let mut subscribers = HashSet::new();