oxide_mvu/
emitter.rs

1//! Event emitter for embedding callbacks in Props.
2
3use thingbuf::mpsc::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 channel sender, making it cheap to clone
13/// and thread-safe without any locking overhead.
14///
15/// # Example
16///
17/// ```rust
18/// use oxide_mvu::{Emitter, MvuLogic, Effect};
19///
20/// #[derive(Clone)]
21/// enum Event { Click }
22///
23/// #[derive(Clone)]
24/// struct Model { clicks: u32 }
25///
26/// struct Props {
27///     clicks: u32,
28///     on_click: Box<dyn Fn()>,
29/// }
30///
31/// struct MyApp;
32///
33/// impl MvuLogic<Event, Model, Props> for MyApp {
34///     fn init(&self, model: Model) -> (Model, Effect<Event>) {
35///         (model, Effect::none())
36///     }
37///
38///     fn update(&self, event: Event, model: &Model) -> (Model, Effect<Event>) {
39///         match event {
40///             Event::Click => {
41///                 let new_model = Model {
42///                     clicks: model.clicks + 1,
43///                     ..model.clone()
44///                 };
45///                 (new_model, Effect::none())
46///             }
47///         }
48///     }
49///
50///     fn view(&self, model: &Model, emitter: &Emitter<Event>) -> Props {
51///         let emitter = emitter.clone();
52///         Props {
53///             clicks: model.clicks,
54///             on_click: Box::new(move || {
55///                 emitter.try_emit(Event::Click);
56///             }),
57///         }
58///     }
59/// }
60/// ```
61// Note: We wrap events in Option internally to satisfy thingbuf's Default requirement
62// for its recycling mechanism. This allows us to avoid requiring Default on user events.
63pub struct Emitter<Event: EventTrait>(pub(crate) Sender<Option<Event>>);
64
65impl<Event: EventTrait> Clone for Emitter<Event> {
66    fn clone(&self) -> Self {
67        Self(self.0.clone())
68    }
69}
70
71impl<Event: EventTrait> Emitter<Event> {
72    /// Create a new emitter from a channel sender.
73    pub(crate) fn new(sender: Sender<Option<Event>>) -> Self {
74        Self(sender)
75    }
76
77    /// Emit an event without blocking.
78    ///
79    /// This attempts to queue the event for processing by the runtime. If the
80    /// event queue is full, the event will be dropped and `false` is returned.
81    ///
82    /// Multiple threads can safely call this method concurrently via the lock-free channel.
83    pub fn try_emit(&self, event: Event) -> bool {
84        self.0.try_send(Some(event)).is_ok()
85    }
86
87    /// Emit an event, waiting until space is available.
88    ///
89    /// This queues the event for processing by the runtime. If the queue is full,
90    /// this method will await until space becomes available.
91    ///
92    /// Multiple threads can safely call this method concurrently via the lock-free channel.
93    pub async fn emit(&self, event: Event) {
94        self.0.send(Some(event)).await.ok();
95    }
96}