1use dioxus_core::{Runtime, ScopeId, Subscribers};
2use generational_box::BorrowResult;
3use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
4
5mod memo;
6pub use memo::*;
7
8mod signal;
9pub use signal::*;
10
11use crate::{Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt, WritableRef};
12
13pub trait InitializeFromFunction<T> {
15 fn initialize_from_function(f: fn() -> T) -> Self;
17}
18
19impl<T> InitializeFromFunction<T> for T {
20 fn initialize_from_function(f: fn() -> T) -> Self {
21 f()
22 }
23}
24
25pub struct Global<T, R = T> {
27 constructor: fn() -> R,
28 key: GlobalKey<'static>,
29 phantom: std::marker::PhantomData<fn() -> T>,
30}
31
32impl<T: Clone, R: Clone> Deref for Global<T, R>
36where
37 T: Readable<Target = R> + InitializeFromFunction<R> + 'static,
38 T::Target: 'static,
39{
40 type Target = dyn Fn() -> R;
41
42 fn deref(&self) -> &Self::Target {
43 unsafe { ReadableExt::deref_impl(self) }
44 }
45}
46
47impl<T, R> Readable for Global<T, R>
48where
49 T: Readable<Target = R> + InitializeFromFunction<R> + Clone + 'static,
50{
51 type Target = R;
52 type Storage = T::Storage;
53
54 #[track_caller]
55 fn try_read_unchecked(
56 &self,
57 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
58 where
59 R: 'static,
60 {
61 self.resolve().try_read_unchecked()
62 }
63
64 #[track_caller]
65 fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
66 where
67 R: 'static,
68 {
69 self.resolve().try_peek_unchecked()
70 }
71
72 fn subscribers(&self) -> Subscribers
73 where
74 R: 'static,
75 {
76 self.resolve().subscribers()
77 }
78}
79
80impl<T: Clone, R> Writable for Global<T, R>
81where
82 T: Writable<Target = R> + InitializeFromFunction<R> + 'static,
83{
84 type WriteMetadata = T::WriteMetadata;
85
86 #[track_caller]
87 fn try_write_unchecked(
88 &self,
89 ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
90 self.resolve().try_write_unchecked()
91 }
92}
93
94impl<T: Clone, R> Global<T, R>
95where
96 T: Writable<Target = R> + InitializeFromFunction<R> + 'static,
97{
98 pub fn write(&self) -> WritableRef<'static, T, R> {
100 self.resolve().try_write_unchecked().unwrap()
101 }
102
103 #[track_caller]
106 pub fn with_mut<O>(&self, f: impl FnOnce(&mut R) -> O) -> O
107 where
108 T::Target: 'static,
109 {
110 self.resolve().with_mut(f)
111 }
112}
113
114impl<T: Clone, R> Global<T, R>
115where
116 T: InitializeFromFunction<R>,
117{
118 #[track_caller]
119 pub const fn new(constructor: fn() -> R) -> Self {
121 let key = std::panic::Location::caller();
122 Self {
123 constructor,
124 key: GlobalKey::new(key),
125 phantom: std::marker::PhantomData,
126 }
127 }
128
129 #[track_caller]
133 pub const fn with_name(constructor: fn() -> R, key: &'static str) -> Self {
134 Self {
135 constructor,
136 key: GlobalKey::File {
137 file: key,
138 line: 0,
139 column: 0,
140 index: 0,
141 },
142 phantom: std::marker::PhantomData,
143 }
144 }
145
146 #[track_caller]
150 pub const fn with_location(
151 constructor: fn() -> R,
152 file: &'static str,
153 line: u32,
154 column: u32,
155 index: usize,
156 ) -> Self {
157 Self {
158 constructor,
159 key: GlobalKey::File {
160 file,
161 line: line as _,
162 column: column as _,
163 index: index as _,
164 },
165 phantom: std::marker::PhantomData,
166 }
167 }
168
169 pub fn key(&self) -> GlobalKey<'static> {
171 self.key.clone()
172 }
173
174 pub fn resolve(&self) -> T
177 where
178 T: 'static,
179 {
180 let key = self.key();
181
182 let context = get_global_context();
183
184 {
186 let read = context.map.borrow();
187 if let Some(signal) = read.get(&key) {
188 return signal.downcast_ref::<T>().cloned().unwrap();
189 }
190 }
191 let signal = dioxus_core::Runtime::current().in_scope(ScopeId::ROOT, || {
194 T::initialize_from_function(self.constructor)
195 });
196 context
197 .map
198 .borrow_mut()
199 .insert(key, Box::new(signal.clone()));
200 signal
201 }
202
203 pub fn origin_scope(&self) -> ScopeId {
205 ScopeId::ROOT
206 }
207}
208
209#[derive(Clone, Default)]
211pub struct GlobalLazyContext {
212 map: Rc<RefCell<HashMap<GlobalKey<'static>, Box<dyn Any>>>>,
213}
214
215#[derive(Clone, Debug, PartialEq, Eq, Hash)]
217pub enum GlobalKey<'a> {
218 File {
220 file: &'a str,
222
223 line: u32,
225
226 column: u32,
228
229 index: u32,
231 },
232
233 Raw(&'a str),
235}
236
237impl<'a> GlobalKey<'a> {
238 pub const fn new(key: &'a Location<'a>) -> Self {
240 GlobalKey::File {
241 file: key.file(),
242 line: key.line(),
243 column: key.column(),
244 index: 0,
245 }
246 }
247}
248
249impl From<&'static Location<'static>> for GlobalKey<'static> {
250 fn from(key: &'static Location<'static>) -> Self {
251 Self::new(key)
252 }
253}
254
255impl GlobalLazyContext {
256 pub fn get_signal_with_key<T: 'static>(&self, key: GlobalKey) -> Option<Signal<T>> {
259 self.map.borrow().get(&key).map(|f| {
260 *f.downcast_ref::<Signal<T>>().unwrap_or_else(|| {
261 panic!(
262 "Global signal with key {:?} is not of the expected type. Keys are {:?}",
263 key,
264 self.map.borrow().keys()
265 )
266 })
267 })
268 }
269
270 #[doc(hidden)]
271 pub fn clear<T: 'static>(&self) {
273 self.map.borrow_mut().retain(|_k, v| !v.is::<T>());
274 }
275}
276
277pub fn get_global_context() -> GlobalLazyContext {
279 let rt = Runtime::current();
280 match rt.has_context(ScopeId::ROOT) {
281 Some(context) => context,
282 None => rt.provide_context(ScopeId::ROOT, Default::default()),
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
293 fn test_global_keys() {
294 const MYSIGNAL: GlobalSignal<i32> = GlobalSignal::new(|| 42);
296 const MYSIGNAL2: GlobalSignal<i32> = GlobalSignal::new(|| 42);
297 const MYSIGNAL3: GlobalSignal<i32> = GlobalSignal::with_name(|| 42, "custom-keyed");
298
299 let a = MYSIGNAL.key();
300 let b = MYSIGNAL.key();
301 let c = MYSIGNAL.key();
302 assert_eq!(a, b);
303 assert_eq!(b, c);
304
305 let d = MYSIGNAL2.key();
306 assert_ne!(a, d);
307
308 let e = MYSIGNAL3.key();
309 assert_ne!(a, e);
310 }
311}