Skip to main content

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