Skip to main content

spvirit_server/
pvstore.rs

1//! The [`Source`] trait — an object-safe abstraction over any PV data source,
2//! and [`SourceRegistry`] — a dynamic, priority-ordered collection of sources.
3//!
4//! Protocol handlers use `SourceRegistry` to resolve PV names across multiple
5//! registered sources, allowing different backends (in-memory records, hardware
6//! drivers, proxies, etc.) to coexist in a single PVA server. basically what pvxs does with its provider registry.
7
8use std::collections::HashSet;
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::Arc;
12
13use spvirit_codec::spvd_decode::{DecodedValue, StructureDesc};
14use spvirit_types::NtPayload;
15use tokio::sync::{RwLock, mpsc};
16use tracing::debug;
17
18// ---------------------------------------------------------------------------
19// PvInfo — metadata returned by Source::claim
20// ---------------------------------------------------------------------------
21
22/// Metadata about a PV as reported by the source that owns it.
23#[derive(Debug, Clone)]
24pub struct PvInfo {
25    /// Structure descriptor for the PV.
26    pub descriptor: StructureDesc,
27    /// Whether the PV accepts PUT operations.
28    pub writable: bool,
29}
30
31// ---------------------------------------------------------------------------
32// Source — the object-safe provider trait
33// ---------------------------------------------------------------------------
34
35/// Object-safe trait for a PV data provider.
36///
37/// A source is responsible for a set of PV names. The server's
38/// [`SourceRegistry`] iterates sources in priority order to find the first
39/// that *claims* a given name.
40///
41/// # Implementing a custom source
42///
43/// ```rust,ignore
44/// use spvirit_server::pvstore::{Source, PvInfo};
45///
46/// struct MySource { /* ... */ }
47///
48/// impl Source for MySource {
49///     fn claim(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<PvInfo>> + Send + '_>> {
50///         Box::pin(async move { /* ... */ })
51///     }
52///     // ...other methods...
53/// }
54/// ```
55pub trait Source: Send + Sync {
56    /// Check whether this source owns `name` and, if so, return its metadata.
57    ///
58    /// Return `None` to let the registry try the next source.
59    fn claim(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<PvInfo>> + Send + '_>>;
60
61    /// Read the current value of a PV.
62    ///
63    /// Only called for PVs this source has previously claimed.
64    fn get(&self, name: &str) -> Pin<Box<dyn Future<Output = Option<NtPayload>> + Send + '_>>;
65
66    /// Apply a PUT value to a PV.
67    ///
68    /// Returns the list of `(pv_name, updated_payload)` pairs for all PVs
69    /// that changed as a result (e.g. forward-link propagation).
70    fn put(
71        &self,
72        name: &str,
73        value: &DecodedValue,
74    ) -> Pin<Box<dyn Future<Output = Result<Vec<(String, NtPayload)>, String>> + Send + '_>>;
75
76    /// Subscribe to value-change notifications on a PV.
77    ///
78    /// Returns `None` if the PV does not support subscription.
79    fn subscribe(
80        &self,
81        name: &str,
82    ) -> Pin<Box<dyn Future<Output = Option<mpsc::Receiver<NtPayload>>> + Send + '_>>;
83
84    /// Execute an RPC call on a channel.
85    ///
86    /// `name` is the channel name, `args` is the decoded request structure.
87    /// Returns the response payload on success.
88    ///
89    /// The default implementation returns an error — override it in sources
90    /// that provide RPC endpoints.
91    fn rpc(
92        &self,
93        _name: &str,
94        _args: &DecodedValue,
95    ) -> Pin<Box<dyn Future<Output = Result<NtPayload, String>> + Send + '_>> {
96        Box::pin(async { Err("RPC not supported".to_string()) })
97    }
98
99    /// List all PV names provided by this source.
100    fn names(&self) -> Pin<Box<dyn Future<Output = Vec<String>> + Send + '_>>;
101}
102
103// ---------------------------------------------------------------------------
104// SourceEntry — one registered source with its priority
105// ---------------------------------------------------------------------------
106
107struct SourceEntry {
108    /// Human-readable label for debugging / logging.
109    #[allow(dead_code)]
110    label: String,
111    /// Lower values are checked first.
112    order: i32,
113    /// The actual source implementation.
114    source: Arc<dyn Source>,
115}
116
117// ---------------------------------------------------------------------------
118// SourceRegistry — ordered collection of sources
119// ---------------------------------------------------------------------------
120
121/// A dynamic, priority-ordered registry of [`Source`] providers.
122///
123/// PV name resolution iterates sources from lowest `order` to highest and
124/// delegates to the first source that claims the name.
125pub struct SourceRegistry {
126    sources: RwLock<Vec<SourceEntry>>,
127}
128
129impl SourceRegistry {
130    /// Create an empty registry.
131    pub fn new() -> Self {
132        Self {
133            sources: RwLock::new(Vec::new()),
134        }
135    }
136
137    /// Register a new source at the given priority.
138    ///
139    /// Lower `order` values are queried first.
140    pub async fn add(&self, label: impl Into<String>, order: i32, source: Arc<dyn Source>) {
141        let label = label.into();
142        debug!(
143            "SourceRegistry: adding source '{}' at order {}",
144            label, order
145        );
146        let mut sources = self.sources.write().await;
147        sources.push(SourceEntry {
148            label,
149            order,
150            source,
151        });
152        sources.sort_by_key(|e| e.order);
153    }
154
155    /// Remove all sources with the given label.
156    pub async fn remove(&self, label: &str) {
157        debug!("SourceRegistry: removing source '{}'", label);
158        let mut sources = self.sources.write().await;
159        sources.retain(|e| e.label != label);
160    }
161
162    // ── Delegating operations ────────────────────────────────────────
163
164    /// Find the first source that claims `name` and return its metadata.
165    pub async fn claim(&self, name: &str) -> Option<PvInfo> {
166        let sources = self.sources.read().await;
167        for entry in sources.iter() {
168            if let Some(info) = entry.source.claim(name).await {
169                return Some(info);
170            }
171        }
172        None
173    }
174
175    /// Check whether any source claims the given PV name.
176    pub async fn has_pv(&self, name: &str) -> bool {
177        self.claim(name).await.is_some()
178    }
179
180    /// Get the value from the first source that claims the PV.
181    pub async fn get(&self, name: &str) -> Option<NtPayload> {
182        let sources = self.sources.read().await;
183        for entry in sources.iter() {
184            if entry.source.claim(name).await.is_some() {
185                return entry.source.get(name).await;
186            }
187        }
188        None
189    }
190
191    /// Get the structure descriptor from the first source that claims the PV.
192    pub async fn get_descriptor(&self, name: &str) -> Option<StructureDesc> {
193        self.claim(name).await.map(|info| info.descriptor)
194    }
195
196    /// Check if the PV is writable (via the first claiming source).
197    pub async fn is_writable(&self, name: &str) -> bool {
198        self.claim(name).await.is_some_and(|info| info.writable)
199    }
200
201    /// Delegate a PUT to the first source that claims the PV.
202    pub async fn put(
203        &self,
204        name: &str,
205        value: &DecodedValue,
206    ) -> Result<Vec<(String, NtPayload)>, String> {
207        let sources = self.sources.read().await;
208        for entry in sources.iter() {
209            if entry.source.claim(name).await.is_some() {
210                return entry.source.put(name, value).await;
211            }
212        }
213        Err(format!("PV '{}' not found", name))
214    }
215
216    /// Subscribe via the first source that claims the PV.
217    pub async fn subscribe(&self, name: &str) -> Option<mpsc::Receiver<NtPayload>> {
218        let sources = self.sources.read().await;
219        for entry in sources.iter() {
220            if entry.source.claim(name).await.is_some() {
221                return entry.source.subscribe(name).await;
222            }
223        }
224        None
225    }
226
227    /// Execute an RPC call via the first source that claims the channel.
228    pub async fn rpc(&self, name: &str, args: &DecodedValue) -> Result<NtPayload, String> {
229        let sources = self.sources.read().await;
230        for entry in sources.iter() {
231            if entry.source.claim(name).await.is_some() {
232                return entry.source.rpc(name, args).await;
233            }
234        }
235        Err(format!("RPC channel '{}' not found", name))
236    }
237
238    /// Collect all PV names from every registered source.
239    pub async fn names(&self) -> Vec<String> {
240        let sources = self.sources.read().await;
241        let mut seen = HashSet::new();
242        let mut all = Vec::new();
243        for entry in sources.iter() {
244            for name in entry.source.names().await {
245                if seen.insert(name.clone()) {
246                    all.push(name);
247                }
248            }
249        }
250        all.sort();
251        all
252    }
253}
254
255impl Default for SourceRegistry {
256    fn default() -> Self {
257        Self::new()
258    }
259}