Skip to main content

nami_core/
lib.rs

1//! Core components for the Nami framework.
2
3#![no_std]
4#![forbid(unsafe_code)]
5extern crate alloc;
6// Observability scopes its observer to one reactive graph with a thread-local,
7// which needs `std`. The crate stays `no_std` in every other configuration.
8#[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/// Stable identity for reactive signal instances that own observable state.
22#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
23pub struct SignalIdentity(usize);
24
25impl SignalIdentity {
26    /// Creates an identity from a shared allocation pointer.
27    ///
28    /// The allocation must be tied to the semantic signal instance and be cloned
29    /// together with that instance.
30    #[must_use]
31    pub fn from_rc<T>(value: &Rc<T>) -> Self {
32        Self::from_ptr(Rc::as_ptr(value))
33    }
34
35    /// Creates an identity from a stable allocation pointer.
36    ///
37    /// # Panics
38    ///
39    /// Panics when the pointer is null.
40    #[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    /// Raw value for renderer-local key composition.
48    #[must_use]
49    pub const fn raw(self) -> usize {
50        self.0
51    }
52
53    /// Creates a stable derived identity for a property of this signal.
54    #[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    /// Combines two stable signal identities into one deterministic identity.
63    #[must_use]
64    pub const fn combine(self, other: Self) -> Self {
65        self.with_discriminator(other.0)
66    }
67
68    /// Creates a stable discriminator from a call site and semantic wrapper type.
69    #[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
109/// Collection types for Nami.
110pub mod collection;
111pub mod dictionary;
112pub mod observe;
113pub mod watcher;
114/// The core trait for reactive system.
115///
116/// Types implementing `Signal` represent a computation that can produce a value
117/// and notify observers when that value changes.
118pub trait Signal: Clone + 'static {
119    /// The type of value produced by this computation.
120    type Output: 'static;
121    /// The guard type returned by the watch method that manages watcher lifecycle.
122    type Guard: WatcherGuard;
123
124    /// Execute the computation and return the current value.
125    fn get(&self) -> Self::Output;
126
127    /// Returns the stable semantic identity of this signal, when one exists.
128    ///
129    /// Constant signals intentionally return `None` because they do not own
130    /// observable state.
131    fn identity(&self) -> Option<SignalIdentity> {
132        None
133    }
134
135    /// Register a watcher to be notified when the computed value changes.
136    ///
137    /// Returns a guard that, when dropped, will unregister the watcher.
138    #[must_use]
139    fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard;
140}
141
142/// The `CustomBinding` trait represents a computable value that can also be set.
143///
144/// Any type implementing this trait must also implement `Signal` to provide the
145/// ability to retrieve its current value, and adds the ability to mutate the value.
146pub trait CustomBinding: Signal {
147    /// Sets a new value for this binding.
148    ///
149    /// This will typically trigger notifications to any watchers.
150    fn set(&self, value: Self::Output);
151}
152
153/// Macro to implement the Signal trait for constant types.
154///
155/// This macro generates Signal implementations for types that don't change,
156/// providing them with empty watcher functionality since they never notify changes.
157#[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    // Fixed-size arrays of `Clone + 'static` elements act as constant signals.
249    // This lets callers pass typed value arrays (e.g. mesh-gradient palettes)
250    // directly into `IntoSignal<[T; N]>`-typed parameters without wrapping.
251    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}