Skip to main content

openlogi_device/channel/
pool.rs

1//! Shared HID++ channels for long-running agent sessions.
2
3use std::sync::{Arc, Weak};
4
5use hidpp::channel::HidppChannel;
6use tokio::sync::Mutex;
7
8use crate::backend::{BackendError, HidBackend};
9use crate::channel::route::{DeviceRoute, open_route_channel};
10
11/// Reuses one open HID++ channel for routes on the same receiver.
12#[derive(Clone)]
13pub struct ChannelPool {
14    /// The HID stack routes are opened through. `openlogi-hid` supplies this
15    /// host's; tests and other hosts supply their own.
16    backend: Arc<dyn HidBackend>,
17    entries: Arc<Mutex<Vec<PoolEntry>>>,
18}
19
20struct PoolEntry {
21    route: DeviceRoute,
22    channel: Weak<HidppChannel>,
23}
24
25impl ChannelPool {
26    /// A pool that opens through `backend`.
27    #[must_use]
28    pub fn with_backend(backend: Arc<dyn HidBackend>) -> Self {
29        Self {
30            backend,
31            entries: Arc::new(Mutex::new(Vec::new())),
32        }
33    }
34
35    /// Return a shared channel reaching `route`, opening it when necessary.
36    pub async fn open(
37        &self,
38        route: &DeviceRoute,
39    ) -> Result<Option<Arc<HidppChannel>>, BackendError> {
40        let mut entries = self.entries.lock().await;
41        entries.retain(|entry| entry.channel.strong_count() > 0);
42        if let Some(channel) = entries.iter().find_map(|entry| {
43            entry
44                .route
45                .shares_transport(route)
46                .then(|| entry.channel.upgrade())
47                .flatten()
48        }) {
49            return Ok(Some(channel));
50        }
51        let Some(channel) = open_route_channel(&*self.backend, route).await? else {
52            return Ok(None);
53        };
54        entries.push(PoolEntry {
55            route: route.clone(),
56            channel: Arc::downgrade(&channel),
57        });
58        Ok(Some(channel))
59    }
60}