use std::collections::HashSet;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use spvirit_codec::spvd_decode::{DecodedValue, StructureDesc};
use spvirit_types::NtPayload;
use tokio::sync::{RwLock, mpsc};
use tracing::debug;
#[derive(Debug, Clone)]
pub struct PvInfo {
pub descriptor: StructureDesc,
pub writable: bool,
}
pub trait Source: Send + Sync {
fn claim(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<PvInfo>> + Send + '_>>;
fn get(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<NtPayload>> + Send + '_>>;
fn put(
&self,
name: &str,
value: &DecodedValue,
) -> Pin<Box<dyn Future<Output = Result<Vec<(String, NtPayload)>, String>> + Send + '_>>;
fn subscribe(
&self,
name: &str,
) -> Pin<Box<dyn Future<Output = Option<mpsc::Receiver<NtPayload>>> + Send + '_>>;
fn rpc(
&self,
_name: &str,
_args: &DecodedValue,
) -> Pin<Box<dyn Future<Output = Result<NtPayload, String>> + Send + '_>> {
Box::pin(async { Err("RPC not supported".to_string()) })
}
fn names(&self) -> Pin<Box<dyn Future<Output = Vec<String>> + Send + '_>>;
}
struct SourceEntry {
#[allow(dead_code)]
label: String,
order: i32,
source: Arc<dyn Source>,
}
pub struct SourceRegistry {
sources: RwLock<Vec<SourceEntry>>,
}
impl SourceRegistry {
pub fn new() -> Self {
Self {
sources: RwLock::new(Vec::new()),
}
}
pub async fn add(&self, label: impl Into<String>, order: i32, source: Arc<dyn Source>) {
let label = label.into();
debug!("SourceRegistry: adding source '{}' at order {}", label, order);
let mut sources = self.sources.write().await;
sources.push(SourceEntry {
label,
order,
source,
});
sources.sort_by_key(|e| e.order);
}
pub async fn remove(&self, label: &str) {
debug!("SourceRegistry: removing source '{}'", label);
let mut sources = self.sources.write().await;
sources.retain(|e| e.label != label);
}
pub async fn claim(&self, name: &str) -> Option<PvInfo> {
let sources = self.sources.read().await;
for entry in sources.iter() {
if let Some(info) = entry.source.claim(name).await {
return Some(info);
}
}
None
}
pub async fn has_pv(&self, name: &str) -> bool {
self.claim(name).await.is_some()
}
pub async fn get(&self, name: &str) -> Option<NtPayload> {
let sources = self.sources.read().await;
for entry in sources.iter() {
if entry.source.claim(name).await.is_some() {
return entry.source.get(name).await;
}
}
None
}
pub async fn get_descriptor(&self, name: &str) -> Option<StructureDesc> {
self.claim(name).await.map(|info| info.descriptor)
}
pub async fn is_writable(&self, name: &str) -> bool {
self.claim(name).await.is_some_and(|info| info.writable)
}
pub async fn put(
&self,
name: &str,
value: &DecodedValue,
) -> Result<Vec<(String, NtPayload)>, String> {
let sources = self.sources.read().await;
for entry in sources.iter() {
if entry.source.claim(name).await.is_some() {
return entry.source.put(name, value).await;
}
}
Err(format!("PV '{}' not found", name))
}
pub async fn subscribe(&self, name: &str) -> Option<mpsc::Receiver<NtPayload>> {
let sources = self.sources.read().await;
for entry in sources.iter() {
if entry.source.claim(name).await.is_some() {
return entry.source.subscribe(name).await;
}
}
None
}
pub async fn rpc(&self, name: &str, args: &DecodedValue) -> Result<NtPayload, String> {
let sources = self.sources.read().await;
for entry in sources.iter() {
if entry.source.claim(name).await.is_some() {
return entry.source.rpc(name, args).await;
}
}
Err(format!("RPC channel '{}' not found", name))
}
pub async fn names(&self) -> Vec<String> {
let sources = self.sources.read().await;
let mut seen = HashSet::new();
let mut all = Vec::new();
for entry in sources.iter() {
for name in entry.source.names().await {
if seen.insert(name.clone()) {
all.push(name);
}
}
}
all.sort();
all
}
}
impl Default for SourceRegistry {
fn default() -> Self {
Self::new()
}
}