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/// Defines a channel for radio communication.
25/// Channels are used to subscribe to specific changes in the global state.
26/// Each channel must implement this trait to be used with [`RadioStation`] and [`Radio`].
27///
28/// Channels allow fine-grained control over which components re-render when the state changes.
29/// Components only re-render when a channel they are subscribed to is notified.
30///
31/// # Example
32///
33/// ```rust, no_run
34/// # use freya::radio::*;
35///
36/// # struct Data;
37///
38/// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
39/// pub enum DataChannel {
40///     ListCreation,
41///     SpecificListItemUpdate(usize),
42/// }
43///
44/// impl RadioChannel<Data> for DataChannel {}
45/// ```
46#[cfg(not(feature = "tracing"))]
47pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
48    /// Derive additional channels based on the current state value.
49    /// This allows a single write operation to notify multiple channels.
50    ///
51    /// By default, returns a vector containing only `self`.
52    ///
53    /// # Example
54    ///
55    /// ```rust, no_run
56    /// # use freya::radio::*;
57    ///
58    /// # struct Data;
59    ///
60    /// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
61    /// pub enum DataChannel {
62    ///     All,
63    ///     Specific(usize),
64    /// }
65    ///
66    /// impl RadioChannel<Data> for DataChannel {
67    ///     fn derive_channel(self, _data: &Data) -> Vec<Self> {
68    ///         match self {
69    ///             DataChannel::All => vec![DataChannel::All],
70    ///             DataChannel::Specific(id) => vec![DataChannel::All, DataChannel::Specific(id)],
71    ///         }
72    ///     }
73    /// }
74    /// ```
75    fn derive_channel(self, _radio: &T) -> Vec<Self> {
76        vec![self]
77    }
78}
79
80/// The central hub for global state management in Freya applications.
81/// A `RadioStation` holds the global state value and manages subscriptions to different channels.
82/// Components can subscribe to specific channels to receive notifications when the state changes.
83///
84/// RadioStations can be shared across multiple windows or components using [`use_share_radio`].
85///
86/// # Examples
87///
88/// ## Basic usage
89///
90/// ```rust, no_run
91/// # use freya::prelude::*;
92/// # use freya::radio::*;
93///
94/// #[derive(Default)]
95/// struct AppState {
96///     count: i32,
97/// }
98///
99/// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
100/// enum AppChannel {
101///     Count,
102/// }
103///
104/// impl RadioChannel<AppState> for AppChannel {}
105///
106/// fn app() -> impl IntoElement {
107///     // Create a radio station (scoped to this component tree)
108///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
109///
110///     let mut radio = use_radio(AppChannel::Count);
111///
112///     rect()
113///         .child(label().text(format!("Count: {}", radio.read().count)))
114///         .child(
115///             Button::new()
116///                 .on_press(move |_| radio.write().count += 1)
117///                 .child("Increment"),
118///         )
119/// }
120/// ```
121///
122/// ## Global radio station for multi-window apps
123///
124/// ```rust, ignore
125/// # use freya::prelude::*;
126/// # use freya::radio::*;
127///
128/// let radio_station = RadioStation::create_global(AppState::default);
129///
130/// launch(
131///     LaunchConfig::new()
132///         .with_window(WindowConfig::new(Window1 { radio_station }))
133///         .with_window(WindowConfig::new(Window2 { radio_station })),
134/// );
135/// ```
136pub struct RadioStation<Value, Channel>
137where
138    Channel: RadioChannel<Value>,
139    Value: 'static,
140{
141    pub(crate) value: State<Value>,
142    listeners: State<HashMap<Channel, Rc<RefCell<FxHashSet<ReactiveContext>>>>>,
143}
144
145impl<Value, Channel> Clone for RadioStation<Value, Channel>
146where
147    Channel: RadioChannel<Value>,
148{
149    fn clone(&self) -> Self {
150        *self
151    }
152}
153
154impl<Value, Channel> Copy for RadioStation<Value, Channel> where Channel: RadioChannel<Value> {}
155
156impl<Value, Channel> RadioStation<Value, Channel>
157where
158    Channel: RadioChannel<Value>,
159{
160    pub(crate) fn create(init_value: Value) -> Self {
161        RadioStation {
162            value: State::create(init_value),
163            listeners: State::create(HashMap::default()),
164        }
165    }
166
167    /// Create a global `RadioStation` that lives for the entire application lifetime.
168    /// This is useful for sharing state across multiple windows.
169    ///
170    /// You would usually want to call this in your `main` function, not anywhere else.
171    ///
172    /// # Example
173    ///
174    /// ```rust, ignore
175    /// # use freya::prelude::*;
176    /// # use freya::radio::*;
177    ///
178    /// let radio_station = RadioStation::create_global(AppState::default);
179    ///
180    /// launch(
181    ///     LaunchConfig::new()
182    ///         .with_window(WindowConfig::new(Window1 { radio_station }))
183    ///         .with_window(WindowConfig::new(Window2 { radio_station })),
184    /// );
185    /// ```
186    pub fn create_global(init_value: Value) -> Self {
187        RadioStation {
188            value: State::create_global(init_value),
189            listeners: State::create_global(HashMap::default()),
190        }
191    }
192
193    pub(crate) fn is_listening(
194        &self,
195        channel: &Channel,
196        reactive_context: &ReactiveContext,
197    ) -> bool {
198        let listeners = self.listeners.peek();
199        listeners
200            .get(channel)
201            .map(|contexts| contexts.borrow().contains(reactive_context))
202            .unwrap_or_default()
203    }
204
205    pub(crate) fn listen(&self, channel: Channel, mut reactive_context: ReactiveContext) {
206        let mut listeners = self.listeners.write_unchecked();
207        let listeners = listeners.entry(channel).or_default();
208        reactive_context.subscribe(listeners);
209    }
210
211    pub(crate) fn notify_listeners(&self, channel: &Channel) {
212        let listeners = self.listeners.write_unchecked();
213
214        #[cfg(feature = "tracing")]
215        tracing::info!("Notifying {channel:?}");
216
217        for (listener_channel, listeners) in listeners.iter() {
218            if listener_channel == channel {
219                for reactive_context in listeners.borrow().iter() {
220                    reactive_context.notify();
221                }
222            }
223        }
224    }
225
226    /// Read the current state value and subscribe to all channel changes.
227    /// Any component calling this will re-render when any channel is notified.
228    ///
229    /// # Example
230    ///
231    /// ```rust, ignore
232    /// # use freya::radio::*;
233    /// let value = radio_station.read();
234    /// ```
235    pub fn read(&'_ self) -> ReadRef<'_, Value> {
236        self.value.read()
237    }
238
239    pub fn peek_unchecked(&self) -> ReadRef<'static, Value> {
240        self.value.peek()
241    }
242
243    pub fn write_unchecked(&self) -> WriteRef<'static, Value> {
244        self.value.write_unchecked()
245    }
246
247    /// Read the current state value without subscribing to changes.
248    /// Components using this will not re-render when the state changes.
249    ///
250    /// # Example
251    ///
252    /// ```rust, ignore
253    /// # use freya::radio::*;
254    /// let value = radio_station.peek();
255    /// ```
256    pub fn peek(&'_ self) -> ReadRef<'_, Value> {
257        self.value.peek()
258    }
259
260    pub(crate) fn cleanup(&self) {
261        let mut listeners = self.listeners.write_unchecked();
262
263        // Clean up those channels with no reactive contexts
264        listeners.retain(|_, listeners| !listeners.borrow().is_empty());
265
266        #[cfg(feature = "tracing")]
267        {
268            use itertools::Itertools;
269            use tracing::{
270                Level,
271                info,
272                span,
273            };
274
275            let mut channels_subscribers = HashMap::<&Channel, usize>::new();
276
277            for (channel, listeners) in listeners.iter() {
278                *channels_subscribers.entry(&channel).or_default() = listeners.borrow().len();
279            }
280
281            let span = span!(Level::DEBUG, "Radio Station Metrics");
282            let _enter = span.enter();
283
284            for (channel, count) in channels_subscribers.iter().sorted() {
285                info!(" {count} subscribers for {channel:?}")
286            }
287        }
288    }
289
290    /// Modify the state using a specific channel.
291    /// This will notify all subscribers to that channel (and any derived channels).
292    ///
293    /// Returns a [`RadioGuard`] that allows direct mutation of the state.
294    /// The guard automatically notifies listeners when dropped.
295    ///
296    /// # Example
297    ///
298    /// ```rust, ignore
299    /// # use freya::radio::*;
300    /// radio_station.write_channel(MyChannel::Update).count += 1;
301    /// ```
302    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
303        let value = self.value.write_unchecked();
304        RadioGuard {
305            channels: channel.clone().derive_channel(&*value),
306            station: *self,
307            value,
308        }
309    }
310}
311
312pub struct RadioAntenna<Value, Channel>
313where
314    Channel: RadioChannel<Value>,
315    Value: 'static,
316{
317    pub(crate) channel: Channel,
318    pub(crate) station: RadioStation<Value, Channel>,
319}
320
321impl<Value, Channel> RadioAntenna<Value, Channel>
322where
323    Channel: RadioChannel<Value>,
324{
325    pub(crate) fn new(
326        channel: Channel,
327        station: RadioStation<Value, Channel>,
328    ) -> RadioAntenna<Value, Channel> {
329        RadioAntenna { channel, station }
330    }
331}
332impl<Value, Channel> Clone for RadioAntenna<Value, Channel>
333where
334    Channel: RadioChannel<Value>,
335{
336    fn clone(&self) -> Self {
337        Self {
338            channel: self.channel.clone(),
339            station: self.station,
340        }
341    }
342}
343
344pub struct RadioGuard<Value, Channel>
345where
346    Channel: RadioChannel<Value>,
347    Value: 'static,
348{
349    pub(crate) station: RadioStation<Value, Channel>,
350    pub(crate) channels: Vec<Channel>,
351    pub(crate) value: WriteRef<'static, Value>,
352}
353
354impl<Value, Channel> Drop for RadioGuard<Value, Channel>
355where
356    Channel: RadioChannel<Value>,
357{
358    fn drop(&mut self) {
359        for channel in &mut self.channels {
360            self.station.notify_listeners(channel)
361        }
362        if !self.channels.is_empty() {
363            self.station.cleanup();
364        }
365    }
366}
367
368impl<Value, Channel> Deref for RadioGuard<Value, Channel>
369where
370    Channel: RadioChannel<Value>,
371{
372    type Target = WriteRef<'static, Value>;
373
374    fn deref(&self) -> &Self::Target {
375        &self.value
376    }
377}
378
379impl<Value, Channel> DerefMut for RadioGuard<Value, Channel>
380where
381    Channel: RadioChannel<Value>,
382{
383    fn deref_mut(&mut self) -> &mut WriteRef<'static, Value> {
384        &mut self.value
385    }
386}
387
388/// A reactive handle to the global state for a specific channel.
389/// `Radio` provides methods to read and write the global state, and automatically subscribes
390/// the current component to re-render when the associated channel is notified.
391///
392/// Each `Radio` instance is tied to a specific channel, allowing fine-grained control
393/// over which components update when the state changes.
394///
395/// # Examples
396///
397/// ## Basic usage
398///
399/// ```rust, ignore
400/// # use freya::prelude::*;
401/// # use freya::radio::*;
402///
403/// #[derive(PartialEq)]
404/// struct MyComponent {}
405///
406/// impl Component for MyComponent {
407///     fn render(&self) -> impl IntoElement {
408///         let mut radio = use_radio(MyChannel::Count);
409///
410///         rect()
411///             .child(label().text(format!("Count: {}", radio.read().count)))
412///             .child(
413///                 Button::new()
414///                     .on_press(move |_| radio.write().count += 1)
415///                     .child("Increment"),
416///             )
417///     }
418/// }
419/// ```
420///
421/// ## Using reducers
422///
423/// ```rust, ignore
424/// # use freya::prelude::*;
425/// # use freya::radio::*;
426///
427/// #[derive(Clone)]
428/// struct CounterState {
429///     count: i32,
430/// }
431///
432/// impl DataReducer for CounterState {
433///     type Channel = CounterChannel;
434///     type Action = CounterAction;
435///
436///     fn reduce(&mut self, action: CounterAction) -> ChannelSelection<CounterChannel> {
437///         match action {
438///             CounterAction::Increment => self.count += 1,
439///             CounterAction::Decrement => self.count -= 1,
440///         }
441///         ChannelSelection::Current
442///     }
443/// }
444///
445/// #[derive(PartialEq)]
446/// struct CounterComponent {}
447///
448/// impl Component for CounterComponent {
449///     fn render(&self) -> impl IntoElement {
450///         let mut radio = use_radio(CounterChannel::Count);
451///
452///         rect()
453///             .child(
454///                 Button::new()
455///                     .on_press(move |_| radio.apply(CounterAction::Increment))
456///                     .child("+"),
457///             )
458///             .child(label().text(format!("{}", radio.read().count)))
459///             .child(
460///                 Button::new()
461///                     .on_press(move |_| radio.apply(CounterAction::Decrement))
462///                     .child("-"),
463///             )
464///     }
465/// }
466/// ```
467pub struct Radio<Value, Channel>
468where
469    Channel: RadioChannel<Value>,
470    Value: 'static,
471{
472    pub(crate) antenna: State<RadioAntenna<Value, Channel>>,
473}
474
475impl<Value, Channel> Clone for Radio<Value, Channel>
476where
477    Channel: RadioChannel<Value>,
478{
479    fn clone(&self) -> Self {
480        *self
481    }
482}
483impl<Value, Channel> Copy for Radio<Value, Channel> where Channel: RadioChannel<Value> {}
484
485impl<Value, Channel> PartialEq for Radio<Value, Channel>
486where
487    Channel: RadioChannel<Value>,
488{
489    fn eq(&self, other: &Self) -> bool {
490        self.antenna == other.antenna
491    }
492}
493
494impl<Value, Channel> Radio<Value, Channel>
495where
496    Channel: RadioChannel<Value>,
497{
498    pub(crate) fn new(antenna: State<RadioAntenna<Value, Channel>>) -> Radio<Value, Channel> {
499        Radio { antenna }
500    }
501
502    pub(crate) fn subscribe_if_not(&self) {
503        if let Some(rc) = ReactiveContext::try_current() {
504            let antenna = &self.antenna.write_unchecked();
505            let channel = antenna.channel.clone();
506            let is_listening = antenna.station.is_listening(&channel, &rc);
507
508            // Subscribe the reader reactive context to the channel if it wasn't already
509            if !is_listening {
510                antenna.station.listen(channel, rc);
511            }
512        }
513    }
514
515    /// Read the current state value and subscribe the current component to changes
516    /// on this radio's channel. The component will re-render when this channel is notified.
517    ///
518    /// # Example
519    ///
520    /// ```rust, ignore
521    /// # use freya::radio::*;
522    /// let count = radio.read().count;
523    /// ```
524    pub fn read(&'_ self) -> ReadRef<'_, Value> {
525        self.subscribe_if_not();
526        self.antenna.peek().station.value.peek()
527    }
528
529    /// Read the current state value inside a callback.
530    ///
531    /// Example:
532    ///
533    /// ```rust, ignore
534    /// # use freya::radio::*;
535    /// radio.with(|value| {
536    ///     // Do something with `value`
537    /// });
538    /// ```
539    pub fn with(&self, cb: impl FnOnce(ReadRef<Value>)) {
540        self.subscribe_if_not();
541        let value = self.antenna.peek().station.value;
542        let borrow = value.read();
543        cb(borrow);
544    }
545
546    /// Get a mutable reference to the state for writing.
547    /// Changes will notify subscribers to this radio's channel.
548    ///
549    /// Returns a [`RadioGuard`] that allows direct mutation of the state.
550    ///
551    /// # Example
552    ///
553    /// ```rust, ignore
554    /// # use freya::radio::*;
555    /// radio.write().count += 1;
556    /// ```
557    pub fn write(&mut self) -> RadioGuard<Value, Channel> {
558        let value = self.antenna.peek().station.value.write_unchecked();
559        let channel = self.antenna.peek().channel.clone();
560        RadioGuard {
561            channels: channel.derive_channel(&*value),
562            station: self.antenna.read().station,
563            value,
564        }
565    }
566
567    /// Get a mutable reference to the current state value, inside a callback.
568    ///
569    /// Example:
570    ///
571    /// ```rust, ignore
572    /// # use freya::radio::*;
573    /// radio.write_with(|value| {
574    ///     // Modify `value`
575    /// });
576    /// ```
577    pub fn write_with(&mut self, cb: impl FnOnce(RadioGuard<Value, Channel>)) {
578        let guard = self.write();
579        cb(guard);
580    }
581
582    /// Modify the state using a custom Channel.
583    ///
584    /// ## Example:
585    /// ```rust, ignore
586    /// # use freya::radio::*;
587    /// radio.write(Channel::Whatever).value = 1;
588    /// ```
589    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
590        let value = self.antenna.peek().station.value.write_unchecked();
591        RadioGuard {
592            channels: channel.derive_channel(&*value),
593            station: self.antenna.read().station,
594            value,
595        }
596    }
597
598    /// Get a mutable reference to the current state value, inside a callback.
599    ///
600    /// Example:
601    ///
602    /// ```rust, ignore
603    /// # use freya::radio::*;
604    /// radio.write_channel_with(Channel::Whatever, |value| {
605    ///     // Modify `value`
606    /// });
607    /// ```
608    pub fn write_channel_with(
609        &mut self,
610        channel: Channel,
611        cb: impl FnOnce(RadioGuard<Value, Channel>),
612    ) {
613        let guard = self.write_channel(channel);
614        cb(guard);
615    }
616
617    /// Get a mutable reference to the current state value, inside a callback that returns the channel to be used.
618    ///
619    /// Example:
620    ///
621    /// ```rust, ignore
622    /// # use freya::radio::*;
623    /// radio.write_with_channel_selection(|value| {
624    ///     // Modify `value`
625    ///     if value.cool {
626    ///         ChannelSelection::Select(Channel::Whatever)
627    ///     } else {
628    ///         ChannelSelection::Silence
629    ///     }
630    /// });
631    /// ```
632    pub fn write_with_channel_selection(
633        &mut self,
634        cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
635    ) -> ChannelSelection<Channel> {
636        let value = self.antenna.peek().station.value.write_unchecked();
637        let mut guard = RadioGuard {
638            channels: Vec::default(),
639            station: self.antenna.read().station,
640            value,
641        };
642        let channel_selection = cb(&mut guard.value);
643        let channel = match channel_selection.clone() {
644            ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
645            ChannelSelection::Silence => None,
646            ChannelSelection::Select(c) => Some(c),
647        };
648        if let Some(channel) = channel {
649            for channel in channel.derive_channel(&guard.value) {
650                self.antenna.peek().station.notify_listeners(&channel)
651            }
652            self.antenna.peek().station.cleanup();
653        }
654
655        channel_selection
656    }
657
658    /// Modify the state silently, no component will be notified.
659    ///
660    /// This is not recommended, the only intended usage for this is inside [RadioAsyncReducer].
661    pub fn write_silently(&mut self) -> RadioGuard<Value, Channel> {
662        let value = self.antenna.peek().station.value.write_unchecked();
663        RadioGuard {
664            channels: Vec::default(),
665            station: self.antenna.read().station,
666            value,
667        }
668    }
669}
670
671impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
672
673#[derive(Clone)]
674pub enum ChannelSelection<Channel> {
675    /// Notify the channel associated with the used [Radio].
676    Current,
677    /// Notify a given `Channel`.
678    Select(Channel),
679    /// No subscriber will be notified.
680    Silence,
681}
682
683impl<Channel> ChannelSelection<Channel> {
684    /// Change to [ChannelSelection::Current]
685    pub fn current(&mut self) {
686        *self = Self::Current
687    }
688
689    /// Change to [ChannelSelection::Select]
690    pub fn select(&mut self, channel: Channel) {
691        *self = Self::Select(channel)
692    }
693
694    /// Change to [ChannelSelection::Silence]
695    pub fn silence(&mut self) {
696        *self = Self::Silence
697    }
698
699    /// Check if it is of type [ChannelSelection::Current]
700    pub fn is_current(&self) -> bool {
701        matches!(self, Self::Current)
702    }
703
704    /// Check if it is of type [ChannelSelection::Select] and return the channel.
705    pub fn is_select(&self) -> Option<&Channel> {
706        match self {
707            Self::Select(channel) => Some(channel),
708            _ => None,
709        }
710    }
711
712    /// Check if it is of type [ChannelSelection::Silence]
713    pub fn is_silence(&self) -> bool {
714        matches!(self, Self::Silence)
715    }
716}
717
718/// Provide an existing [`RadioStation`] to descendant components.
719/// This is useful for sharing the same global state across different parts of the component tree
720/// or across multiple windows.
721pub fn use_share_radio<Value, Channel>(radio: impl FnOnce() -> RadioStation<Value, Channel>)
722where
723    Channel: RadioChannel<Value>,
724    Value: 'static,
725{
726    use_provide_context(radio);
727}
728
729/// Subscribe to the global state for a specific channel.
730/// Returns a [`Radio`] handle that allows reading and writing the state.
731/// The current component will re-render whenever the specified channel is notified.
732///
733/// This hook must be called within a component that has access to a [`RadioStation`]
734/// (either through [`use_init_radio_station`] or [`use_share_radio`]).
735///
736/// # Example
737///
738/// ```rust, ignore
739/// # use freya::prelude::*;
740/// # use freya::radio::*;
741///
742/// fn app() -> impl IntoElement {
743///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
744///
745///     rect().child(Counter {})
746/// }
747///
748/// #[derive(PartialEq)]
749/// struct Counter {}
750///
751/// impl Component for Counter {
752///     fn render(&self) -> impl IntoElement {
753///         let mut radio = use_radio(AppChannel::Count);
754///
755///         rect()
756///             .child(label().text(format!("Count: {}", radio.read().count)))
757///             .child(
758///                 Button::new()
759///                     .on_press(move |_| radio.write().count += 1)
760///                     .child("+"),
761///             )
762///     }
763/// }
764/// ```
765pub fn use_radio<Value, Channel>(channel: Channel) -> Radio<Value, Channel>
766where
767    Channel: RadioChannel<Value>,
768    Value: 'static,
769{
770    let station = use_consume::<RadioStation<Value, Channel>>();
771
772    let mut radio = use_hook(|| {
773        let antenna = RadioAntenna::new(channel.clone(), station);
774        Radio::new(State::create(antenna))
775    });
776
777    if radio.antenna.peek().channel != channel {
778        radio.antenna.write().channel = channel;
779    }
780
781    radio
782}
783
784/// Initialize a new radio station in the current component tree.
785/// This provides the global state to all descendant components.
786///
787/// Returns the [`RadioStation`] instance for direct access if needed.
788///
789/// # Example
790///
791/// ```rust, ignore
792/// # use freya::prelude::*;
793/// # use freya::radio::*;
794///
795/// fn app() -> impl IntoElement {
796///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
797///
798///     rect().child(MyComponent {})
799/// }
800/// ```
801pub fn use_init_radio_station<Value, Channel>(
802    init_value: impl FnOnce() -> Value,
803) -> RadioStation<Value, Channel>
804where
805    Channel: RadioChannel<Value>,
806    Value: 'static,
807{
808    use_provide_context(|| RadioStation::create(init_value()))
809}
810
811pub fn use_radio_station<Value, Channel>() -> RadioStation<Value, Channel>
812where
813    Channel: RadioChannel<Value>,
814    Value: 'static,
815{
816    use_consume::<RadioStation<Value, Channel>>()
817}
818
819/// Trait for implementing a reducer pattern on your state.
820/// Reducers allow you to define actions that modify the state in a controlled way.
821///
822/// Implement this trait on your state type to enable the [`RadioReducer`] functionality.
823///
824/// # Example
825///
826/// ```rust, ignore
827/// # use freya::radio::*;
828///
829/// #[derive(Clone)]
830/// struct Counter {
831///     count: i32,
832/// }
833///
834/// #[derive(Clone)]
835/// enum CounterAction {
836///     Increment,
837///     Decrement,
838///     Set(i32),
839/// }
840///
841/// impl DataReducer for Counter {
842///     type Channel = CounterChannel;
843///     type Action = CounterAction;
844///
845///     fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel> {
846///         match action {
847///             CounterAction::Increment => self.count += 1,
848///             CounterAction::Decrement => self.count -= 1,
849///             CounterAction::Set(value) => self.count = value,
850///         }
851///         ChannelSelection::Current
852///     }
853/// }
854/// ```
855pub trait DataReducer {
856    type Channel;
857    type Action;
858
859    fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
860}
861
862pub trait RadioReducer {
863    type Action;
864    type Channel;
865
866    fn apply(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
867}
868
869impl<Data: DataReducer<Channel = Channel, Action = Action>, Channel: RadioChannel<Data>, Action>
870    RadioReducer for Radio<Data, Channel>
871{
872    type Action = Action;
873    type Channel = Channel;
874
875    fn apply(&mut self, action: Action) -> ChannelSelection<Channel> {
876        self.write_with_channel_selection(|data| data.reduce(action))
877    }
878}
879
880pub trait DataAsyncReducer {
881    type Channel;
882    type Action;
883
884    #[allow(async_fn_in_trait)]
885    async fn async_reduce(
886        _radio: &mut Radio<Self, Self::Channel>,
887        _action: Self::Action,
888    ) -> ChannelSelection<Self::Channel>
889    where
890        Self::Channel: RadioChannel<Self>,
891        Self: Sized;
892}
893
894pub trait RadioAsyncReducer {
895    type Action;
896
897    fn async_apply(&mut self, _action: Self::Action)
898    where
899        Self::Action: 'static;
900}
901
902impl<
903    Data: DataAsyncReducer<Channel = Channel, Action = Action>,
904    Channel: RadioChannel<Data>,
905    Action,
906> RadioAsyncReducer for Radio<Data, Channel>
907{
908    type Action = Action;
909
910    fn async_apply(&mut self, action: Self::Action)
911    where
912        Self::Action: 'static,
913    {
914        let mut radio = *self;
915        spawn(async move {
916            let channel = Data::async_reduce(&mut radio, action).await;
917            radio.write_with_channel_selection(|_| channel);
918        });
919    }
920}