Skip to main content

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}