1#![no_std]
4#![forbid(unsafe_code)]
5extern crate alloc;
6#[cfg(feature = "observability")]
9extern crate std;
10
11use alloc::rc::Rc;
12use core::{
13 any::TypeId,
14 cell::RefCell,
15 hash::{Hash, Hasher},
16 panic::Location,
17};
18
19use crate::watcher::{Context, WatcherGuard};
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
23pub struct SignalIdentity(usize);
24
25impl SignalIdentity {
26 #[must_use]
31 pub fn from_rc<T>(value: &Rc<T>) -> Self {
32 Self::from_ptr(Rc::as_ptr(value))
33 }
34
35 #[must_use]
41 pub fn from_ptr<T>(value: *const T) -> Self {
42 let value = value.cast::<()>() as usize;
43 assert!(value != 0, "signal identity pointer must not be null");
44 Self(value)
45 }
46
47 #[must_use]
49 pub const fn raw(self) -> usize {
50 self.0
51 }
52
53 #[must_use]
55 pub const fn with_discriminator(self, discriminator: usize) -> Self {
56 Self(
57 self.0.wrapping_mul(0x9E37_79B9usize).rotate_left(7)
58 ^ discriminator.wrapping_add(0x517C_C1B7usize),
59 )
60 }
61
62 #[must_use]
64 pub const fn combine(self, other: Self) -> Self {
65 self.with_discriminator(other.0)
66 }
67
68 #[must_use]
70 pub fn call_site_discriminator<T: 'static>(caller: &'static Location<'static>) -> usize {
71 let mut hasher = IdentityDiscriminatorHasher::new();
72 TypeId::of::<T>().hash(&mut hasher);
73 caller.file().hash(&mut hasher);
74 caller.line().hash(&mut hasher);
75 caller.column().hash(&mut hasher);
76 hasher.value()
77 }
78}
79
80struct IdentityDiscriminatorHasher(usize);
81
82impl IdentityDiscriminatorHasher {
83 const OFFSET: usize = 0x811C_9DC5;
84 const PRIME: usize = 0x0100_0193;
85
86 const fn new() -> Self {
87 Self(Self::OFFSET)
88 }
89
90 const fn value(&self) -> usize {
91 self.0
92 }
93}
94
95impl Hasher for IdentityDiscriminatorHasher {
96 fn finish(&self) -> u64 {
97 self.0 as u64
98 }
99
100 fn write(&mut self, bytes: &[u8]) {
101 for byte in bytes {
102 self.0 ^= usize::from(*byte);
103 self.0 = self.0.wrapping_mul(Self::PRIME);
104 self.0 = self.0.rotate_left(5);
105 }
106 }
107}
108
109pub mod collection;
111pub mod dictionary;
112pub mod observe;
113pub mod watcher;
114pub trait Signal: Clone + 'static {
119 type Output: 'static;
121 type Guard: WatcherGuard;
123
124 fn get(&self) -> Self::Output;
126
127 fn identity(&self) -> Option<SignalIdentity> {
132 None
133 }
134
135 #[must_use]
139 fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard;
140}
141
142pub trait CustomBinding: Signal {
147 fn set(&self, value: Self::Output);
151}
152
153#[macro_export]
158macro_rules! impl_constant {
159 ($($ty:ty),*) => {
160 $(
161 impl $crate::Signal for $ty {
162 type Output = Self;
163 type Guard = ();
164
165 fn get(&self) -> Self::Output {
166 self.clone()
167 }
168
169 fn watch(
170 &self,
171 _watcher: impl Fn($crate::watcher::Context<Self::Output>)+'static,
172 ) {
173
174 }
175 }
176 )*
177 };
178
179}
180
181macro_rules! impl_generic_constant {
182
183 ( $($ty:ident < $($param:ident),* >),* $(,)? ) => {
184 $(
185 impl<$($param: Clone + 'static),*> $crate::Signal for $ty<$($param),*> {
186 type Output = Self;
187 type Guard = ();
188
189 fn get(&self) -> Self::Output {
190 self.clone()
191 }
192
193 fn watch(
194 &self,
195 _watcher: impl Fn($crate::watcher::Context<Self::Output>)+'static,
196 ) {
197
198 }
199 }
200 )*
201 };
202
203
204
205
206}
207
208mod impl_constant {
209 use alloc::borrow::Cow;
210 use alloc::collections::{BTreeMap, BTreeSet};
211 use core::time::Duration;
212
213 use crate::Signal;
214 use alloc::string::String;
215 use alloc::vec::Vec;
216 impl_constant!(
217 &'static str,
218 u8,
219 u16,
220 u32,
221 u64,
222 usize,
223 i8,
224 i16,
225 i32,
226 i64,
227 isize,
228 f32,
229 f64,
230 bool,
231 char,
232 Duration,
233 String,
234 Cow<'static, str>
235 );
236
237 impl_generic_constant!(Vec<T>,BTreeMap<K,V>,BTreeSet<T>);
238
239 impl<T: 'static> Signal for &'static [T] {
240 type Output = &'static [T];
241 type Guard = ();
242 fn get(&self) -> Self::Output {
243 self
244 }
245 fn watch(&self, _watcher: impl Fn(crate::watcher::Context<Self::Output>) + 'static) {}
246 }
247
248 impl<T: Clone + 'static, const N: usize> Signal for [T; N] {
252 type Output = Self;
253 type Guard = ();
254 fn get(&self) -> Self::Output {
255 self.clone()
256 }
257 fn watch(&self, _watcher: impl Fn(crate::watcher::Context<Self::Output>) + 'static) {}
258 }
259}
260
261impl<T: Signal> Signal for Option<T> {
262 type Output = Option<T::Output>;
263 type Guard = Option<T::Guard>;
264 fn get(&self) -> Self::Output {
265 self.as_ref().map(Signal::get)
266 }
267 fn identity(&self) -> Option<SignalIdentity> {
268 self.as_ref().and_then(Signal::identity)
269 }
270 fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
271 self.as_ref()
272 .map(|s| s.watch(move |context| watcher(context.map(Some))))
273 }
274}
275
276impl<T: Signal, E: Signal> Signal for Result<T, E> {
277 type Output = Result<T::Output, E::Output>;
278 type Guard = Result<T::Guard, E::Guard>;
279 fn get(&self) -> Self::Output {
280 match &self {
281 Ok(s) => Ok(s.get()),
282 Err(e) => Err(e.get()),
283 }
284 }
285 fn identity(&self) -> Option<SignalIdentity> {
286 match &self {
287 Ok(s) => s.identity(),
288 Err(e) => e.identity(),
289 }
290 }
291 fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
292 match &self {
293 Ok(s) => Ok(s.watch(move |context| watcher(context.map(Ok)))),
294 Err(e) => Err(e.watch(move |context| watcher(context.map(Err)))),
295 }
296 }
297}
298
299struct TupleWatchState<L, R, W> {
300 latest_left: RefCell<L>,
301 latest_right: RefCell<R>,
302 watcher: W,
303}
304
305impl<T, U> Signal for (T, U)
306where
307 T: Signal,
308 U: Signal,
309 T::Output: Clone,
310 U::Output: Clone,
311{
312 type Output = (T::Output, U::Output);
313 type Guard = (T::Guard, U::Guard);
314
315 fn get(&self) -> Self::Output {
316 (self.0.get(), self.1.get())
317 }
318
319 fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
320 let state = Rc::new(TupleWatchState {
321 latest_left: RefCell::new(self.0.get()),
322 latest_right: RefCell::new(self.1.get()),
323 watcher,
324 });
325
326 let left_guard = {
327 let state = Rc::clone(&state);
328 self.0.watch(move |ctx: Context<T::Output>| {
329 let updated_left = ctx.value().clone();
330 *state.latest_left.borrow_mut() = updated_left;
331 let right = state.latest_right.borrow().clone();
332 (state.watcher)(ctx.map(|left| (left, right)));
333 })
334 };
335
336 let right_guard = self.1.watch(move |ctx: Context<U::Output>| {
337 let updated_right = ctx.value().clone();
338 *state.latest_right.borrow_mut() = updated_right;
339 let left = state.latest_left.borrow().clone();
340 (state.watcher)(ctx.map(|right| (left, right)));
341 });
342
343 (left_guard, right_guard)
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use alloc::{rc::Rc, vec, vec::Vec};
350 use core::cell::RefCell;
351
352 use crate::{Signal, watcher::Context};
353
354 #[derive(Clone)]
355 struct TestSignal<T> {
356 value: Rc<RefCell<T>>,
357 watchers: Rc<RefCell<Vec<Watcher<T>>>>,
358 }
359
360 type Watcher<T> = Rc<dyn Fn(Context<T>)>;
361
362 impl<T: Clone + 'static> TestSignal<T> {
363 fn new(value: T) -> Self {
364 Self {
365 value: Rc::new(RefCell::new(value)),
366 watchers: Rc::new(RefCell::new(Vec::new())),
367 }
368 }
369
370 fn set(&self, value: T) {
371 *self.value.borrow_mut() = value.clone();
372 for watcher in self.watchers.borrow().iter() {
373 watcher(Context::from(value.clone()));
374 }
375 }
376 }
377
378 impl<T: Clone + 'static> Signal for TestSignal<T> {
379 type Output = T;
380 type Guard = ();
381
382 fn get(&self) -> Self::Output {
383 self.value.borrow().clone()
384 }
385
386 fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
387 self.watchers.borrow_mut().push(Rc::new(watcher));
388 }
389 }
390
391 #[test]
392 fn tuple_signal_tracks_both_inputs() {
393 let left = TestSignal::new(1_i32);
394 let right = TestSignal::new(2_i32);
395 let pair = (left.clone(), right.clone());
396 let updates = Rc::new(RefCell::new(Vec::new()));
397
398 let _ = pair.watch({
399 let updates = updates.clone();
400 move |ctx| {
401 updates.borrow_mut().push(ctx.into_value());
402 }
403 });
404
405 left.set(3);
406 right.set(4);
407
408 assert_eq!(pair.get(), (3, 4));
409 assert_eq!(*updates.borrow(), vec![(3, 2), (3, 4)]);
410 }
411}