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}