Skip to main content

tmai_core/api/
events.rs

1//! Core event system for push-based change notification.
2//!
3//! The event system supports two modes:
4//! - **Bridge mode**: `start_monitoring()` spawns a Poller internally and
5//!   bridges `PollMessage` → `CoreEvent` automatically (for headless/web-only).
6//! - **External mode**: The consumer (TUI) runs its own Poller and calls
7//!   `notify_agents_updated()` / `notify_teams_updated()` to emit events.
8
9use tokio::sync::broadcast;
10
11use super::core::TmaiCore;
12
13/// Events emitted by the core when state changes occur.
14///
15/// Consumers call [`TmaiCore::subscribe()`] to receive these events
16/// via a `broadcast::Receiver`.
17#[derive(Debug, Clone)]
18pub enum CoreEvent {
19    /// The full agent list was refreshed (after a poll cycle)
20    AgentsUpdated,
21
22    /// A single agent changed status
23    AgentStatusChanged {
24        /// Agent target ID
25        target: String,
26        /// Previous status description
27        old_status: String,
28        /// New status description
29        new_status: String,
30    },
31
32    /// A new agent appeared
33    AgentAppeared {
34        /// Agent target ID
35        target: String,
36    },
37
38    /// An agent disappeared
39    AgentDisappeared {
40        /// Agent target ID
41        target: String,
42    },
43
44    /// Team data was refreshed
45    TeamsUpdated,
46}
47
48impl TmaiCore {
49    /// Subscribe to core events.
50    ///
51    /// Returns a broadcast receiver that will receive [`CoreEvent`]s.
52    /// If the receiver falls behind, older events are dropped (lagged).
53    pub fn subscribe(&self) -> broadcast::Receiver<CoreEvent> {
54        self.event_sender().subscribe()
55    }
56
57    /// Notify subscribers that the agent list was updated.
58    ///
59    /// Called by external consumers (e.g. TUI main loop) after processing
60    /// `PollMessage::AgentsUpdated`. Ignored if no subscribers are listening.
61    pub fn notify_agents_updated(&self) {
62        let _ = self.event_sender().send(CoreEvent::AgentsUpdated);
63    }
64
65    /// Notify subscribers that team data was updated.
66    ///
67    /// Called by external consumers after team scan completes.
68    pub fn notify_teams_updated(&self) {
69        let _ = self.event_sender().send(CoreEvent::TeamsUpdated);
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::api::builder::TmaiCoreBuilder;
77    use crate::config::Settings;
78
79    #[tokio::test]
80    async fn test_subscribe_receives_events() {
81        let core = TmaiCoreBuilder::new(Settings::default()).build();
82        let mut rx = core.subscribe();
83
84        // Send an event via the internal sender
85        let tx = core.event_sender();
86        tx.send(CoreEvent::AgentsUpdated).unwrap();
87
88        let event = rx.recv().await.unwrap();
89        assert!(matches!(event, CoreEvent::AgentsUpdated));
90    }
91
92    #[tokio::test]
93    async fn test_subscribe_multiple_receivers() {
94        let core = TmaiCoreBuilder::new(Settings::default()).build();
95        let mut rx1 = core.subscribe();
96        let mut rx2 = core.subscribe();
97
98        let tx = core.event_sender();
99        tx.send(CoreEvent::TeamsUpdated).unwrap();
100
101        let e1 = rx1.recv().await.unwrap();
102        let e2 = rx2.recv().await.unwrap();
103        assert!(matches!(e1, CoreEvent::TeamsUpdated));
104        assert!(matches!(e2, CoreEvent::TeamsUpdated));
105    }
106
107    #[tokio::test]
108    async fn test_notify_agents_updated() {
109        let core = TmaiCoreBuilder::new(Settings::default()).build();
110        let mut rx = core.subscribe();
111
112        core.notify_agents_updated();
113
114        let event = rx.recv().await.unwrap();
115        assert!(matches!(event, CoreEvent::AgentsUpdated));
116    }
117
118    #[tokio::test]
119    async fn test_notify_teams_updated() {
120        let core = TmaiCoreBuilder::new(Settings::default()).build();
121        let mut rx = core.subscribe();
122
123        core.notify_teams_updated();
124
125        let event = rx.recv().await.unwrap();
126        assert!(matches!(event, CoreEvent::TeamsUpdated));
127    }
128
129    #[test]
130    fn test_notify_no_subscribers() {
131        let core = TmaiCoreBuilder::new(Settings::default()).build();
132        // Should not panic even with no subscribers
133        core.notify_agents_updated();
134        core.notify_teams_updated();
135    }
136}