feagi_structures/feagi_signal.rs
1use crate::{define_index, FeagiDataError};
2use std::collections::HashMap;
3use std::fmt::Debug;
4use std::sync::{Arc, Mutex};
5
6define_index!(
7 FeagiSignalIndex,
8 u32,
9 "A unique identifier for a subscription to a FeagiSignal"
10);
11
12/// Event signal system similar to Godot signals.
13///
14/// Allows subscribing callbacks that will be invoked when events are emitted.
15/// Uses `FnMut` to allow closures to capture and modify external state via `Arc<Mutex<T>>`.
16///
17/// # Example with Shared State
18/// ```
19/// use std::sync::{Arc, Mutex};
20/// use feagi_structures::FeagiSignal;
21///
22/// struct MyHandler {
23/// count: i32,
24/// }
25///
26/// impl MyHandler {
27/// fn handle_event(&mut self, data: &String) {
28/// self.count += 1;
29/// println!("Event {}: {}", self.count, data);
30/// }
31/// }
32///
33/// let handler = Arc::new(Mutex::new(MyHandler { count: 0 }));
34/// let mut signal = FeagiSignal::new();
35///
36/// // Clone Arc for the closure
37/// let handler_clone = Arc::clone(&handler);
38/// signal.connect(move |data| {
39/// handler_clone.lock().unwrap().handle_event(data);
40/// });
41///
42/// signal.emit(&"Hello".to_string());
43/// assert_eq!(handler.lock().unwrap().count, 1);
44/// ```
45type SignalListener<T> = Box<dyn FnMut(&T) + Send>;
46
47pub struct FeagiSignal<T> {
48 listeners: HashMap<FeagiSignalIndex, SignalListener<T>>,
49 next_index: u32,
50}
51
52impl<T> FeagiSignal<T> {
53 /// Creates a new empty signal.
54 pub fn new() -> Self {
55 Self {
56 listeners: HashMap::new(),
57 next_index: 0,
58 }
59 }
60
61 /// Connects a callback to this signal.
62 ///
63 /// The callback will be invoked whenever `emit()` is called.
64 /// Returns a handle that can be used to disconnect later.
65 ///
66 /// # Example
67 /// ```
68 /// use feagi_structures::FeagiSignal;
69 ///
70 /// let mut signal = FeagiSignal::new();
71 /// let handle = signal.connect(|value: &i32| {
72 /// println!("Received: {}", value);
73 /// });
74 ///
75 /// signal.emit(&42);
76 /// signal.disconnect(handle).unwrap();
77 /// ```
78 pub fn connect<F>(&mut self, f: F) -> FeagiSignalIndex
79 // Will overflow after 4 billion subscriptions. Too bad!
80 where
81 F: FnMut(&T) + Send + 'static,
82 {
83 self.listeners.insert(self.next_index.into(), Box::new(f));
84 self.next_index += 1;
85 (self.next_index - 1).into()
86 }
87
88 /// Disconnects a previously connected callback.
89 ///
90 /// Returns an error if no callback with the given index exists.
91 pub fn disconnect(&mut self, index: FeagiSignalIndex) -> Result<(), FeagiDataError> {
92 if self.listeners.remove(&index).is_some() {
93 return Ok(());
94 }
95 Err(FeagiDataError::BadParameters(format!(
96 "No subscription found with identifier {}!",
97 index
98 )))
99 }
100
101 /// Emits an event to all connected callbacks.
102 ///
103 /// Callbacks are invoked in arbitrary order.
104 pub fn emit(&mut self, value: &T) {
105 for f in self.listeners.values_mut() {
106 f(value);
107 }
108 }
109
110 /// Helper to connect a closure that captures an `Arc<Mutex<S>>`.
111 ///
112 /// This is a convenience method for the common pattern of calling methods
113 /// on shared mutable state from within the callback.
114 ///
115 /// # Example
116 /// ```
117 /// use std::sync::{Arc, Mutex};
118 /// use feagi_structures::FeagiSignal;
119 ///
120 /// struct Counter { value: i32 }
121 /// impl Counter {
122 /// fn increment(&mut self) { self.value += 1; }
123 /// }
124 ///
125 /// let counter = Arc::new(Mutex::new(Counter { value: 0 }));
126 /// let mut signal = FeagiSignal::new();
127 ///
128 /// signal.connect_with_shared_state(
129 /// Arc::clone(&counter),
130 /// |state, _event| state.increment()
131 /// );
132 ///
133 /// signal.emit(&"event");
134 /// assert_eq!(counter.lock().unwrap().value, 1);
135 /// ```
136 pub fn connect_with_shared_state<S, F>(
137 &mut self,
138 state: Arc<Mutex<S>>,
139 mut callback: F,
140 ) -> FeagiSignalIndex
141 where
142 S: Send + 'static,
143 F: FnMut(&mut S, &T) + Send + 'static,
144 {
145 self.connect(move |event| {
146 if let Ok(mut guard) = state.lock() {
147 callback(&mut *guard, event);
148 }
149 })
150 }
151
152 /// Returns the number of connected listeners.
153 pub fn listener_count(&self) -> usize {
154 self.listeners.len()
155 }
156
157 /// Removes all connected listeners.
158 pub fn disconnect_all(&mut self) {
159 self.listeners.clear();
160 }
161}
162
163// Note: For PyO3 users, you can use this pattern:
164//
165// ```rust,ignore
166// use pyo3::prelude::*;
167// use pyo3::types::PyAny;
168// use std::sync::Arc;
169//
170// #[pyclass]
171// struct MyPythonHandler {
172// callback: Arc<Py<PyAny>>, // Python callable stored as Arc
173// }
174//
175// impl MyPythonHandler {
176// fn handle_event(&self, py: Python, data: &MyEventType) -> PyResult<()> {
177// self.callback.call1(py, (data.clone(),))?;
178// Ok(())
179// }
180// }
181//
182// // In your PyO3 setup:
183// let handler = Arc::new(MyPythonHandler { callback: Arc::new(python_fn) });
184// let handler_clone = Arc::clone(&handler);
185//
186// signal.connect(move |event| {
187// Python::with_gil(|py| {
188// let _ = handler_clone.handle_event(py, event);
189// });
190// });
191// ```
192
193impl<T> Default for FeagiSignal<T> {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199impl<T> Debug for FeagiSignal<T> {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 f.debug_struct("FeagiSignal")
202 .field("listener_count", &self.listeners.len())
203 .field("next_index", &self.next_index)
204 .field(
205 "listener_indices",
206 &self.listeners.keys().collect::<Vec<_>>(),
207 )
208 .finish()
209 }
210}