oxide_mvu/
effect.rs

1//! Declarative effect system for describing deferred event processing.
2
3#[cfg(feature = "no_std")]
4use alloc::boxed::Box;
5#[cfg(feature = "no_std")]
6use alloc::vec::Vec;
7
8use crate::Emitter;
9
10/// Declarative description of events to be processed.
11///
12/// Effects allow you to describe asynchronous or deferred work that will
13/// produce events. They are returned from [`MvuLogic::init`](crate::MvuLogic::init)
14/// and [`MvuLogic::update`](crate::MvuLogic::update) alongside the new model state.
15///
16/// # Example
17///
18/// ```rust
19/// use oxide_mvu::Effect;
20///
21/// #[derive(Clone)]
22/// enum Event {
23///     LoadData,
24///     DataLoaded(String),
25/// }
26///
27/// // Trigger a follow-up event
28/// let effect = Effect::just(Event::LoadData);
29///
30/// // Combine multiple effects
31/// let effect = Effect::batch(vec![
32///     Effect::just(Event::LoadData),
33///     Effect::just(Event::DataLoaded("cached".to_string())),
34/// ]);
35///
36/// // No side effects
37/// let effect: Effect<Event> = Effect::none();
38/// ```
39#[allow(clippy::type_complexity)]
40pub struct Effect<Event>(Box<dyn Fn(&Emitter<Event>) + Send + 'static>);
41
42impl<Event: 'static> Effect<Event> {
43    /// Create an empty effect.
44    ///
45    /// This is private - use [`Effect::none()`] instead.
46    fn new() -> Self {
47        Self(Box::new(|_| {}))
48    }
49
50    pub fn execute(&self, emitter: &Emitter<Event>) {
51        (self.0)(emitter);
52    }
53
54    /// Create an effect just a single event.
55    ///
56    /// Useful for triggering immediate follow-up events.
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// use oxide_mvu::Effect;
62    ///
63    /// #[derive(Clone)]
64    /// enum Event { Refresh }
65    ///
66    /// let effect = Effect::just(Event::Refresh);
67    /// ```
68    pub fn just(event: Event) -> Self
69    where
70        Event: Clone + Send + 'static,
71    {
72        Self(Box::new(move |emitter: &Emitter<Event>| {
73            emitter.emit(event.clone());
74        }))
75    }
76
77    /// Create an empty effect.
78    ///
79    /// Prefer this when semantically indicating "no side effects".
80    ///
81    /// # Example
82    ///
83    /// ```rust
84    /// use oxide_mvu::Effect;
85    ///
86    /// #[derive(Clone)]
87    /// enum Event { Increment }
88    ///
89    /// let effect: Effect<Event> = Effect::none();
90    /// ```
91    pub fn none() -> Self {
92        Self::new()
93    }
94
95    /// Combine multiple effects into a single effect.
96    ///
97    /// All events from all effects will be queued for processing.
98    ///
99    /// # Example
100    ///
101    /// ```rust
102    /// use oxide_mvu::Effect;
103    ///
104    /// #[derive(Clone)]
105    /// enum Event { A, B, C }
106    ///
107    /// let combined = Effect::batch(vec![
108    ///     Effect::just(Event::A),
109    ///     Effect::just(Event::B),
110    ///     Effect::just(Event::C),
111    /// ]);
112    /// ```
113    pub fn batch(effects: Vec<Effect<Event>>) -> Self {
114        Self(Box::new(move |emitter: &Emitter<Event>| {
115            for effect in &effects {
116                effect.execute(emitter);
117            }
118        }))
119    }
120}