use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use spvirit_codec::spvd_decode::DecodedValue;
use spvirit_server::pvstore::{PvInfo, Source};
use spvirit_server::PvaServer;
use spvirit_types::{NtPayload, NtScalar, ScalarValue};
use spvirit_server::simple_store::descriptor_for_payload;
use tokio::sync::mpsc;
struct ConstSource {
pvs: Vec<(&'static str, f64)>,
}
impl ConstSource {
fn new() -> Self {
Self {
pvs: vec![("CONST:PI", std::f64::consts::PI), ("CONST:E", std::f64::consts::E)],
}
}
fn lookup(&self, name: &str) -> Option<f64> {
self.pvs.iter().find(|(n, _)| *n == name).map(|(_, v)| *v)
}
}
impl Source for ConstSource {
fn claim(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<PvInfo>> + Send + '_>> {
let name = name.to_string();
Box::pin(async move {
let val = self.lookup(&name)?;
let payload = NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(val)));
Some(PvInfo {
descriptor: descriptor_for_payload(&payload),
writable: false,
})
})
}
fn get(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<NtPayload>> + Send + '_>> {
let name = name.to_string();
Box::pin(async move {
let val = self.lookup(&name)?;
Some(NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(val))))
})
}
fn put(
&self,
_name: &str,
_value: &DecodedValue,
) -> Pin<Box<dyn Future<Output = Result<Vec<(String, NtPayload)>, String>> + Send + '_>> {
Box::pin(async { Err("read-only source".to_string()) })
}
fn subscribe(
&self,
_name: &str,
) -> Pin<Box<dyn Future<Output = Option<mpsc::Receiver<NtPayload>>> + Send + '_>> {
Box::pin(async { None })
}
fn names(&self) -> Pin<Box<dyn Future<Output = Vec<String>> + Send + '_>> {
Box::pin(async move { self.pvs.iter().map(|(n, _)| n.to_string()).collect() })
}
}
struct ComputedSource;
impl Source for ComputedSource {
fn claim(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<PvInfo>> + Send + '_>> {
let name = name.to_string();
Box::pin(async move {
if name == "COMPUTED:TIME" {
let payload = NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(0.0)));
Some(PvInfo {
descriptor: descriptor_for_payload(&payload),
writable: false,
})
} else {
None
}
})
}
fn get(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<NtPayload>> + Send + '_>> {
let name = name.to_string();
Box::pin(async move {
if name == "COMPUTED:TIME" {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64();
Some(NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(now))))
} else {
None
}
})
}
fn put(
&self,
_name: &str,
_value: &DecodedValue,
) -> Pin<Box<dyn Future<Output = Result<Vec<(String, NtPayload)>, String>> + Send + '_>> {
Box::pin(async { Err("read-only source".to_string()) })
}
fn subscribe(
&self,
_name: &str,
) -> Pin<Box<dyn Future<Output = Option<mpsc::Receiver<NtPayload>>> + Send + '_>> {
Box::pin(async { None })
}
fn names(&self) -> Pin<Box<dyn Future<Output = Vec<String>> + Send + '_>> {
Box::pin(async { vec!["COMPUTED:TIME".to_string()] })
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let counter = Arc::new(AtomicU64::new(0));
let c = counter.clone();
let server = PvaServer::builder()
.ai("SIM:COUNTER", 0.0)
.source("constants", -10, Arc::new(ConstSource::new()))
.source("computed", 10, Arc::new(ComputedSource))
.build();
let store = server.store().clone();
tokio::spawn(async move {
loop {
let n = c.fetch_add(1, Ordering::Relaxed);
store
.set_value("SIM:COUNTER", ScalarValue::F64(n as f64))
.await;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
println!("multi_source server running — PVs: CONST:PI, CONST:E, SIM:COUNTER, COMPUTED:TIME");
server.run().await
}