freya_radio/hooks/
use_radio.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    hash::Hash,
5    ops::{
6        Deref,
7        DerefMut,
8    },
9    rc::Rc,
10};
11
12use freya_core::{
13    integration::FxHashSet,
14    prelude::*,
15};
16
17#[cfg(feature = "tracing")]
18pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
19    fn derive_channel(self, _radio: &T) -> Vec<Self> {
20        vec![self]
21    }
22}
23
24#[cfg(not(feature = "tracing"))]
25pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
26    fn derive_channel(self, _radio: &T) -> Vec<Self> {
27        vec![self]
28    }
29}
30
31/// Holds a global state and all its subscribers.
32pub struct RadioStation<Value, Channel>
33where
34    Channel: RadioChannel<Value>,
35    Value: 'static,
36{
37    value: State<Value>,
38    listeners: State<HashMap<Channel, Rc<RefCell<FxHashSet<ReactiveContext>>>>>,
39}
40
41impl<Value, Channel> Clone for RadioStation<Value, Channel>
42where
43    Channel: RadioChannel<Value>,
44{
45    fn clone(&self) -> Self {
46        *self
47    }
48}
49
50impl<Value, Channel> Copy for RadioStation<Value, Channel> where Channel: RadioChannel<Value> {}
51
52impl<Value, Channel> RadioStation<Value, Channel>
53where
54    Channel: RadioChannel<Value>,
55{
56    pub(crate) fn create(init_value: Value) -> Self {
57        RadioStation {
58            value: State::create(init_value),
59            listeners: State::create(HashMap::default()),
60        }
61    }
62
63    /// Create a [RadioStation] expected to live until the end of the process.
64    pub fn create_global(init_value: Value) -> Self {
65        RadioStation {
66            value: State::create_global(init_value),
67            listeners: State::create_global(HashMap::default()),
68        }
69    }
70
71    pub(crate) fn is_listening(
72        &self,
73        channel: &Channel,
74        reactive_context: &ReactiveContext,
75    ) -> bool {
76        let listeners = self.listeners.peek();
77        listeners
78            .get(channel)
79            .map(|contexts| contexts.borrow().contains(reactive_context))
80            .unwrap_or_default()
81    }
82
83    pub(crate) fn listen(&self, channel: Channel, mut reactive_context: ReactiveContext) {
84        let mut listeners = self.listeners.write_unchecked();
85        let listeners = listeners.entry(channel).or_default();
86        reactive_context.subscribe(listeners);
87    }
88
89    pub(crate) fn notify_listeners(&self, channel: &Channel) {
90        let listeners = self.listeners.write_unchecked();
91
92        #[cfg(feature = "tracing")]
93        tracing::info!("Notifying {channel:?}");
94
95        for (listener_channel, listeners) in listeners.iter() {
96            if listener_channel == channel {
97                for reactive_context in listeners.borrow().iter() {
98                    reactive_context.notify();
99                }
100            }
101        }
102    }
103
104    /// Read the current state value. This effectively subscribes to any change no matter the channel.
105    ///
106    /// Example:
107    ///
108    /// ```rs
109    /// let value = radio.read();
110    /// ```
111    pub fn read(&'_ self) -> ReadRef<'_, Value> {
112        self.value.read()
113    }
114
115    /// Read the current state value without subscribing.
116    ///
117    /// Example:
118    ///
119    /// ```rs
120    /// let value = radio.peek();
121    /// ```
122    pub fn peek(&'_ self) -> ReadRef<'_, Value> {
123        self.value.peek()
124    }
125
126    pub(crate) fn cleanup(&self) {
127        let mut listeners = self.listeners.write_unchecked();
128
129        // Clean up those channels with no reactive contexts
130        listeners.retain(|_, listeners| !listeners.borrow().is_empty());
131
132        #[cfg(feature = "tracing")]
133        {
134            use itertools::Itertools;
135            use tracing::{
136                Level,
137                info,
138                span,
139            };
140
141            let mut channels_subscribers = HashMap::<&Channel, usize>::new();
142
143            for (channel, listeners) in listeners.iter() {
144                *channels_subscribers.entry(&channel).or_default() = listeners.borrow().len();
145            }
146
147            let span = span!(Level::DEBUG, "Radio Station Metrics");
148            let _enter = span.enter();
149
150            for (channel, count) in channels_subscribers.iter().sorted() {
151                info!(" {count} subscribers for {channel:?}")
152            }
153        }
154    }
155
156    /// Modify the state using a custom Channel.
157    ///
158    /// ## Example:
159    /// ```rs, no_run
160    /// radio_station.write(Channel::Whatever).value = 1;
161    /// ```
162    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
163        let value = self.value.write_unchecked();
164        RadioGuard {
165            channels: channel.clone().derive_channel(&*value),
166            station: *self,
167            value,
168        }
169    }
170}
171
172pub struct RadioAntenna<Value, Channel>
173where
174    Channel: RadioChannel<Value>,
175    Value: 'static,
176{
177    pub(crate) channel: Channel,
178    station: RadioStation<Value, Channel>,
179}
180
181impl<Value, Channel> RadioAntenna<Value, Channel>
182where
183    Channel: RadioChannel<Value>,
184{
185    pub(crate) fn new(
186        channel: Channel,
187        station: RadioStation<Value, Channel>,
188    ) -> RadioAntenna<Value, Channel> {
189        RadioAntenna { channel, station }
190    }
191}
192impl<Value, Channel> Clone for RadioAntenna<Value, Channel>
193where
194    Channel: RadioChannel<Value>,
195{
196    fn clone(&self) -> Self {
197        Self {
198            channel: self.channel.clone(),
199            station: self.station,
200        }
201    }
202}
203
204pub struct RadioGuard<Value, Channel>
205where
206    Channel: RadioChannel<Value>,
207    Value: 'static,
208{
209    station: RadioStation<Value, Channel>,
210    channels: Vec<Channel>,
211    value: WriteRef<'static, Value>,
212}
213
214impl<Value, Channel> Drop for RadioGuard<Value, Channel>
215where
216    Channel: RadioChannel<Value>,
217{
218    fn drop(&mut self) {
219        for channel in &mut self.channels {
220            self.station.notify_listeners(channel)
221        }
222        if !self.channels.is_empty() {
223            self.station.cleanup();
224        }
225    }
226}
227
228impl<Value, Channel> Deref for RadioGuard<Value, Channel>
229where
230    Channel: RadioChannel<Value>,
231{
232    type Target = WriteRef<'static, Value>;
233
234    fn deref(&self) -> &Self::Target {
235        &self.value
236    }
237}
238
239impl<Value, Channel> DerefMut for RadioGuard<Value, Channel>
240where
241    Channel: RadioChannel<Value>,
242{
243    fn deref_mut(&mut self) -> &mut WriteRef<'static, Value> {
244        &mut self.value
245    }
246}
247
248/// `Radio` lets you access the state and is subscribed given it's `Channel`.
249pub struct Radio<Value, Channel>
250where
251    Channel: RadioChannel<Value>,
252    Value: 'static,
253{
254    antenna: State<RadioAntenna<Value, Channel>>,
255}
256
257impl<Value, Channel> Clone for Radio<Value, Channel>
258where
259    Channel: RadioChannel<Value>,
260{
261    fn clone(&self) -> Self {
262        *self
263    }
264}
265impl<Value, Channel> Copy for Radio<Value, Channel> where Channel: RadioChannel<Value> {}
266
267impl<Value, Channel> PartialEq for Radio<Value, Channel>
268where
269    Channel: RadioChannel<Value>,
270{
271    fn eq(&self, other: &Self) -> bool {
272        self.antenna == other.antenna
273    }
274}
275
276impl<Value, Channel> MutView<'static, Value> for Radio<Value, Channel>
277where
278    Channel: RadioChannel<Value>,
279{
280    fn read(&mut self) -> ReadRef<'static, Value> {
281        self.subscribe_if_not();
282        self.antenna.peek().station.value.peek()
283    }
284
285    fn peek(&mut self) -> ReadRef<'static, Value> {
286        self.antenna.peek().station.value.peek()
287    }
288
289    fn write(&mut self) -> WriteRef<'static, Value> {
290        let value = self.antenna.peek().station.value.write_unchecked();
291        let channel = self.antenna.peek().channel.clone();
292        for channel in channel.derive_channel(&value) {
293            self.antenna.peek().station.notify_listeners(&channel)
294        }
295        value
296    }
297
298    fn write_if(&mut self, with: impl FnOnce(WriteRef<'static, Value>) -> bool) {
299        let changed = with(self.antenna.peek().station.value.write_unchecked());
300        let channel = changed.then_some(self.antenna.peek().channel.clone());
301        if let Some(channel) = channel {
302            let value = self.antenna.peek().station.value.write_unchecked();
303            for channel in channel.derive_channel(&value) {
304                self.antenna.peek().station.notify_listeners(&channel)
305            }
306            self.antenna.peek().station.cleanup();
307        }
308    }
309}
310
311impl<Value, Channel> Radio<Value, Channel>
312where
313    Channel: RadioChannel<Value>,
314{
315    pub(crate) fn new(antenna: State<RadioAntenna<Value, Channel>>) -> Radio<Value, Channel> {
316        Radio { antenna }
317    }
318
319    pub(crate) fn subscribe_if_not(&self) {
320        if let Some(rc) = ReactiveContext::try_current() {
321            let antenna = &self.antenna.write_unchecked();
322            let channel = antenna.channel.clone();
323            let is_listening = antenna.station.is_listening(&channel, &rc);
324
325            // Subscribe the reader reactive context to the channel if it wasn't already
326            if !is_listening {
327                antenna.station.listen(channel, rc);
328            }
329        }
330    }
331
332    /// Read the current state value.
333    ///
334    /// Example:
335    ///
336    /// ```rs
337    /// let value = radio.read();
338    /// ```
339    pub fn read(&'_ self) -> ReadRef<'_, Value> {
340        self.subscribe_if_not();
341        self.antenna.peek().station.value.peek()
342    }
343
344    /// Read the current state value inside a callback.
345    ///
346    /// Example:
347    ///
348    /// ```rs
349    /// radio.with(|value| {
350    ///     // Do something with `value`
351    /// });
352    /// ```
353    pub fn with(&self, cb: impl FnOnce(ReadRef<Value>)) {
354        self.subscribe_if_not();
355        let value = self.antenna.peek().station.value;
356        let borrow = value.read();
357        cb(borrow);
358    }
359
360    /// Modify the state using the channel this radio was created with.
361    ///
362    /// Example:
363    ///
364    /// ```rs
365    /// radio.write().value = 1;
366    /// ```
367    pub fn write(&mut self) -> RadioGuard<Value, Channel> {
368        let value = self.antenna.peek().station.value.write_unchecked();
369        let channel = self.antenna.peek().channel.clone();
370        RadioGuard {
371            channels: channel.derive_channel(&*value),
372            station: self.antenna.read().station,
373            value,
374        }
375    }
376
377    /// Get a mutable reference to the current state value, inside a callback.
378    ///
379    /// Example:
380    ///
381    /// ```rs
382    /// radio.write_with(|value| {
383    ///     // Modify `value`
384    /// });
385    /// ```
386    pub fn write_with(&mut self, cb: impl FnOnce(RadioGuard<Value, Channel>)) {
387        let guard = self.write();
388        cb(guard);
389    }
390
391    /// Modify the state using a custom Channel.
392    ///
393    /// ## Example:
394    /// ```rs, no_run
395    /// radio.write(Channel::Whatever).value = 1;
396    /// ```
397    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
398        let value = self.antenna.peek().station.value.write_unchecked();
399        RadioGuard {
400            channels: channel.derive_channel(&*value),
401            station: self.antenna.read().station,
402            value,
403        }
404    }
405
406    /// Get a mutable reference to the current state value, inside a callback.
407    ///
408    /// Example:
409    ///
410    /// ```rs
411    /// radio.write_channel_with(Channel::Whatever, |value| {
412    ///     // Modify `value`
413    /// });
414    /// ```
415    pub fn write_channel_with(
416        &mut self,
417        channel: Channel,
418        cb: impl FnOnce(RadioGuard<Value, Channel>),
419    ) {
420        let guard = self.write_channel(channel);
421        cb(guard);
422    }
423
424    /// Get a mutable reference to the current state value, inside a callback that returns the channel to be used.
425    ///
426    /// Example:
427    ///
428    /// ```rs
429    /// radio.write_with_channel_selection(|value| {
430    ///     // Modify `value`
431    ///     if value.cool {
432    ///         ChannelSelection::Select(Channel::Whatever)
433    ///     } else {
434    ///         ChannelSelection::Silence
435    ///     }
436    /// });
437    /// ```
438    pub fn write_with_channel_selection(
439        &mut self,
440        cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
441    ) -> ChannelSelection<Channel> {
442        let value = self.antenna.peek().station.value.write_unchecked();
443        let mut guard = RadioGuard {
444            channels: Vec::default(),
445            station: self.antenna.read().station,
446            value,
447        };
448        let channel_selection = cb(&mut guard.value);
449        let channel = match channel_selection.clone() {
450            ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
451            ChannelSelection::Silence => None,
452            ChannelSelection::Select(c) => Some(c),
453        };
454        if let Some(channel) = channel {
455            for channel in channel.derive_channel(&guard.value) {
456                self.antenna.peek().station.notify_listeners(&channel)
457            }
458            self.antenna.peek().station.cleanup();
459        }
460
461        channel_selection
462    }
463
464    /// Modify the state silently, no component will be notified.
465    ///
466    /// This is not recommended, the only intended usage for this is inside [RadioAsyncReducer].
467    pub fn write_silently(&mut self) -> RadioGuard<Value, Channel> {
468        let value = self.antenna.peek().station.value.write_unchecked();
469        RadioGuard {
470            channels: Vec::default(),
471            station: self.antenna.read().station,
472            value,
473        }
474    }
475}
476
477impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
478
479#[derive(Clone)]
480pub enum ChannelSelection<Channel> {
481    /// Notify the channel associated with the used [Radio].
482    Current,
483    /// Notify a given `Channel`.
484    Select(Channel),
485    /// No subscriber will be notified.
486    Silence,
487}
488
489impl<Channel> ChannelSelection<Channel> {
490    /// Change to [ChannelSelection::Current]
491    pub fn current(&mut self) {
492        *self = Self::Current
493    }
494
495    /// Change to [ChannelSelection::Select]
496    pub fn select(&mut self, channel: Channel) {
497        *self = Self::Select(channel)
498    }
499
500    /// Change to [ChannelSelection::Silence]
501    pub fn silence(&mut self) {
502        *self = Self::Silence
503    }
504
505    /// Check if it is of type [ChannelSelection::Current]
506    pub fn is_current(&self) -> bool {
507        matches!(self, Self::Current)
508    }
509
510    /// Check if it is of type [ChannelSelection::Select] and return the channel.
511    pub fn is_select(&self) -> Option<&Channel> {
512        match self {
513            Self::Select(channel) => Some(channel),
514            _ => None,
515        }
516    }
517
518    /// Check if it is of type [ChannelSelection::Silence]
519    pub fn is_silence(&self) -> bool {
520        matches!(self, Self::Silence)
521    }
522}
523
524/// Provide the given [RadioStation] to descendants components, this is the same as when
525/// using [use_init_radio_station] except that you pass an already created one.
526///
527/// This is specially useful to share the same [RadioStation] across different windows.
528pub fn use_share_radio<Value, Channel>(radio: impl FnOnce() -> RadioStation<Value, Channel>)
529where
530    Channel: RadioChannel<Value>,
531    Value: 'static,
532{
533    use_provide_context(radio);
534}
535
536/// Consume the state and subscribe using the given `channel`
537/// Any mutation using this radio will notify other subscribers to the same `channel`,
538/// unless you explicitely pass a custom channel using other methods as [`Radio::write_channel()`]
539pub fn use_radio<Value, Channel>(channel: Channel) -> Radio<Value, Channel>
540where
541    Channel: RadioChannel<Value>,
542    Value: 'static,
543{
544    let station = use_consume::<RadioStation<Value, Channel>>();
545
546    let mut radio = use_hook(|| {
547        let antenna = RadioAntenna::new(channel.clone(), station);
548        Radio::new(State::create(antenna))
549    });
550
551    if radio.antenna.peek().channel != channel {
552        radio.antenna.write().channel = channel;
553    }
554
555    radio
556}
557
558pub fn use_init_radio_station<Value, Channel>(
559    init_value: impl FnOnce() -> Value,
560) -> RadioStation<Value, Channel>
561where
562    Channel: RadioChannel<Value>,
563    Value: 'static,
564{
565    use_provide_context(|| RadioStation::create(init_value()))
566}
567
568pub fn use_radio_station<Value, Channel>() -> RadioStation<Value, Channel>
569where
570    Channel: RadioChannel<Value>,
571    Value: 'static,
572{
573    use_consume::<RadioStation<Value, Channel>>()
574}
575
576pub trait DataReducer {
577    type Channel;
578    type Action;
579
580    fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
581}
582
583pub trait RadioReducer {
584    type Action;
585    type Channel;
586
587    fn apply(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
588}
589
590impl<Data: DataReducer<Channel = Channel, Action = Action>, Channel: RadioChannel<Data>, Action>
591    RadioReducer for Radio<Data, Channel>
592{
593    type Action = Action;
594    type Channel = Channel;
595
596    fn apply(&mut self, action: Action) -> ChannelSelection<Channel> {
597        self.write_with_channel_selection(|data| data.reduce(action))
598    }
599}
600
601pub trait DataAsyncReducer {
602    type Channel;
603    type Action;
604
605    #[allow(async_fn_in_trait)]
606    async fn async_reduce(
607        _radio: &mut Radio<Self, Self::Channel>,
608        _action: Self::Action,
609    ) -> ChannelSelection<Self::Channel>
610    where
611        Self::Channel: RadioChannel<Self>,
612        Self: Sized;
613}
614
615pub trait RadioAsyncReducer {
616    type Action;
617
618    fn async_apply(&mut self, _action: Self::Action)
619    where
620        Self::Action: 'static;
621}
622
623impl<
624    Data: DataAsyncReducer<Channel = Channel, Action = Action>,
625    Channel: RadioChannel<Data>,
626    Action,
627> RadioAsyncReducer for Radio<Data, Channel>
628{
629    type Action = Action;
630
631    fn async_apply(&mut self, action: Self::Action)
632    where
633        Self::Action: 'static,
634    {
635        let mut radio = *self;
636        spawn(async move {
637            let channel = Data::async_reduce(&mut radio, action).await;
638            radio.write_with_channel_selection(|_| channel);
639        });
640    }
641}