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}