From 9731640434cb8f7b5079d0ac1862262e19c2ed6b Mon Sep 17 00:00:00 2001 From: DianQK Date: Wed, 21 Jun 2023 20:07:49 +0800 Subject: [PATCH 1/2] Revert "fix: use typeid of atoms for their unique ID (#558)" This reverts commit bf9901a6cd98d9487174f00b9b344f6105677017. --- packages/fermi/src/atoms/atom.rs | 9 +++------ packages/fermi/src/atoms/atomfamily.rs | 9 +++------ packages/fermi/src/atoms/atomref.rs | 7 ++----- packages/fermi/src/root.rs | 24 +++--------------------- 4 files changed, 11 insertions(+), 38 deletions(-) diff --git a/packages/fermi/src/atoms/atom.rs b/packages/fermi/src/atoms/atom.rs index 81e04ce0b..2b99174b5 100644 --- a/packages/fermi/src/atoms/atom.rs +++ b/packages/fermi/src/atoms/atom.rs @@ -3,7 +3,7 @@ use crate::{AtomId, AtomRoot, Readable, Writable}; pub type Atom = fn(AtomBuilder) -> T; pub struct AtomBuilder; -impl Readable for Atom { +impl Readable for Atom { fn read(&self, _root: AtomRoot) -> Option { todo!() } @@ -11,14 +11,11 @@ impl Readable for Atom { (*self)(AtomBuilder) } fn unique_id(&self) -> AtomId { - AtomId { - ptr: *self as *const (), - type_id: std::any::TypeId::of::(), - } + *self as *const () } } -impl Writable for Atom { +impl Writable for Atom { fn write(&self, _root: AtomRoot, _value: V) { todo!() } diff --git a/packages/fermi/src/atoms/atomfamily.rs b/packages/fermi/src/atoms/atomfamily.rs index 29cecd4b2..60fc22bcb 100644 --- a/packages/fermi/src/atoms/atomfamily.rs +++ b/packages/fermi/src/atoms/atomfamily.rs @@ -4,7 +4,7 @@ use im_rc::HashMap as ImMap; pub struct AtomFamilyBuilder; pub type AtomFamily = fn(AtomFamilyBuilder) -> ImMap; -impl Readable> for AtomFamily { +impl Readable> for AtomFamily { fn read(&self, _root: AtomRoot) -> Option> { todo!() } @@ -14,14 +14,11 @@ impl Readable> for AtomFamily { } fn unique_id(&self) -> AtomId { - AtomId { - ptr: *self as *const (), - type_id: std::any::TypeId::of::(), - } + *self as *const () } } -impl Writable> for AtomFamily { +impl Writable> for 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..59e412031 100644 --- a/packages/fermi/src/atoms/atomref.rs +++ b/packages/fermi/src/atoms/atomref.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; pub struct AtomRefBuilder; pub type AtomRef = fn(AtomRefBuilder) -> T; -impl Readable> for AtomRef { +impl Readable> for AtomRef { fn read(&self, _root: AtomRoot) -> Option> { todo!() } @@ -14,10 +14,7 @@ impl Readable> for AtomRef { } fn unique_id(&self) -> AtomId { - AtomId { - ptr: *self as *const (), - type_id: std::any::TypeId::of::(), - } + *self as *const () } } 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(); From 535435a4cf7ad8c3a1e6cc9bdb82eb4f705d544e Mon Sep 17 00:00:00 2001 From: DianQK Date: Wed, 21 Jun 2023 20:09:11 +0800 Subject: [PATCH 2/2] Fix the issue of duplicate unique ID for atoms using `newtype`. The MergeFunctionsPass in LLVM merges identical functions (https://rust.godbolt.org/z/3nnr9nMne), resulting in the same function addresses. --- .github/workflows/macos.yml | 4 ++- .github/workflows/windows.yml | 1 + .../en/__unused/advanced-guides/12-signals.md | 4 +-- docs/guide/src/en/async/use_coroutine.md | 8 ++--- .../src/en/best_practices/error_handling.md | 6 ++-- docs/guide/src/pt-br/async/use_coroutine.md | 8 ++--- .../pt-br/best_practices/error_handling.md | 6 ++-- examples/fermi.rs | 10 +++---- packages/fermi/README.md | 20 ++++++------- packages/fermi/src/atoms/atom.rs | 30 ++++++++++++++----- packages/fermi/src/atoms/atomfamily.rs | 10 +++---- packages/fermi/src/atoms/atomref.rs | 12 ++++---- packages/fermi/src/hooks/atom_ref.rs | 5 +++- packages/fermi/src/hooks/state.rs | 2 +- 14 files changed, 74 insertions(+), 52 deletions(-) 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 c7012a119..486758c8d 100644 --- a/docs/guide/src/en/__unused/advanced-guides/12-signals.md +++ b/docs/guide/src/en/__unused/advanced-guides/12-signals.md @@ -95,7 +95,7 @@ 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 -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 +131,7 @@ 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 -const DICT: AtomFamily = |_| {}; +const DICT: AtomFamily = AtomFamily(|_| {}); const List: Component = |cx|{ let dict = use_signal(cx, &DICT); cx.render(rsx!( diff --git a/docs/guide/src/en/async/use_coroutine.md b/docs/guide/src/en/async/use_coroutine.md index 537e27ea4..caffb6bbc 100644 --- a/docs/guide/src/en/async/use_coroutine.md +++ b/docs/guide/src/en/async/use_coroutine.md @@ -146,7 +146,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 -static USERNAME: Atom = |_| "default".to_string(); +static USERNAME: Atom = Atom(|_| "default".to_string()); fn app(cx: Scope) -> Element { let atoms = use_atom_root(cx); @@ -159,7 +159,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}" } @@ -177,8 +177,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 afbb45417..992926054 100644 --- a/docs/guide/src/en/best_practices/error_handling.md +++ b/docs/guide/src/en/best_practices/error_handling.md @@ -118,14 +118,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 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!" }), @@ -139,7 +139,7 @@ Now, whenever a downstream component has an error in its actions, it can simply ```rust 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 cd7cbff11..b6fd71c6c 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 -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 938322234..e0a7be509 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 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 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 f04ec5961..f8192bbc7 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 2b99174b5..bba1f532f 100644 --- a/packages/fermi/src/atoms/atom.rs +++ b/packages/fermi/src/atoms/atom.rs @@ -1,21 +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 { - *self as *const () + *self as *const Atom as *const () } } -impl Writable for Atom { +impl Writable for &'static Atom { fn write(&self, _root: AtomRoot, _value: V) { todo!() } @@ -23,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 60fc22bcb..45f6b8feb 100644 --- a/packages/fermi/src/atoms/atomfamily.rs +++ b/packages/fermi/src/atoms/atomfamily.rs @@ -2,23 +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 { - *self as *const () + *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 59e412031..5e863ade2 100644 --- a/packages/fermi/src/atoms/atomref.rs +++ b/packages/fermi/src/atoms/atomref.rs @@ -2,24 +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 { - *self as *const () + *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 {