Skip to main content

nami/reactive_core/
signal.rs

1//! This module provides a framework for reactive computations that can track dependencies
2//! and automatically update when their inputs change.
3//!
4//! The core abstractions include:
5//! - `Signal` - A trait for values that can be computed and watched for changes
6//! - `IntoSignal` - Conversion trait for working with signals
7//! - `IntoComputed` - Conversion trait for creating computed values
8//!
9//! This system enables building reactive data flows where computations automatically
10//! re-execute when their dependencies change, similar to reactive programming models
11//! found in front-end frameworks.
12
13use core::panic::Location;
14
15mod computed;
16pub use computed::*;
17
18use crate::{
19    map::{Map, map},
20    watcher::Context,
21};
22
23pub use nami_core::{Signal, SignalIdentity};
24
25/// A trait for converting a value into a computation.
26pub trait IntoSignal<Output> {
27    /// The specific computation type that will be produced.
28    type Signal: Signal<Output = Output>;
29
30    /// Convert this value into a computation.
31    fn into_signal(self) -> Self::Signal;
32}
33
34/// A trait for converting a value directly into a `Computed<Output>`.
35///
36/// This is a convenience trait that builds on `IntoSignal`.
37pub trait IntoComputed<Output>: IntoSignal<Output> + 'static {
38    /// Convert this value into a `Computed<Output>`.
39    fn into_computed(self) -> Computed<Output>;
40}
41
42/// Blanket implementation of `IntoSignal` for any type that implements `Signal`.
43///
44/// This allows for automatic conversion between compatible computation types.
45impl<C, Output> IntoSignal<Output> for C
46where
47    C: Signal,
48    C::Output: 'static + Clone,
49    Output: From<C::Output> + 'static,
50{
51    type Signal = Map<C, fn(C::Output) -> Output, Output>;
52
53    /// Convert this computation into one that produces the desired output type.
54    fn into_signal(self) -> Self::Signal {
55        map(self, core::convert::Into::into)
56    }
57}
58
59/// Blanket implementation of `IntoComputed` for any type that implements `IntoSignal`.
60impl<C, Output> IntoComputed<Output> for C
61where
62    C: IntoSignal<Output> + 'static,
63    C::Signal: Clone + 'static,
64{
65    /// Convert this value into a `Computed<Output>`.
66    fn into_computed(self) -> Computed<Output> {
67        Computed::new(self.into_signal())
68    }
69}
70
71/// A wrapper for a computation that attaches additional metadata.
72///
73/// This can be used to carry extra information alongside a computation.
74#[derive(Debug, Clone)]
75pub struct WithMetadata<C, T> {
76    /// The metadata to be associated with the computation.
77    metadata: T,
78
79    /// The underlying computation.
80    signal: C,
81    discriminator: usize,
82}
83
84impl<C, T: 'static> WithMetadata<C, T> {
85    /// Create a new computation with associated metadata.
86    #[track_caller]
87    pub fn new(metadata: T, signal: C) -> Self {
88        Self {
89            metadata,
90            signal,
91            discriminator: SignalIdentity::call_site_discriminator::<T>(Location::caller()),
92        }
93    }
94}
95
96/// Implementation of `signal` for `WithMetadata`.
97///
98/// This delegates the computation to the wrapped value but enriches
99/// the watcher notifications with the metadata.
100impl<C: Signal, T: Clone + 'static> Signal for WithMetadata<C, T> {
101    type Output = C::Output;
102    type Guard = C::Guard;
103
104    /// Execute the underlying computation.
105    fn get(&self) -> Self::Output {
106        self.signal.get()
107    }
108
109    fn identity(&self) -> Option<SignalIdentity> {
110        self.signal
111            .identity()
112            .map(|identity| identity.with_discriminator(self.discriminator))
113    }
114
115    /// Register a watcher, enriching notifications with the metadata.
116    fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
117        let with = self.metadata.clone();
118        self.signal
119            .watch(move |context: Context<<C as Signal>::Output>| {
120                watcher(context.with(with.clone()));
121            })
122    }
123}
124
125impl_signal_wrapper_ops!(WithMetadata<C, T>, [C, T], C);