Skip to main content

rustolio_web/hooks/
global.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11use super::{Effect, Signal, SignalBase, SignalGetter, SignalSetter, SignalUpdater};
12
13/// Can be used to create a [`GlobalSignal`], a global [`Calculated`] or a global [`Effect`].
14///
15/// [`GlobalSignal`]: crate::prelude::GlobalSignal
16/// [`Calculated`]: crate::prelude::Calculated
17/// [`Effect`]: crate::prelude::Effect
18#[macro_export]
19macro_rules! __global {
20    // Empty case for recursion
21    () => {};
22
23    // // Global Signal - Result
24    // ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = ($init:expr)?; $($rest:tt)*) => {
25    //     $(#[$attr])*
26    //     $vis static $name: GlobalWrapper<$t> = {
27    //         thread_local! {
28    //             static SIGNAL: $t = unsafe {
29    //                 let s = $init;
30    //                 let s = $crate::prelude::__web_global_macro::match_result("global!", s);
31    //                 $crate::hooks::SignalBase::globalize(&s);
32    //                 s
33    //             };
34    //         }
35    //         GlobalWrapper::__new(&SIGNAL)
36    //     };
37    //     $crate::__global!($($rest)*);
38    // };
39
40    // Global Signal
41    ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => {
42        $(#[$attr])*
43        $vis static $name: GlobalWrapper<$t> = {
44            thread_local! {
45                static __SIGNAL: $t = unsafe {
46                    let s = $init;
47                    $crate::hooks::SignalBase::globalize(&s);
48                    s
49                };
50            }
51            GlobalWrapper::__new(&__SIGNAL)
52        };
53        $crate::__global!($($rest)*);
54    };
55
56    // // Global Signal
57    // ($(#[$attr:meta])* $vis:vis static $name:ident: GlobalSignal<$t:ty> = $init:expr; $($rest:tt)*) => {
58    //     $(#[$attr])*
59    //     $vis static $name: GlobalWrapper<GlobalSignal<$t>> = {
60    //         thread_local! {
61    //             static SIGNAL: GlobalSignal<$t> = $init;
62    //         }
63    //         GlobalWrapper::__new(&SIGNAL)
64    //     };
65    //     $crate::__global!($($rest)*);
66    // };
67
68    // // Global Calculate
69    // ($(#[$attr:meta])* $vis:vis static $name:ident: Calculated<$t:ty> = Calculated::new($init:expr); $($rest:tt)*) => {
70    //     $(#[$attr])*
71    //     $vis static $name: GlobalWrapper<Calculated<$t>> = {
72    //         thread_local! {
73    //             static CALCULATED: Calculated<$t> = unsafe {
74    //                 // SAFETY: Cannot use scoped signals here
75    //                 Calculated::__global($init)
76    //             };
77    //         }
78    //         GlobalWrapper::__new(&CALCULATED)
79    //     };
80    //     $crate::__global!($($rest)*);
81    // };
82    // ($(#[$attr:meta])* $vis:vis static $name:ident: Calculated<$t:ty> = Calculated::new_result($init:expr); $($rest:tt)*) => {
83    //     $(#[$attr])*
84    //     $vis static $name: GlobalWrapper<Calculated<$t>> = {
85    //         thread_local! {
86    //             static CALCULATED: Calculated<$t> = unsafe {
87    //                 // SAFETY: Cannot use scoped signals here
88    //                 Calculated::__global_result($init)
89    //             };
90    //         }
91    //         GlobalWrapper::__new(&CALCULATED)
92    //     };
93    //     $crate::__global!($($rest)*);
94    // };
95
96    // Global Effect
97    (Effect::$method:ident($init:expr); $($rest:tt)*) => {
98        thread_local! {
99            static __EFFECT: Effect = unsafe {
100                // SAFETY: Cannot use scoped signals here
101                let mut e = Effect::$method($init);
102                e.deregister();
103                e
104            };
105        }
106        __web_global_macro::inventory::submit! { GlobalWrapper::__new(&__EFFECT) }
107        $crate::__global!($($rest)*);
108    };
109
110    // // thread_local! copy for convenience
111    // ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const $init:block; $($rest:tt)*) => {
112    //     thread_local! {
113    //         $(#[$attr])* $vis static $name: $t = const $init;
114    //     }
115    //     $crate::__global!($($rest)*);
116    // };
117
118    // // thread_local! copy for convenience
119    // ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => {
120    //     thread_local! {
121    //         $vis static $name: $t = $init;
122    //     }
123    //     $crate::__global!($($rest)*);
124    // };
125}
126pub use __global as global;
127
128#[derive(Debug, Clone, Copy)]
129pub struct GlobalWrapper<S: 'static> {
130    inner: &'static std::thread::LocalKey<S>,
131}
132
133impl<S> GlobalWrapper<S>
134where
135    S: Clone + 'static,
136{
137    /// Should only called using the [`global!`] macro.
138    #[doc(hidden)]
139    pub const fn __new(inner: &'static std::thread::LocalKey<S>) -> Self {
140        GlobalWrapper { inner }
141    }
142}
143
144impl<S, T> SignalBase<T> for GlobalWrapper<S>
145where
146    S: SignalBase<T>,
147{
148    fn base(&self) -> Signal<T> {
149        self.inner.with(|s| s.base())
150    }
151}
152impl<S, T> SignalGetter<T> for GlobalWrapper<S>
153where
154    S: SignalGetter<T>,
155    T: Clone + 'static,
156{
157}
158impl<S, T> SignalSetter<T> for GlobalWrapper<S>
159where
160    S: Copy + SignalSetter<T>,
161    T: PartialEq + 'static,
162{
163}
164impl<S, T> SignalUpdater<T> for GlobalWrapper<S>
165where
166    S: Copy + SignalUpdater<T>,
167    T: 'static,
168{
169}
170
171impl<S, T> From<GlobalWrapper<S>> for Signal<T>
172where
173    S: Copy + SignalGetter<T>,
174    T: Clone + 'static,
175{
176    fn from(val: GlobalWrapper<S>) -> Self {
177        val.base()
178    }
179}
180
181inventory::collect!(GlobalWrapper<Effect>);
182
183impl GlobalWrapper<Effect> {
184    pub(crate) fn register_globals() {
185        #[cfg(target_arch = "wasm32")]
186        {
187            // FIX: wasm builds run out of memory
188            // https://github.com/dtolnay/inventory/issues/77
189            unsafe extern "C" {
190                fn __wasm_call_ctors();
191            }
192            unsafe { __wasm_call_ctors() };
193        }
194
195        for effect in inventory::iter::<GlobalWrapper<Effect>> {
196            effect.inner.with(|_f| {})
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use std::sync::atomic::{AtomicUsize, Ordering};
204
205    use crate::prelude::*;
206
207    static EFFECT_CALLED: AtomicUsize = AtomicUsize::new(0);
208
209    global! {
210        // static SOME_VALUE: String = String::from("test");
211        // static SOME_MUT_VALUE: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
212
213        static GLOBAL_SIGNAL1: GlobalSignal<i32> = GlobalSignal::new(0);
214        static GLOBAL_SIGNAL2: GlobalSignal<i32> = GlobalSignal::new(0);
215        static GLOBAL_CALCULATED1: Calculated<i32> = Calculated::new(|| GLOBAL_SIGNAL1.value() * 2);
216        // static GLOBAL_SIGNAL3: Signal<i32> = 0;
217        Effect::new(|| {
218            EFFECT_CALLED.fetch_add(1, Ordering::Relaxed);
219            let _ = GLOBAL_SIGNAL1.value();
220        });
221    }
222
223    // #[test]
224    // fn test_thread_local() {
225    //     assert_eq!(SOME_VALUE.with(|v| v.clone()), String::from("test"));
226    //     SOME_MUT_VALUE.with(|v| v.borrow_mut().push(1));
227    //     SOME_MUT_VALUE.with(|v| assert_eq!(v.borrow().as_ref(), vec![1]));
228    // }
229
230    #[test]
231    fn test_global_signal() {
232        assert_eq!(EFFECT_CALLED.load(Ordering::Relaxed), 0);
233
234        GlobalWrapper::register_globals();
235        assert_eq!(EFFECT_CALLED.load(Ordering::Relaxed), 1);
236
237        assert_eq!(GLOBAL_SIGNAL1.value(), 0);
238
239        GLOBAL_SIGNAL1.set(42);
240        assert_eq!(GLOBAL_SIGNAL1.value(), 42);
241        assert_eq!(EFFECT_CALLED.load(Ordering::Relaxed), 2);
242
243        GLOBAL_SIGNAL1.update(|v| *v += 1);
244        assert_eq!(GLOBAL_SIGNAL1.value(), 43);
245        assert_eq!(EFFECT_CALLED.load(Ordering::Relaxed), 3);
246
247        assert_eq!(GLOBAL_CALCULATED1.value(), 43 * 2);
248    }
249
250    #[test]
251    fn test_deref() {
252        let local_signal: Signal<i32> = GLOBAL_SIGNAL2.into();
253        assert_eq!(local_signal.value(), 0);
254        local_signal.set(50);
255        assert_eq!(local_signal.value(), 50);
256        assert_eq!(GLOBAL_SIGNAL2.value(), 50);
257    }
258}