Skip to main content

infinity_bridge_host/
hub.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::Duration;
5
6use infinity_bridge_wire::{AckPayload, BridgeError, CmdPayload, EventPayload, HelloPayload, WireMsg};
7use serde_json::Value;
8use tokio::sync::{Mutex, broadcast, mpsc, oneshot, watch};
9use uuid::Uuid;
10
11use crate::client::Client;
12
13#[derive(Debug, Clone)]
14pub struct ClientInfo {
15    pub id: u64,
16    pub hello: Option<HelloPayload>,
17}
18
19pub(crate) struct Hub {
20    next_id: AtomicU64,
21    clients: Mutex<HashMap<u64, Client>>,
22    pending: Mutex<HashMap<String, oneshot::Sender<AckPayload>>>,
23
24    event_tx: broadcast::Sender<EventPayload>,
25
26    connection_tx: watch::Sender<bool>,
27    connection_rx: watch::Receiver<bool>,
28
29    connect_notify: tokio::sync::Notify,
30}
31
32impl Hub {
33    pub fn new(event_capacity: usize) -> Arc<Self> {
34        let (event_tx, _) = broadcast::channel(event_capacity);
35        let (connection_tx, connection_rx) = watch::channel(false);
36
37        Arc::new(Self {
38            next_id: AtomicU64::new(1),
39            clients: Mutex::new(HashMap::new()),
40            pending: Mutex::new(HashMap::new()),
41            event_tx,
42            connection_tx,
43            connection_rx,
44            connect_notify: tokio::sync::Notify::new(),
45        })
46    }
47
48    pub async fn register_client(&self, tx: mpsc::UnboundedSender<String>) -> u64 {
49        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
50        let mut clients = self.clients.lock().await;
51        clients.insert(
52            id,
53            Client {
54                tx,
55                hello: None,
56                last_seen: tokio::time::Instant::now(),
57            },
58        );
59        self.connection_tx.send_replace(true);
60        self.connect_notify.notify_waiters();
61        id
62    }
63
64    pub async fn set_client_hello(&self, id: u64, hello: HelloPayload) {
65        let mut clients = self.clients.lock().await;
66        if let Some(c) = clients.get_mut(&id) {
67            c.hello = Some(hello);
68        }
69    }
70
71    pub async fn unregister_client(&self, id: u64) {
72        let mut clients = self.clients.lock().await;
73        clients.remove(&id);
74        let connected = !clients.is_empty();
75        self.connection_tx.send_replace(connected);
76    }
77
78    pub async fn touch_client(&self, id: u64) {
79        let mut clients = self.clients.lock().await;
80        if let Some(c) = clients.get_mut(&id) {
81            c.last_seen = tokio::time::Instant::now();
82        }
83    }
84
85    pub async fn reap_dead_clients(&self, timeout: Duration) -> Vec<u64> {
86        let now = tokio::time::Instant::now();
87        let mut clients = self.clients.lock().await;
88        let dead: Vec<u64> = clients
89            .iter()
90            .filter(|(_, c)| now.duration_since(c.last_seen) > timeout)
91            .map(|(&id, _)| id)
92            .collect();
93
94        for &id in &dead {
95            clients.remove(&id);
96        }
97
98        if !dead.is_empty() {
99            let connected = !clients.is_empty();
100            self.connection_tx.send_replace(connected);
101        }
102
103        dead
104    }
105
106    pub async fn is_connected(&self) -> bool {
107        !self.clients.lock().await.is_empty()
108    }
109
110    pub async fn wait_connected(&self) {
111        loop {
112            if self.is_connected().await {
113                return;
114            }
115            self.connect_notify.notified().await;
116        }
117    }
118
119    pub fn subscribe_connection_status(&self) -> watch::Receiver<bool> {
120        self.connection_rx.clone()
121    }
122
123    pub async fn connected_clients(&self) -> Vec<ClientInfo> {
124        let clients = self.clients.lock().await;
125        clients
126            .iter()
127            .map(|(&id, c)| ClientInfo {
128                id,
129                hello: c.hello.clone(),
130            })
131            .collect()
132    }
133
134    pub fn subscribe_events(&self) -> broadcast::Receiver<EventPayload> {
135        self.event_tx.subscribe()
136    }
137
138    pub fn dispatch_event(&self, event: EventPayload) {
139        let _ = self.event_tx.send(event);
140    }
141
142    pub async fn emit(&self, name: impl Into<String>, data: Value) -> Result<(), BridgeError> {
143        let msg = WireMsg::Event(EventPayload::new(name, data));
144        let json = msg.to_json()?;
145
146        let clients = self.clients.lock().await;
147        if clients.is_empty() {
148            return Err(BridgeError::no_clients(
149                "no gauges connected — event dropped",
150            ));
151        }
152
153        let mut send_failures = 0u32;
154        for client in clients.values() {
155            if client.tx.send(json.clone()).is_err() {
156                send_failures += 1;
157            }
158        }
159
160        if send_failures > 0 && send_failures as usize == clients.len() {
161            return Err(BridgeError::transport(
162                "all gauge connections failed to accept event",
163            ));
164        }
165
166        Ok(())
167    }
168
169    pub async fn command(
170        &self,
171        name: Option<&str>,
172        payload: Value,
173        timeout: Duration,
174    ) -> Result<Value, BridgeError> {
175        let id = Uuid::new_v4().to_string();
176
177        let cmd = match name {
178            Some(n) => CmdPayload::named(id.clone(), n, payload),
179            None => CmdPayload::new(id.clone(), payload),
180        };
181        let msg = WireMsg::Cmd(cmd);
182        let json = msg.to_json()?;
183
184        let (ack_tx, ack_rx) = oneshot::channel();
185        self.pending.lock().await.insert(id.clone(), ack_tx);
186
187        {
188            let clients = self.clients.lock().await;
189            if clients.is_empty() {
190                self.pending.lock().await.remove(&id);
191                return Err(BridgeError::no_clients(
192                    "no gauges connected — cannot send command",
193                ));
194            }
195            for client in clients.values() {
196                let _ = client.tx.send(json.clone());
197            }
198        }
199
200        let ack_result = tokio::time::timeout(timeout, ack_rx).await;
201
202        self.pending.lock().await.remove(&id);
203
204        match ack_result {
205            Ok(Ok(ack)) => {
206                if ack.ok {
207                    Ok(ack.response.unwrap_or(Value::Null))
208                } else {
209                    Err(BridgeError::application(
210                        ack.error.unwrap_or_else(|| "unknown error".into()),
211                    ))
212                }
213            }
214            Ok(Err(_)) => Err(BridgeError::transport(
215                "all gauge connections dropped before ack",
216            )),
217            Err(_) => Err(BridgeError::timeout(format!(
218                "no ack received within {timeout:?}"
219            ))),
220        }
221    }
222
223    pub async fn dispatch_ack(&self, ack: AckPayload) {
224        let tx = self.pending.lock().await.remove(&ack.id);
225        if let Some(tx) = tx {
226            let _ = tx.send(ack);
227        }
228    }
229
230    pub async fn send_to(&self, client_id: u64, json: String) -> Result<(), BridgeError> {
231        let clients = self.clients.lock().await;
232        let client = clients
233            .get(&client_id)
234            .ok_or_else(|| BridgeError::transport(format!("client {client_id} not found")))?;
235        client
236            .tx
237            .send(json)
238            .map_err(|_| BridgeError::transport(format!("client {client_id} channel closed")))
239    }
240
241    pub async fn broadcast(&self, json: String) -> Result<(), BridgeError> {
242        let clients = self.clients.lock().await;
243        if clients.is_empty() {
244            return Err(BridgeError::no_clients("no gauges connected"));
245        }
246        for client in clients.values() {
247            let _ = client.tx.send(json.clone());
248        }
249        Ok(())
250    }
251}