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}