Skip to main content

cliffy_core/
event.rs

1//! Event - Discrete occurrences with geometric transformations
2//!
3//! An `Event<T>` represents a stream of discrete occurrences over time.
4//! Each occurrence carries a value of type `T` and can be transformed
5//! or combined with other events.
6//!
7//! This is inspired by Conal Elliott's classical FRP semantics where
8//! `Event a = [(Time, a)]` - a list of time-value pairs.
9
10use crate::geometric::{FromGeometric, IntoGeometric, GA3};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14/// An event occurrence with a value
15#[derive(Debug, Clone)]
16pub struct Occurrence<T> {
17    /// The value of this occurrence
18    pub value: T,
19    /// Timestamp (relative to behavior creation)
20    pub timestamp: f64,
21}
22
23/// A stream of discrete occurrences
24///
25/// `Event<T>` allows you to react to discrete happenings like clicks,
26/// keypresses, or timer ticks. Events can be mapped, filtered, and
27/// merged with other events.
28///
29/// # Example
30///
31/// ```rust
32/// use cliffy_core::{event, Event};
33///
34/// let clicks = event::<()>();
35///
36/// clicks.subscribe(|_| {
37///     println!("Clicked!");
38/// });
39///
40/// clicks.emit(());
41/// ```
42#[allow(clippy::type_complexity)]
43pub struct Event<T> {
44    /// Subscribers to notify on occurrence
45    subscribers: Rc<RefCell<Vec<(usize, Box<dyn Fn(&Occurrence<T>)>)>>>,
46
47    /// Next subscriber ID
48    next_id: Rc<RefCell<usize>>,
49
50    /// Start time for timestamp calculation
51    start_time: std::time::Instant,
52
53    /// Optional geometric transform applied to each event
54    transform: Option<GA3>,
55}
56
57impl<T> Clone for Event<T> {
58    fn clone(&self) -> Self {
59        Self {
60            subscribers: Rc::clone(&self.subscribers),
61            next_id: Rc::clone(&self.next_id),
62            start_time: self.start_time,
63            transform: self.transform.clone(),
64        }
65    }
66}
67
68impl<T: Clone + 'static> Event<T> {
69    /// Create a new event stream
70    pub fn new() -> Self {
71        Self {
72            subscribers: Rc::new(RefCell::new(Vec::new())),
73            next_id: Rc::new(RefCell::new(0)),
74            start_time: std::time::Instant::now(),
75            transform: None,
76        }
77    }
78
79    /// Emit a value to all subscribers
80    pub fn emit(&self, value: T) {
81        let occurrence = Occurrence {
82            value,
83            timestamp: self.start_time.elapsed().as_secs_f64(),
84        };
85
86        for (_, callback) in self.subscribers.borrow().iter() {
87            callback(&occurrence);
88        }
89    }
90
91    /// Subscribe to this event stream
92    pub fn subscribe<F>(&self, callback: F) -> EventSubscription
93    where
94        F: Fn(&T) + 'static,
95    {
96        let id = {
97            let mut next = self.next_id.borrow_mut();
98            let id = *next;
99            *next += 1;
100            id
101        };
102
103        self.subscribers
104            .borrow_mut()
105            .push((id, Box::new(move |occ| callback(&occ.value))));
106
107        let subscribers = Rc::clone(&self.subscribers);
108        EventSubscription {
109            id,
110            unsubscribe: Rc::new(RefCell::new(Some(Box::new(move || {
111                subscribers.borrow_mut().retain(|(i, _)| *i != id);
112            })))),
113        }
114    }
115
116    /// Map a function over this event stream
117    pub fn map<U, F>(&self, f: F) -> Event<U>
118    where
119        U: Clone + 'static,
120        F: Fn(T) -> U + 'static,
121    {
122        let mapped = Event::<U>::new();
123
124        let mapped_clone = mapped.clone();
125        self.subscribe(move |value| {
126            mapped_clone.emit(f(value.clone()));
127        });
128
129        mapped
130    }
131
132    /// Filter events based on a predicate
133    pub fn filter<F>(&self, predicate: F) -> Event<T>
134    where
135        F: Fn(&T) -> bool + 'static,
136    {
137        let filtered = Event::<T>::new();
138
139        let filtered_clone = filtered.clone();
140        self.subscribe(move |value| {
141            if predicate(value) {
142                filtered_clone.emit(value.clone());
143            }
144        });
145
146        filtered
147    }
148
149    /// Merge two event streams
150    pub fn merge(&self, other: &Event<T>) -> Event<T> {
151        let merged = Event::<T>::new();
152
153        let merged_clone = merged.clone();
154        self.subscribe(move |value| {
155            merged_clone.emit(value.clone());
156        });
157
158        let merged_clone = merged.clone();
159        other.subscribe(move |value| {
160            merged_clone.emit(value.clone());
161        });
162
163        merged
164    }
165
166    /// Fold events into a behavior, accumulating values
167    pub fn fold<S, F>(&self, initial: S, f: F) -> crate::Behavior<S>
168    where
169        S: IntoGeometric + FromGeometric + Clone + 'static,
170        F: Fn(S, T) -> S + 'static,
171    {
172        let behavior = crate::behavior(initial);
173
174        let behavior_clone = behavior.clone();
175        self.subscribe(move |value| {
176            behavior_clone.update(|state| f(state, value.clone()));
177        });
178
179        behavior
180    }
181}
182
183impl<T: Clone + 'static> Default for Event<T> {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189/// A subscription handle for events
190#[allow(clippy::type_complexity)]
191pub struct EventSubscription {
192    #[allow(dead_code)] // Reserved for debugging
193    id: usize,
194    unsubscribe: Rc<RefCell<Option<Box<dyn Fn()>>>>,
195}
196
197impl EventSubscription {
198    /// Unsubscribe from the event stream
199    pub fn unsubscribe(self) {
200        if let Some(unsub) = self.unsubscribe.borrow_mut().take() {
201            unsub();
202        }
203    }
204}
205
206/// Convenience function to create an event stream
207///
208/// # Example
209///
210/// ```rust
211/// use cliffy_core::event;
212///
213/// let clicks = event::<()>();
214/// let key_presses = event::<char>();
215/// let values = event::<i32>();
216/// ```
217pub fn event<T: Clone + 'static>() -> Event<T> {
218    Event::new()
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use std::cell::Cell;
225
226    #[test]
227    fn test_event_new_and_emit() {
228        let evt = event::<i32>();
229        let received = Rc::new(Cell::new(0));
230        let received_clone = Rc::clone(&received);
231
232        evt.subscribe(move |value| {
233            received_clone.set(*value);
234        });
235
236        evt.emit(42);
237        assert_eq!(received.get(), 42);
238    }
239
240    #[test]
241    fn test_event_multiple_subscribers() {
242        let evt = event::<i32>();
243        let count = Rc::new(Cell::new(0));
244
245        let count1 = Rc::clone(&count);
246        evt.subscribe(move |_| count1.set(count1.get() + 1));
247
248        let count2 = Rc::clone(&count);
249        evt.subscribe(move |_| count2.set(count2.get() + 1));
250
251        evt.emit(1);
252        assert_eq!(count.get(), 2);
253    }
254
255    #[test]
256    fn test_event_unsubscribe() {
257        let evt = event::<i32>();
258        let count = Rc::new(Cell::new(0));
259        let count_clone = Rc::clone(&count);
260
261        let sub = evt.subscribe(move |_| {
262            count_clone.set(count_clone.get() + 1);
263        });
264
265        evt.emit(1);
266        assert_eq!(count.get(), 1);
267
268        sub.unsubscribe();
269
270        evt.emit(2);
271        assert_eq!(count.get(), 1); // Should not increase
272    }
273
274    #[test]
275    fn test_event_map() {
276        let evt = event::<i32>();
277        let doubled = evt.map(|n| n * 2);
278
279        let received = Rc::new(Cell::new(0));
280        let received_clone = Rc::clone(&received);
281
282        doubled.subscribe(move |value| {
283            received_clone.set(*value);
284        });
285
286        evt.emit(5);
287        assert_eq!(received.get(), 10);
288    }
289
290    #[test]
291    fn test_event_filter() {
292        let evt = event::<i32>();
293        let evens = evt.filter(|n| n % 2 == 0);
294
295        let received = Rc::new(RefCell::new(Vec::new()));
296        let received_clone = Rc::clone(&received);
297
298        evens.subscribe(move |value| {
299            received_clone.borrow_mut().push(*value);
300        });
301
302        evt.emit(1);
303        evt.emit(2);
304        evt.emit(3);
305        evt.emit(4);
306
307        assert_eq!(*received.borrow(), vec![2, 4]);
308    }
309
310    #[test]
311    fn test_event_merge() {
312        let evt1 = event::<i32>();
313        let evt2 = event::<i32>();
314        let merged = evt1.merge(&evt2);
315
316        let received = Rc::new(RefCell::new(Vec::new()));
317        let received_clone = Rc::clone(&received);
318
319        merged.subscribe(move |value| {
320            received_clone.borrow_mut().push(*value);
321        });
322
323        evt1.emit(1);
324        evt2.emit(2);
325        evt1.emit(3);
326
327        assert_eq!(*received.borrow(), vec![1, 2, 3]);
328    }
329
330    #[test]
331    fn test_event_fold() {
332        let clicks = event::<()>();
333        let count = clicks.fold(0i32, |n, _| n + 1);
334
335        assert_eq!(count.sample(), 0);
336
337        clicks.emit(());
338        assert_eq!(count.sample(), 1);
339
340        clicks.emit(());
341        clicks.emit(());
342        assert_eq!(count.sample(), 3);
343    }
344}