ソースを参照

create Signal::global

Evan Almloff 1 年間 前
コミット
bc914deeaa

+ 0 - 3
Cargo.toml

@@ -19,7 +19,6 @@ members = [
     "packages/desktop",
     "packages/mobile",
     "packages/interpreter",
-    # "packages/fermi",
     "packages/liveview",
     "packages/autofmt",
     "packages/check",
@@ -73,7 +72,6 @@ dioxus-ssr = { path = "packages/ssr", version = "0.4.0"  }
 dioxus-desktop = { path = "packages/desktop", version = "0.4.0"  }
 dioxus-mobile = { path = "packages/mobile", version = "0.4.0"  }
 dioxus-interpreter-js = { path = "packages/interpreter", version = "0.4.0" }
-# fermi = { path = "packages/fermi", version = "0.4.0"  }
 dioxus-liveview = { path = "packages/liveview", version = "0.4.0"  }
 dioxus-autofmt = { path = "packages/autofmt", version = "0.4.0"  }
 dioxus-check = { path = "packages/check", version = "0.4.0"  }
@@ -141,7 +139,6 @@ serde = { version = "1.0.136", features = ["derive"] }
 serde_json = "1.0.79"
 rand = { version = "0.8.4", features = ["small_rng"] }
 tokio = { version = "1.16.1", features = ["full"] }
-# fermi = { workspace = true }
 
 # To make most examples faster to compile, we split out assets and http-related stuff
 # This trims off like 270 dependencies, leading to a significant speedup in compilation time

+ 19 - 0
examples/global.rs

@@ -0,0 +1,19 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use dioxus::prelude::*;
+
+static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
+
+fn main() {
+    launch_desktop(app);
+}
+
+fn app() -> Element {
+    rsx! {
+        h1 { "High-Five counter: {COUNT}" }
+        button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
+        button { onclick: move |_| *COUNT.write() -= 1, "Down low!" }
+    }
+}

+ 5 - 0
packages/core/src/scope_context.rs

@@ -390,4 +390,9 @@ impl ScopeId {
     pub fn height(self) -> u32 {
         Runtime::with_scope(self, |cx| cx.height()).expect("to be in a dioxus runtime")
     }
+
+    /// Run a closure inside of scope's runtime
+    pub fn in_runtime<T>(self, f: impl FnOnce() -> T) -> T {
+        Runtime::with_scope(self, |_| f()).expect("to be in a dioxus runtime")
+    }
 }

+ 0 - 20
packages/fermi/Cargo.toml

@@ -1,20 +0,0 @@
-[package]
-name = "fermi"
-version = { workspace = true }
-authors = ["Jonathan Kelley"]
-edition = "2021"
-description = "Global state management for Dioxus"
-license = "MIT OR Apache-2.0"
-repository = "https://github.com/DioxusLabs/dioxus/"
-homepage = "https://dioxuslabs.com"
-keywords = ["dom", "ui", "gui", "react", "state-management"]
-
-
-
-[dependencies]
-dioxus-core = { workspace = true }
-im-rc = { version = "15.0.0", features = ["serde"] }
-tracing = { workspace = true }
-
-[dev-dependencies]
-closure = "0.3.0"

+ 0 - 112
packages/fermi/README.md

@@ -1,112 +0,0 @@
-<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/fermi">
-    <img src="https://img.shields.io/crates/v/fermi.svg?style=flat-square"
-    alt="Crates.io version" />
-  </a>
-  <!-- Downloads -->
-  <a href="https://crates.io/crates/fermi">
-    <img src="https://img.shields.io/crates/d/fermi.svg?style=flat-square"
-      alt="Download" />
-  </a>
-  <!-- docs -->
-  <a href="https://docs.rs/fermi">
-    <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/DioxusLabs/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, ignore
-static NAME: Atom<&str> = Atom(|_| "Dioxus");
-```
-
-From anywhere in our app, we can read the value of our atom:
-
-```rust, ignore
-fn NameCard() -> Element {
-    let name = use_read(&NAME);
-    rsx!{ h1 { "Hello, {name}"} })
-}
-```
-
-We can also set the value of our atom, also from anywhere in our app:
-
-```rust, ignore
-fn NameCard() -> Element {
-    let set_name = use_set(&NAME);
-    rsx!{
-        button {
-            onclick: move |_| set_name("Fermi"),
-            "Set name to fermi"
-        }
-    })
-}
-```
-
-If needed, we can update the atom's value, based on itself:
-
-```rust, ignore
-static COUNT: Atom<i32> = Atom(|_| 0);
-
-fn Counter() -> Element {
-    let mut count = use_atom_state(&COUNT);
-
-    rsx!{
-        p {
-          "{count}"
-        }
-        button {
-            onclick: move |_| count += 1,
-            "Increment counter"
-        }
-    })
-}
-```
-
-It's that simple!
-
-## Installation
-
-Fermi is currently under construction, so you have to use the `master` branch to get started.
-
-```toml
-[dependencies]
-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
-```
-
-## 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
-- [ ] Support for memoized Selectors
-- [ ] Support for memoized SelectorFamilies
-- [ ] Support for UseFermiCallback for access to fermi from async

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

@@ -1,44 +0,0 @@
-use crate::{AtomId, AtomRoot, Readable, Writable};
-
-pub struct Atom<T>(pub fn(AtomBuilder) -> T);
-pub struct AtomBuilder;
-
-impl<V> Readable<V> for &'static Atom<V> {
-    fn read(&self, _root: AtomRoot) -> Option<V> {
-        todo!()
-    }
-    fn init(&self) -> V {
-        self.0(AtomBuilder)
-    }
-    fn unique_id(&self) -> AtomId {
-        *self as *const Atom<V> as *const ()
-    }
-}
-
-impl<V> Writable<V> for &'static Atom<V> {
-    fn write(&self, _root: AtomRoot, _value: V) {
-        todo!()
-    }
-}
-
-#[test]
-fn atom_compiles() {
-    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<Vec<S>> = Atom(|_| Vec::new());
-    static TEST_ATOM_2: Atom<Vec<String>> = Atom(|_| Vec::new());
-    assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
-}

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

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

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

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

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

@@ -1 +0,0 @@
-

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

@@ -1 +0,0 @@
-

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

@@ -1,54 +0,0 @@
-#![allow(clippy::all, unused)]
-
-use std::rc::Rc;
-
-use dioxus_core::prelude::*;
-
-use crate::{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!()
-    }
-}
-
-#[must_use]
-pub fn use_atom_context() -> &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();
-    )*}
-}

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

@@ -1,119 +0,0 @@
-use crate::{use_atom_root, AtomId, AtomRef, AtomRoot, Readable};
-use dioxus_core::ScopeId;
-use std::{
-    cell::{Ref, RefCell, RefMut},
-    rc::Rc,
-};
-
-///
-///
-///
-///
-///
-///
-///
-///
-#[must_use]
-pub fn use_atom_ref<'a, T: 'static>(
-    cx: &'a ScopeState,
-    atom: &'static AtomRef<T>,
-) -> &'a 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()),
-            },
-            AtomRefSubscription {
-                ptr: atom.unique_id(),
-                root: root.clone(),
-                scope_id: cx.scope_id(),
-            },
-        )
-    })
-    .0
-}
-
-pub struct AtomRefSubscription {
-    ptr: AtomId,
-    root: Rc<AtomRoot>,
-    scope_id: ScopeId,
-}
-
-impl Drop for AtomRefSubscription {
-    fn drop(&mut self) {
-        self.root.unsubscribe(self.ptr, self.scope_id)
-    }
-}
-
-pub struct UseAtomRef<T> {
-    ptr: AtomId,
-    value: Rc<RefCell<T>>,
-    root: Rc<AtomRoot>,
-    scope_id: ScopeId,
-}
-
-impl<T> Clone for UseAtomRef<T> {
-    fn clone(&self) -> Self {
-        Self {
-            ptr: self.ptr,
-            value: self.value.clone(),
-            root: self.root.clone(),
-            scope_id: self.scope_id,
-        }
-    }
-}
-
-impl<T: 'static> UseAtomRef<T> {
-    pub fn read(&self) -> Ref<T> {
-        self.value.borrow()
-    }
-
-    /// This is silent operation
-    /// call `.force_update()` manually if required
-    pub fn with_mut_silent(&self, cb: impl FnOnce(&mut T)) {
-        cb(&mut *self.write_silent())
-    }
-
-    pub fn write(&self) -> RefMut<T> {
-        self.root.force_update(self.ptr);
-        self.value.borrow_mut()
-    }
-
-    /// Silent write to AtomRef
-    /// does not update Subscribed scopes
-    pub fn write_silent(&self) -> RefMut<T> {
-        self.value.borrow_mut()
-    }
-
-    /// Replace old value with new one
-    pub fn set(&self, new: T) {
-        self.root.force_update(self.ptr);
-        self.root.set(self.ptr, new);
-    }
-
-    /// Do not update provided context on Write ops
-    /// Example:
-    /// ```ignore
-    /// static ATOM_DATA: AtomRef<Collection> = |_| Default::default();
-    /// fn App() {
-    ///     use_init_atom_root(cx);
-    ///     let atom_data = use_atom_ref(ATOM_DATA);
-    ///     atom_data.unsubscribe(cx);
-    ///     atom_data.write().update();
-    /// }
-    /// ```
-    pub fn unsubscribe(&self) {
-        self.root.unsubscribe(self.ptr, cx.scope_id());
-    }
-
-    /// Force update of subscribed Scopes
-    pub fn force_update(&self) {
-        self.root.force_update(self.ptr);
-    }
-}

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

@@ -1,12 +0,0 @@
-use std::rc::Rc;
-
-use crate::AtomRoot;
-use dioxus_core::ScopeState;
-
-// Returns the atom root, initiaizing it at the root of the app if it does not exist.
-pub fn use_atom_root() -> &Rc<AtomRoot> {
-    cx.use_hook(|| match cx.consume_context::<Rc<AtomRoot>>() {
-        Some(root) => root,
-        None => panic!("No atom root found in context. Did you forget to call use_init_atom_root at the top of your app?"),
-    })
-}

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

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

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

@@ -1,38 +0,0 @@
-use crate::{use_atom_root, AtomId, AtomRoot, Readable};
-use dioxus_core::ScopeId;
-use std::rc::Rc;
-
-#[must_use]
-pub fn use_read<V: 'static>(f: impl Readable<V>) -> &V {
-    use_read_rc(f).as_ref()
-}
-
-#[must_use]
-pub fn use_read_rc<V: 'static>(f: impl Readable<V>) -> &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()
-}

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

@@ -1,14 +0,0 @@
-use crate::{use_atom_root, Writable};
-use dioxus_core::ScopeState;
-use std::rc::Rc;
-
-#[must_use]
-pub fn use_set<T: 'static>(f: impl Writable<T>) -> &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)>
-    })
-}

+ 0 - 423
packages/fermi/src/hooks/state.rs

@@ -1,423 +0,0 @@
-use crate::{AtomId, AtomRoot, Writable};
-use dioxus_core::ScopeId;
-use std::{
-    cell::RefMut,
-    fmt::{Debug, Display},
-    ops::{Add, Div, Mul, Not, Sub},
-    rc::Rc,
-};
-
-/// Store state between component renders.
-///
-/// ## Dioxus equivalent of AtomState, designed for Rust
-///
-/// The Dioxus version of `AtomState` for state management inside components. It allows you to ergonomically store and
-/// modify state between component renders. When the state is updated, the component will re-render.
-///
-///
-/// ```ignore
-/// static COUNT: Atom<u32> = |_| 0;
-///
-/// fn Example() -> Element {
-///     let mut count = use_atom_state(&COUNT);
-///
-///     rsx! {
-///         div {
-///             h1 { "Count: {count}" }
-///             button { onclick: move |_| count += 1, "Increment" }
-///             button { onclick: move |_| count -= 1, "Decrement" }
-///         }
-///     ))
-/// }
-/// ```
-#[must_use]
-pub fn use_atom_state<T: 'static>(f: impl Writable<T>) -> &AtomState<T> {
-    let root = crate::use_atom_root(cx);
-
-    let inner = cx.use_hook(|| AtomState {
-        value: None,
-        root: root.clone(),
-        scope_id: cx.scope_id(),
-        id: f.unique_id(),
-    });
-
-    inner.value = Some(inner.root.register(f, cx.scope_id()));
-
-    inner
-}
-
-pub struct AtomState<V: 'static> {
-    root: Rc<AtomRoot>,
-    id: AtomId,
-    scope_id: ScopeId,
-    value: Option<Rc<V>>,
-}
-
-impl<V> Drop for AtomState<V> {
-    fn drop(&mut self) {
-        self.root.unsubscribe(self.id, self.scope_id)
-    }
-}
-
-impl<T: 'static> AtomState<T> {
-    /// Set the state to a new value.
-    pub fn set(&self, new: T) {
-        self.root.set(self.id, new)
-    }
-
-    /// Get the current value of the state by cloning its container Rc.
-    ///
-    /// This is useful when you are dealing with state in async contexts but need
-    /// to know the current value. You are not given a reference to the state.
-    ///
-    /// # Examples
-    /// An async context might need to know the current value:
-    ///
-    /// ```rust, ignore
-    /// fn component() -> Element {
-    ///     let count = use_signal(|| 0);
-    ///     cx.spawn({
-    ///         let set_count = count.to_owned();
-    ///         async move {
-    ///             let current = set_count.current();
-    ///         }
-    ///     })
-    /// }
-    /// ```
-    #[must_use]
-    pub fn current(&self) -> Rc<T> {
-        let atoms = self.root.atoms.borrow();
-        let slot = atoms.get(&self.id).unwrap();
-        slot.value.clone().downcast().unwrap()
-    }
-
-    /// Get the `setter` function directly without the `AtomState` wrapper.
-    ///
-    /// This is useful for passing the setter function to other components.
-    ///
-    /// However, for most cases, calling `to_owned` on the state is the
-    /// preferred way to get "another" state handle.
-    ///
-    ///
-    /// # Examples
-    /// A component might require an `Rc<dyn Fn(T)>` as an input to set a value.
-    ///
-    /// ```rust, ignore
-    /// fn component() -> Element {
-    ///     let value = use_signal(|| 0);
-    ///
-    ///     rsx!{
-    ///         Component {
-    ///             handler: value.setter()
-    ///         }
-    ///     }
-    /// }
-    /// ```
-    #[must_use]
-    pub fn setter(&self) -> Rc<dyn Fn(T)> {
-        let root = self.root.clone();
-        let id = self.id;
-        Rc::new(move |new_val| root.set(id, new_val))
-    }
-
-    /// Set the state to a new value, using the current state value as a reference.
-    ///
-    /// This is similar to passing a closure to React's `set_value` function.
-    ///
-    /// # Examples
-    ///
-    /// Basic usage:
-    /// ```rust, ignore
-    /// # use dioxus_core::prelude::*;
-    /// # use dioxus_hooks::*;
-    /// fn component() -> Element {
-    ///     let value = use_signal(|| 0);
-    ///
-    ///     // to increment the value
-    ///     value.modify(|v| v + 1);
-    ///
-    ///     // usage in async
-    ///     cx.spawn({
-    ///         let value = value.to_owned();
-    ///         async move {
-    ///             value.modify(|v| v + 1);
-    ///         }
-    ///     });
-    ///
-    ///     # todo!()
-    /// }
-    /// ```
-    pub fn modify(&self, f: impl FnOnce(&T) -> T) {
-        self.root.clone().set(self.id, {
-            let current = self.value.as_ref().unwrap();
-            f(current.as_ref())
-        });
-    }
-
-    /// Get the value of the state when this handle was created.
-    ///
-    /// This method is useful when you want an `Rc` around the data to cheaply
-    /// pass it around your app.
-    ///
-    /// ## Warning
-    ///
-    /// This will return a stale value if used within async contexts.
-    ///
-    /// Try `current` to get the real current value of the state.
-    ///
-    /// ## Example
-    ///
-    /// ```rust, ignore
-    /// # use dioxus_core::prelude::*;
-    /// # use dioxus_hooks::*;
-    /// fn component() -> Element {
-    ///     let value = use_signal(|| 0);
-    ///
-    ///     let as_rc = value.get();
-    ///     assert_eq!(as_rc.as_ref(), &0);
-    ///
-    ///     # todo!()
-    /// }
-    /// ```
-    #[must_use]
-    pub fn get(&self) -> &T {
-        self.value.as_ref().unwrap()
-    }
-
-    #[must_use]
-    pub fn get_rc(&self) -> &Rc<T> {
-        self.value.as_ref().unwrap()
-    }
-
-    /// Mark all consumers of this atom to re-render
-    ///
-    /// ```rust, ignore
-    /// fn component() -> Element {
-    ///     let count = use_signal(|| 0);
-    ///     cx.spawn({
-    ///         let count = count.to_owned();
-    ///         async move {
-    ///             // for the component to re-render
-    ///             count.needs_update();
-    ///         }
-    ///     })
-    /// }
-    /// ```
-    pub fn needs_update(&self) {
-        self.root.force_update(self.id)
-    }
-}
-
-impl<T: Clone> AtomState<T> {
-    /// Get a mutable handle to the value by calling `ToOwned::to_owned` on the
-    /// current value.
-    ///
-    /// This is essentially cloning the underlying value and then setting it,
-    /// giving you a mutable handle in the process. This method is intended for
-    /// types that are cheaply cloneable.
-    ///
-    /// If you are comfortable dealing with `RefMut`, then you can use `make_mut` to get
-    /// the underlying slot. However, be careful with `RefMut` since you might panic
-    /// if the `RefCell` is left open.
-    ///
-    /// # Examples
-    ///
-    /// ```ignore
-    /// let val = use_signal(|| 0);
-    ///
-    /// val.with_mut(|v| *v = 1);
-    /// ```
-    pub fn with_mut(&self, apply: impl FnOnce(&mut T)) {
-        let mut new_val = self.value.as_ref().unwrap().as_ref().to_owned();
-        apply(&mut new_val);
-        self.set(new_val);
-    }
-
-    /// Get a mutable handle to the value by calling `ToOwned::to_owned` on the
-    /// current value.
-    ///
-    /// This is essentially cloning the underlying value and then setting it,
-    /// giving you a mutable handle in the process. This method is intended for
-    /// types that are cheaply cloneable.
-    ///
-    /// # Warning
-    /// Be careful with `RefMut` since you might panic if the `RefCell` is left open!
-    ///
-    /// # Examples
-    ///
-    /// ```ignore
-    /// let val = use_signal(|| 0);
-    ///
-    /// *val.make_mut() += 1;
-    /// ```
-    #[must_use]
-    pub fn make_mut(&self) -> RefMut<T> {
-        todo!("make mut not support for atom values yet")
-        // let mut slot = self.value.as_ref().unwrap();
-
-        // self.needs_update();
-
-        // if Rc::strong_count(&*slot) > 0 {
-        //     *slot = Rc::new(slot.as_ref().to_owned());
-        // }
-
-        // RefMut::map(slot, |rc| Rc::get_mut(rc).expect("the hard count to be 0"))
-    }
-
-    /// Convert this handle to a tuple of the value and the handle itself.
-    #[must_use]
-    pub fn split(&self) -> (&T, &Self) {
-        (self.value.as_ref().unwrap(), self)
-    }
-}
-
-impl<T: 'static> Clone for AtomState<T> {
-    fn clone(&self) -> Self {
-        AtomState {
-            root: self.root.clone(),
-            id: self.id,
-            scope_id: self.scope_id,
-            value: self.value.clone(),
-        }
-    }
-}
-
-impl<T: 'static + Display> std::fmt::Display for AtomState<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.value.as_ref().unwrap())
-    }
-}
-
-impl<T: std::fmt::Binary> std::fmt::Binary for AtomState<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{:b}", self.value.as_ref().unwrap().as_ref())
-    }
-}
-
-impl<T: PartialEq> PartialEq<T> for AtomState<T> {
-    fn eq(&self, other: &T) -> bool {
-        self.value.as_ref().unwrap().as_ref() == other
-    }
-}
-
-// todo: this but for more interesting conrete types
-impl PartialEq<bool> for &AtomState<bool> {
-    fn eq(&self, other: &bool) -> bool {
-        self.value.as_ref().unwrap().as_ref() == other
-    }
-}
-
-impl<T: PartialEq> PartialEq<AtomState<T>> for AtomState<T> {
-    fn eq(&self, other: &AtomState<T>) -> bool {
-        Rc::ptr_eq(self.value.as_ref().unwrap(), other.value.as_ref().unwrap())
-    }
-}
-
-impl<T: Debug> Debug for AtomState<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{:?}", self.value.as_ref().unwrap())
-    }
-}
-
-impl<T> std::ops::Deref for AtomState<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        self.value.as_ref().unwrap().as_ref()
-    }
-}
-
-impl<T: Not + Copy> std::ops::Not for &AtomState<T> {
-    type Output = <T as std::ops::Not>::Output;
-
-    fn not(self) -> Self::Output {
-        self.value.as_ref().unwrap().not()
-    }
-}
-
-impl<T: Not + Copy> std::ops::Not for AtomState<T> {
-    type Output = <T as std::ops::Not>::Output;
-
-    fn not(self) -> Self::Output {
-        self.value.as_ref().unwrap().not()
-    }
-}
-
-impl<T: std::ops::Add + Copy> std::ops::Add<T> for &AtomState<T> {
-    type Output = <T as std::ops::Add>::Output;
-
-    fn add(self, other: T) -> Self::Output {
-        *self.value.as_ref().unwrap().as_ref() + other
-    }
-}
-impl<T: std::ops::Sub + Copy> std::ops::Sub<T> for &AtomState<T> {
-    type Output = <T as std::ops::Sub>::Output;
-
-    fn sub(self, other: T) -> Self::Output {
-        *self.value.as_ref().unwrap().as_ref() - other
-    }
-}
-
-impl<T: std::ops::Div + Copy> std::ops::Div<T> for &AtomState<T> {
-    type Output = <T as std::ops::Div>::Output;
-
-    fn div(self, other: T) -> Self::Output {
-        *self.value.as_ref().unwrap().as_ref() / other
-    }
-}
-
-impl<T: std::ops::Mul + Copy> std::ops::Mul<T> for &AtomState<T> {
-    type Output = <T as std::ops::Mul>::Output;
-
-    fn mul(self, other: T) -> Self::Output {
-        *self.value.as_ref().unwrap().as_ref() * other
-    }
-}
-
-impl<T: Add<Output = T> + Copy> std::ops::AddAssign<T> for &AtomState<T> {
-    fn add_assign(&mut self, rhs: T) {
-        self.set((*self.current()) + rhs);
-    }
-}
-
-impl<T: Sub<Output = T> + Copy> std::ops::SubAssign<T> for &AtomState<T> {
-    fn sub_assign(&mut self, rhs: T) {
-        self.set((*self.current()) - rhs);
-    }
-}
-
-impl<T: Mul<Output = T> + Copy> std::ops::MulAssign<T> for &AtomState<T> {
-    fn mul_assign(&mut self, rhs: T) {
-        self.set((*self.current()) * rhs);
-    }
-}
-
-impl<T: Div<Output = T> + Copy> std::ops::DivAssign<T> for &AtomState<T> {
-    fn div_assign(&mut self, rhs: T) {
-        self.set((*self.current()) / rhs);
-    }
-}
-
-impl<T: Add<Output = T> + Copy> std::ops::AddAssign<T> for AtomState<T> {
-    fn add_assign(&mut self, rhs: T) {
-        self.set((*self.current()) + rhs);
-    }
-}
-
-impl<T: Sub<Output = T> + Copy> std::ops::SubAssign<T> for AtomState<T> {
-    fn sub_assign(&mut self, rhs: T) {
-        self.set((*self.current()) - rhs);
-    }
-}
-
-impl<T: Mul<Output = T> + Copy> std::ops::MulAssign<T> for AtomState<T> {
-    fn mul_assign(&mut self, rhs: T) {
-        self.set((*self.current()) * rhs);
-    }
-}
-
-impl<T: Div<Output = T> + Copy> std::ops::DivAssign<T> for AtomState<T> {
-    fn div_assign(&mut self, rhs: T) {
-        self.set((*self.current()) / rhs);
-    }
-}

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

@@ -1,58 +0,0 @@
-#![doc = include_str!("../README.md")]
-#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
-#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
-
-pub mod prelude {
-    pub use crate::*;
-}
-
-mod root;
-
-pub use atoms::*;
-pub use hooks::*;
-pub use root::*;
-
-mod atoms {
-    mod atom;
-    mod atomfamily;
-    mod atomref;
-    mod selector;
-    mod selectorfamily;
-
-    pub use atom::*;
-    pub use atomfamily::*;
-    pub use atomref::*;
-}
-
-pub mod hooks {
-    mod atom_ref;
-    mod atom_root;
-    mod init_atom_root;
-    mod read;
-    mod set;
-    mod state;
-    pub use atom_ref::*;
-    pub use atom_root::*;
-    pub use init_atom_root::*;
-    pub use read::*;
-    pub use set::*;
-    pub use state::*;
-}
-
-/// 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);
-}

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

@@ -1,123 +0,0 @@
-use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
-
-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: Arc<dyn Fn(ScopeId)>,
-}
-
-pub struct Slot {
-    pub value: Rc<dyn Any>,
-    pub subscribers: HashSet<ScopeId>,
-}
-
-impl AtomRoot {
-    pub fn new(update_any: Arc<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> {
-        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);
-            tracing::trace!("found item with subscribers {:?}", slot.subscribers);
-
-            for scope in &slot.subscribers {
-                tracing::trace!("updating subcsriber");
-                (self.update_any)(*scope);
-            }
-        } else {
-            tracing::trace!("no atoms found for {:?}", ptr);
-            atoms.insert(
-                ptr,
-                Slot {
-                    value: Rc::new(value),
-                    subscribers: HashSet::new(),
-                },
-            );
-        }
-    }
-
-    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() {
-                tracing::trace!("updating subcsriber");
-                (self.update_any)(*scope);
-            }
-        }
-    }
-
-    pub fn read<V: 'static>(&self, f: impl Readable<V>) -> Rc<V> {
-        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.value.clone().downcast().unwrap()
-        } else {
-            let value = Rc::new(f.init());
-            atoms.insert(
-                f.unique_id(),
-                Slot {
-                    value: value.clone(),
-                    subscribers: HashSet::new(),
-                },
-            );
-            value
-        }
-    }
-}

+ 194 - 0
packages/signals/src/global.rs

@@ -0,0 +1,194 @@
+use std::{
+    any::Any,
+    cell::{Ref, RefCell, RefMut},
+    collections::HashMap,
+    mem::MaybeUninit,
+    ops::Deref,
+    rc::Rc,
+};
+
+use dioxus_core::{
+    prelude::{provide_context, try_consume_context, IntoAttributeValue},
+    ScopeId,
+};
+use generational_box::{GenerationalRef, GenerationalRefMut};
+
+use crate::{MappedSignal, Signal, Write};
+
+/// A signal that can be accessed from anywhere in the application and created in a static
+pub struct GlobalSignal<T> {
+    initializer: fn() -> T,
+}
+
+#[derive(Clone)]
+struct GlobalSignalContext {
+    signal: Rc<RefCell<HashMap<*const (), Box<dyn Any>>>>,
+}
+
+fn get_global_context() -> GlobalSignalContext {
+    match try_consume_context() {
+        Some(context) => context,
+        None => {
+            let context = GlobalSignalContext {
+                signal: Rc::new(RefCell::new(HashMap::new())),
+            };
+            provide_context(context)
+        }
+    }
+}
+
+impl<T: 'static> GlobalSignal<T> {
+    /// Create a new global signal with the given initializer.
+    pub const fn new(initializer: fn() -> T) -> GlobalSignal<T> {
+        GlobalSignal { initializer }
+    }
+
+    fn inner_signal(&self) -> Signal<T> {
+        let key = self as *const _ as *const ();
+
+        let context = get_global_context();
+        let mut write = context.signal.borrow_mut();
+        let signal = write.entry(key).or_insert_with(|| {
+            // Constructors are always run in the root scope
+            let value = ScopeId::ROOT.in_runtime(self.initializer);
+            let signal = Signal::new(value);
+            Box::new(signal)
+        });
+
+        *signal.downcast_ref::<Signal<T>>().unwrap()
+    }
+
+    /// Get the scope the signal was created in.
+    pub fn origin_scope(&self) -> ScopeId {
+        ScopeId::ROOT
+    }
+
+    /// Get the current value of the signal. This will subscribe the current scope to the signal.  If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead.
+    ///
+    /// If the signal has been dropped, this will panic.
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<T, Ref<'static, T>> {
+        self.inner_signal().read()
+    }
+
+    /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**
+    ///
+    /// If the signal has been dropped, this will panic.
+    pub fn peek(&self) -> GenerationalRef<T, Ref<'static, T>> {
+        self.inner_signal().peek()
+    }
+
+    /// Get a mutable reference to the signal's value.
+    ///
+    /// If the signal has been dropped, this will panic.
+    #[track_caller]
+    pub fn write(&self) -> Write<T, GenerationalRefMut<T, RefMut<'static, T>>> {
+        self.inner_signal().write()
+    }
+
+    /// Set the value of the signal. This will trigger an update on all subscribers.
+    #[track_caller]
+    pub fn set(&self, value: T) {
+        self.inner_signal().set(value);
+    }
+
+    /// Set the value of the signal without triggering an update on subscribers.
+    #[track_caller]
+    pub fn set_untracked(&self, value: T) {
+        self.inner_signal().set_untracked(value);
+    }
+
+    /// Run a closure with a reference to the signal's value.
+    /// If the signal has been dropped, this will panic.
+    #[track_caller]
+    pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
+        self.inner_signal().with(f)
+    }
+
+    /// Run a closure with a mutable reference to the signal's value.
+    /// If the signal has been dropped, this will panic.
+    #[track_caller]
+    pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
+        self.inner_signal().with_mut(f)
+    }
+
+    /// Map the signal to a new type.
+    pub fn map<O>(
+        &self,
+        f: impl Fn(&T) -> &O + 'static,
+    ) -> MappedSignal<GenerationalRef<O, Ref<'static, O>>> {
+        MappedSignal::new(self.inner_signal(), f)
+    }
+
+    /// Get the generational id of the signal.
+    pub fn id(&self) -> generational_box::GenerationalBoxId {
+        self.inner_signal().id()
+    }
+}
+
+impl<T: 'static> IntoAttributeValue for GlobalSignal<T>
+where
+    T: Clone + IntoAttributeValue,
+{
+    fn into_value(self) -> dioxus_core::AttributeValue {
+        self.inner_signal().into_value()
+    }
+}
+
+impl<T: Clone + 'static> GlobalSignal<T> {
+    /// Get the current value of the signal. This will subscribe the current scope to the signal.
+    /// If the signal has been dropped, this will panic.
+    #[track_caller]
+    pub fn cloned(&self) -> T {
+        self.read().clone()
+    }
+}
+
+impl GlobalSignal<bool> {
+    /// Invert the boolean value of the signal. This will trigger an update on all subscribers.
+    pub fn toggle(&self) {
+        self.set(!self.cloned());
+    }
+}
+
+impl<T: 'static> PartialEq for GlobalSignal<T> {
+    fn eq(&self, other: &Self) -> bool {
+        std::ptr::eq(self, other)
+    }
+}
+
+/// Allow calling a signal with signal() syntax
+///
+/// Currently only limited to copy types, though could probably specialize for string/arc/rc
+impl<T: Clone + 'static> Deref for GlobalSignal<T> {
+    type Target = dyn Fn() -> T;
+
+    fn deref(&self) -> &Self::Target {
+        // https://github.com/dtolnay/case-studies/tree/master/callable-types
+
+        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
+        let uninit_callable = MaybeUninit::<Self>::uninit();
+        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
+        let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
+
+        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
+        let size_of_closure = std::mem::size_of_val(&uninit_closure);
+        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
+
+        // Then cast the lifetime of the closure to the lifetime of &self.
+        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
+            b
+        }
+        let reference_to_closure = cast_lifetime(
+            {
+                // The real closure that we will never use.
+                &uninit_closure
+            },
+            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self.
+            unsafe { std::mem::transmute(self) },
+        );
+
+        // Cast the closure to a trait object.
+        reference_to_closure as &Self::Target
+    }
+}

+ 90 - 19
packages/signals/src/impls.rs

@@ -1,54 +1,57 @@
 use crate::rt::CopyValue;
 use crate::signal::{ReadOnlySignal, Signal, Write};
-use crate::SignalData;
-use generational_box::Mappable;
+use crate::{GlobalSignal, SignalData};
+use generational_box::{GenerationalRef, Mappable};
 use generational_box::{MappableMut, Storage};
 
+use std::cell::Ref;
 use std::{
     fmt::{Debug, Display},
     ops::{Add, Div, Mul, Sub},
 };
 
 macro_rules! read_impls {
-    ($ty:ident, $bound:path, $vec_bound:path) => {
-        impl<T: Default + 'static, S: $bound> Default for $ty<T, S> {
-            #[track_caller]
-            fn default() -> Self {
-                Self::new_maybe_sync(Default::default())
+    ($ty:ident $(, $bound_ty:ident : $bound:path, $vec_bound_ty:ident : $vec_bound:path)?) => {
+        $(
+            impl<T: Default + 'static, $bound_ty: $bound> Default for $ty<T, $bound_ty> {
+                #[track_caller]
+                fn default() -> Self {
+                    Self::new_maybe_sync(Default::default())
+                }
             }
-        }
+        )?
 
-        impl<T, S: $bound> std::clone::Clone for $ty<T, S> {
+        impl<T $(,$bound_ty: $bound)?> std::clone::Clone for $ty<T $(, $bound_ty)?> {
             #[track_caller]
             fn clone(&self) -> Self {
                 *self
             }
         }
 
-        impl<T, S: $bound> Copy for $ty<T, S> {}
+        impl<T $(,$bound_ty: $bound)?> Copy for $ty<T $(, $bound_ty)?> {}
 
-        impl<T: Display + 'static, S: $bound> Display for $ty<T, S> {
+        impl<T: Display + 'static $(,$bound_ty: $bound)?> Display for $ty<T $(, $bound_ty)?> {
             #[track_caller]
             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                 self.with(|v| Display::fmt(v, f))
             }
         }
 
-        impl<T: Debug + 'static, S: $bound> Debug for $ty<T, S> {
+        impl<T: Debug + 'static $(,$bound_ty: $bound)?> Debug for $ty<T $(, $bound_ty)?> {
             #[track_caller]
             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                 self.with(|v| Debug::fmt(v, f))
             }
         }
 
-        impl<T: PartialEq + 'static, S: $bound> PartialEq<T> for $ty<T, S> {
+        impl<T: PartialEq + 'static $(,$bound_ty: $bound)?> PartialEq<T> for $ty<T $(, $bound_ty)?> {
             #[track_caller]
             fn eq(&self, other: &T) -> bool {
                 self.with(|v| *v == *other)
             }
         }
 
-        impl<T: 'static, S: $vec_bound> $ty<Vec<T>, S> {
+        impl<T: 'static $(,$vec_bound_ty: $vec_bound)?> $ty<Vec<T>, $($vec_bound_ty)?> {
             /// Returns the length of the inner vector.
             #[track_caller]
             pub fn len(&self) -> usize {
@@ -130,7 +133,13 @@ macro_rules! write_impls {
             }
         }
 
-        impl<T: 'static, S: $vec_bound> $ty<Vec<T>, S> {
+        write_vec_impls!($ty, S: $vec_bound);
+    };
+}
+
+macro_rules! write_vec_impls {
+    ($ty:ident $(, $vec_bound_ty:ident: $vec_bound:path)?) => {
+        impl<T: 'static $(, $vec_bound_ty: $vec_bound)?> $ty<Vec<T> $(, $vec_bound_ty)?> {
             /// Pushes a new value to the end of the vector.
             #[track_caller]
             pub fn push(&mut self, value: T) {
@@ -194,7 +203,7 @@ macro_rules! write_impls {
     };
 }
 
-read_impls!(CopyValue, Storage<T>, Storage<Vec<T>>);
+read_impls!(CopyValue, S: Storage<T>, S: Storage<Vec<T>>);
 
 impl<T: 'static, S: Storage<Vec<T>>> CopyValue<Vec<T>, S> {
     /// Read a value from the inner vector.
@@ -259,7 +268,7 @@ impl<T: 'static, S: Storage<Option<T>>> CopyValue<Option<T>, S> {
     }
 }
 
-read_impls!(Signal, Storage<SignalData<T>>, Storage<SignalData<Vec<T>>>);
+read_impls!(Signal, S: Storage<SignalData<T>>, S: Storage<SignalData<Vec<T>>>);
 
 impl<T: 'static, S: Storage<SignalData<Vec<T>>>> Signal<Vec<T>, S> {
     /// Read a value from the inner vector.
@@ -339,10 +348,72 @@ impl<T: 'static, S: Storage<SignalData<Option<T>>>> Signal<Option<T>, S> {
 
 read_impls!(
     ReadOnlySignal,
-    Storage<SignalData<T>>,
-    Storage<SignalData<Vec<T>>>
+    S: Storage<SignalData<T>>,
+    S: Storage<SignalData<Vec<T>>>
 );
 
+read_impls!(GlobalSignal);
+
+impl<T: 'static> GlobalSignal<Vec<T>> {
+    /// Read a value from the inner vector.
+    pub fn get(&'static self, index: usize) -> Option<GenerationalRef<T, Ref<'static, T>>> {
+        GenerationalRef::<Vec<T>, Ref<'static, Vec<T>>>::try_map(self.read(), move |v| v.get(index))
+    }
+}
+
+impl<T: 'static> GlobalSignal<Option<T>> {
+    /// Unwraps the inner value and clones it.
+    pub fn unwrap(&'static self) -> T
+    where
+        T: Clone,
+    {
+        self.with(|v| v.clone()).unwrap()
+    }
+
+    /// Attempts to read the inner value of the Option.
+    pub fn as_ref(&'static self) -> Option<GenerationalRef<T, Ref<'static, T>>> {
+        GenerationalRef::<Option<T>, Ref<'static, Option<T>>>::try_map(self.read(), |v| v.as_ref())
+    }
+}
+
+write_vec_impls!(GlobalSignal);
+
+impl<T: 'static> GlobalSignal<Option<T>> {
+    /// Takes the value out of the Option.
+    pub fn take(&self) -> Option<T> {
+        self.with_mut(|v| v.take())
+    }
+
+    /// Replace the value in the Option.
+    pub fn replace(&self, value: T) -> Option<T> {
+        self.with_mut(|v| v.replace(value))
+    }
+
+    /// Gets the value out of the Option, or inserts the given value if the Option is empty.
+    pub fn get_or_insert(&self, default: T) -> GenerationalRef<T, Ref<'static, T>> {
+        self.get_or_insert_with(|| default)
+    }
+
+    /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.
+    pub fn get_or_insert_with(
+        &self,
+        default: impl FnOnce() -> T,
+    ) -> GenerationalRef<T, Ref<'static, T>> {
+        let borrow = self.read();
+        if borrow.is_none() {
+            drop(borrow);
+            self.with_mut(|v| *v = Some(default()));
+            GenerationalRef::<Option<T>, Ref<'static, Option<T>>>::map(self.read(), |v| {
+                v.as_ref().unwrap()
+            })
+        } else {
+            GenerationalRef::<Option<T>, Ref<'static, Option<T>>>::map(borrow, |v| {
+                v.as_ref().unwrap()
+            })
+        }
+    }
+}
+
 /// An iterator over the values of a `CopyValue<Vec<T>>`.
 pub struct CopyValueIterator<T: 'static, S: Storage<Vec<T>>> {
     index: usize,

+ 2 - 0
packages/signals/src/lib.rs

@@ -20,3 +20,5 @@ pub use generational_box::{Storage, SyncStorage, UnsyncStorage};
 pub use map::*;
 mod comparer;
 pub use comparer::*;
+mod global;
+pub use global::*;

+ 14 - 3
packages/signals/src/signal.rs

@@ -1,4 +1,4 @@
-use crate::MappedSignal;
+use crate::{GlobalSignal, MappedSignal};
 use std::{
     cell::RefCell,
     marker::PhantomData,
@@ -220,6 +220,11 @@ impl<T: 'static> Signal<T> {
     pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
         Self::new_maybe_sync_in_scope(value, owner)
     }
+
+    /// Creates a new global Signal that can be used in a global static.
+    pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {
+        GlobalSignal::new(constructor)
+    }
 }
 
 impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
@@ -389,7 +394,8 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
 
     /// Set the value of the signal without triggering an update on subscribers.
     ///
-    /// todo: we should make it so setting while rendering doesn't trigger an update s
+    // todo: we should make it so setting while rendering doesn't trigger an update s
+    #[track_caller]
     pub fn set_untracked(&self, value: T) {
         let mut inner = self.inner.write();
         inner.value = value;
@@ -512,7 +518,12 @@ impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S>
 /// B is the dynamically checked type of the write (RefMut)
 /// S is the storage type of the signal
 /// I is the type of the original signal
-pub struct Write<T: 'static, B: MappableMut<T>, S: Storage<SignalData<I>>, I: 'static = T> {
+pub struct Write<
+    T: 'static,
+    B: MappableMut<T>,
+    S: Storage<SignalData<I>> = UnsyncStorage,
+    I: 'static = T,
+> {
     write: B,
     signal: SignalSubscriberDrop<I, S>,
     phantom: std::marker::PhantomData<T>,