Skip to main content

oxide_mvu/
emitter.rs

1//! Event emitter for embedding callbacks in Props.
2
3use async_channel::Sender;
4
5use crate::Event as EventTrait;
6
7/// Event emitter that can be embedded in Props.
8///
9/// Clone this handle to create callbacks in your Props that can trigger
10/// events when invoked (e.g., by user interaction).
11///
12/// `Emitter` wraps a lock-free MPMC channel sender, making it cheap to clone
13/// and thread-safe without any locking overhead.
14///
15/// # Performance Characteristics
16///
17/// Cloning an `Emitter` is a cheap operation (equivalent to cloning an `Arc`).
18/// It increments a reference count using an atomic operation rather than
19/// allocating new memory. This makes it safe to clone frequently without
20/// significant performance cost.
21///
22/// However, atomic operations do have some overhead (memory barriers, cache
23/// coherency), so the following patterns are recommended for optimal performance:
24///
25/// ## Recommended Pattern: Clone Once per View
26///
27/// When creating multiple callbacks in your `view()` function, clone the emitter
28/// once and reuse it:
29///
30/// ```rust
31/// # use oxide_mvu::{Emitter, MvuLogic, Effect};
32/// # #[derive(Clone)] enum Event { Click, Hover, Focus }
33/// # #[derive(Clone)] struct Model;
34/// # struct Props {
35/// #     on_click: Box<dyn Fn()>,
36/// #     on_hover: Box<dyn Fn()>,
37/// #     on_focus: Box<dyn Fn()>,
38/// # }
39/// # struct MyApp;
40/// # impl MvuLogic<Event, Model, Props> for MyApp {
41/// #     fn init(&self, model: Model) -> (Model, Effect<Event>) { (model, Effect::none()) }
42/// #     fn update(&self, event: Event, model: &Model) -> (Model, Effect<Event>) { (model.clone(), Effect::none()) }
43/// fn view(&self, model: &Model, emitter: &Emitter<Event>) -> Props {
44///     // GOOD: Clone once, reuse for all callbacks
45///     let emitter = emitter.clone();
46///     Props {
47///         on_click: Box::new({
48///             let e = emitter.clone();
49///             move || { e.try_emit(Event::Click); }
50///         }),
51///         on_hover: Box::new({
52///             let e = emitter.clone();
53///             move || { e.try_emit(Event::Hover); }
54///         }),
55///         on_focus: Box::new({
56///             let e = emitter.clone();
57///             move || { e.try_emit(Event::Focus); }
58///         }),
59///     }
60/// }
61/// # }
62/// ```
63///
64/// This pattern minimizes atomic operations while maintaining clean code structure.
65///
66/// # Example
67///
68/// ```rust
69/// use oxide_mvu::{Emitter, MvuLogic, Effect};
70///
71/// #[derive(Clone)]
72/// enum Event { Click }
73///
74/// #[derive(Clone)]
75/// struct Model { clicks: u32 }
76///
77/// struct Props {
78///     clicks: u32,
79///     on_click: Box<dyn Fn()>,
80/// }
81///
82/// struct MyApp;
83///
84/// impl MvuLogic<Event, Model, Props> for MyApp {
85///     fn init(&self, model: Model) -> (Model, Effect<Event>) {
86///         (model, Effect::none())
87///     }
88///
89///     fn update(&self, event: Event, model: &Model) -> (Model, Effect<Event>) {
90///         match event {
91///             Event::Click => {
92///                 let new_model = Model {
93///                     clicks: model.clicks + 1,
94///                     ..model.clone()
95///                 };
96///                 (new_model, Effect::none())
97///             }
98///         }
99///     }
100///
101///     fn view(&self, model: &Model, emitter: &Emitter<Event>) -> Props {
102///         let emitter = emitter.clone();
103///         Props {
104///             clicks: model.clicks,
105///             on_click: Box::new(move || {
106///                 emitter.try_emit(Event::Click);
107///             }),
108///         }
109///     }
110/// }
111/// ```
112pub struct Emitter<Event: EventTrait>(pub(crate) Sender<Event>);
113
114impl<Event: EventTrait> Clone for Emitter<Event> {
115    fn clone(&self) -> Self {
116        Self(self.0.clone())
117    }
118}
119
120impl<Event: EventTrait> Emitter<Event> {
121    /// Create a new emitter from a channel sender.
122    pub(crate) fn new(sender: Sender<Event>) -> Self {
123        Self(sender)
124    }
125
126    /// Emit an event without blocking.
127    ///
128    /// This attempts to queue the event for processing by the runtime. If the
129    /// event queue is full, the event will be dropped and `false` is returned.
130    ///
131    /// Multiple threads can safely call this method concurrently via the lock-free channel.
132    pub fn try_emit(&self, event: Event) -> bool {
133        self.0.try_send(event).is_ok()
134    }
135
136    /// Emit an event, waiting until space is available.
137    ///
138    /// This queues the event for processing by the runtime. If the queue is full,
139    /// this method will await until space becomes available.
140    ///
141    /// Multiple threads can safely call this method concurrently via the lock-free channel.
142    pub async fn emit(&self, event: Event) {
143        self.0.send(event).await.ok();
144    }
145}