maycoon_core/signal/
mod.rs

1use crate::app::context::AppContext;
2use crate::reference::Ref;
3use crate::signal::fixed::FixedSignal;
4use crate::signal::listener::Listener;
5use crate::signal::map::MapSignal;
6use std::rc::Rc;
7
8/// Contains the [FixedSignal] signal.
9pub mod fixed;
10
11/// Contains the [memoized::MemoizedSignal] signal.
12pub mod memoized;
13
14/// Contains the [state::StateSignal] signal.
15pub mod state;
16
17/// Contains the [MapSignal] signal.
18pub mod map;
19
20/// Contains the [eval::EvalSignal] signal.
21pub mod eval;
22
23/// Contains the [Listener] listener.
24pub mod listener;
25
26/// A [Signal] in a [Box].
27pub type BoxedSignal<T> = Box<dyn Signal<T>>;
28
29/// Base signal trait.
30///
31/// Signals store values of type `T` and notify listeners when they change.
32///
33/// **NOTE:** By default, signals don't have any listeners. To "hook" a signal into the application cycle, call [use_signal].
34///
35/// # Avoiding Borrowing Errors
36/// Be careful not to write something like `signal.set(*signal.get());`,
37/// as many signals that use [Rc] or [RefCell] might panic, due to the value already being borrowed
38/// (by the `signal.get();` call). Write `let value = *signal.get();` or `let value = signal.clone()`
39/// and then `signal.set(value);` instead.
40///
41/// [use_signal]: AppContext::use_signal
42/// [RefCell]: std::cell::RefCell
43pub trait Signal<T: 'static>: 'static {
44    /// Get a reference to the current value of the signal.
45    fn get(&self) -> Ref<'_, T>;
46
47    /// Set the value of the signal.
48    ///
49    /// **NOTE:** This does not notify listeners, use [set] instead.
50    fn set_value(&self, value: T);
51
52    /// Add a listener to the signal, which will be called when the inner value changes and returns the signal.
53    fn listen(self, listener: Box<dyn Fn(Ref<'_, T>)>) -> Self
54    where
55        Self: Sized;
56
57    /// Notify listeners that the inner value has changed.
58    /// May also be called manually to update listeners.
59    fn notify(&self);
60
61    /// Set the value of the signal and notify listeners.
62    #[inline(always)]
63    fn set(&self, value: T) {
64        tracing::trace_span!("set signal").in_scope(|| {
65            self.set_value(value);
66            self.notify();
67        });
68    }
69
70    /// Converts the signal into a [MaybeSignal].
71    #[inline(always)]
72    fn maybe(&self) -> MaybeSignal<T>
73    where
74        Self: Sized,
75    {
76        MaybeSignal::signal(self.dyn_clone())
77    }
78
79    /// Converts this signal into a [MapSignal] and applies the given mapping function.
80    #[inline(always)]
81    fn map<U: 'static>(&self, map: impl Fn(Ref<T>) -> Ref<U> + 'static) -> MapSignal<T, U>
82    where
83        Self: Sized,
84    {
85        MapSignal::new(self.dyn_clone(), map)
86    }
87
88    /// Hooks the signal into the given [AppContext].
89    ///
90    /// Required for the signal to become reactive with the app lifecycle.
91    #[inline(always)]
92    fn hook(self, context: &AppContext) -> Self
93    where
94        Self: Sized,
95    {
96        context.use_signal(self)
97    }
98
99    /// Clones the signal into a `Box<dyn Signal<T>>`.
100    ///
101    /// This is an object-safe alternative to simply making the signal [Clone]-dependent.
102    fn dyn_clone(&self) -> Box<dyn Signal<T>>;
103}
104
105/// A value which may be a signal or a fixed value.
106pub enum MaybeSignal<T: 'static> {
107    /// A signal.
108    Signal(BoxedSignal<T>),
109    /// A fixed value wrapped inside a [Rc].
110    Value(Rc<T>),
111}
112
113impl<T: 'static> MaybeSignal<T> {
114    /// Wrap a [Signal] inside a [MaybeSignal].
115    #[inline(always)]
116    pub const fn signal(signal: BoxedSignal<T>) -> Self {
117        Self::Signal(signal)
118    }
119
120    /// Wrap a value inside a [MaybeSignal].
121    #[inline(always)]
122    pub fn value(value: T) -> Self {
123        Self::Value(Rc::new(value))
124    }
125
126    /// Get a reference to the current value.
127    ///
128    /// If the value is a signal, the signal's current value is returned,
129    /// otherwise a [Ref::Rc] of the value is returned.
130    #[inline(always)]
131    pub fn get(&self) -> Ref<'_, T> {
132        match self {
133            MaybeSignal::Signal(signal) => signal.get(),
134            MaybeSignal::Value(value) => Ref::Borrow(value.as_ref()),
135        }
136    }
137
138    /// Converts the [MaybeSignal] into an [BoxedSignal] if it is a [MaybeSignal::Signal].
139    #[inline(always)]
140    pub fn as_signal(&self) -> Option<BoxedSignal<T>> {
141        match self {
142            MaybeSignal::Signal(signal) => Some(signal.dyn_clone()),
143            _ => None,
144        }
145    }
146
147    /// Converts the [MaybeSignal] into a [BoxedSignal].
148    ///
149    /// If the value is a [MaybeSignal::Value], a [FixedSignal] is created.
150    #[inline(always)]
151    pub fn into_signal(self) -> BoxedSignal<T> {
152        match self {
153            MaybeSignal::Signal(signal) => signal,
154            MaybeSignal::Value(value) => Box::new(FixedSignal::from(value)),
155        }
156    }
157
158    /// Applies the given mapping function to the signal.
159    ///
160    /// Returns a [MaybeSignal] containing a [MapSignal] which maps the inner value of the signal.
161    #[inline(always)]
162    pub fn map<U: 'static>(self, map: impl Fn(Ref<T>) -> Ref<U> + 'static) -> MaybeSignal<U> {
163        let signal = self.into_signal();
164
165        MaybeSignal::signal(Box::new(MapSignal::new(signal, map)))
166    }
167}
168
169impl<T: Default + 'static> Default for MaybeSignal<T> {
170    #[inline(always)]
171    fn default() -> Self {
172        Self::value(T::default())
173    }
174}
175
176impl<T: 'static> From<T> for MaybeSignal<T> {
177    #[inline(always)]
178    fn from(value: T) -> Self {
179        Self::value(value)
180    }
181}
182
183impl<T: 'static> From<BoxedSignal<T>> for MaybeSignal<T> {
184    #[inline(always)]
185    fn from(signal: BoxedSignal<T>) -> Self {
186        Self::signal(signal)
187    }
188}
189
190impl<'a, T: 'static> From<&'a BoxedSignal<T>> for MaybeSignal<T> {
191    #[inline(always)]
192    fn from(value: &'a BoxedSignal<T>) -> Self {
193        Self::signal(value.dyn_clone())
194    }
195}
196
197impl<T: 'static, U: 'static> From<MapSignal<T, U>> for MaybeSignal<U> {
198    #[inline(always)]
199    fn from(value: MapSignal<T, U>) -> Self {
200        Self::signal(Box::new(value))
201    }
202}