Skip to main content

epics_seq/
channel_store.rs

1use std::sync::Mutex;
2
3use epics_base_rs::types::EpicsValue;
4
5/// Per-channel value slot holding the latest monitor/get result.
6#[derive(Debug, Clone)]
7pub struct ChannelValueSlot {
8    pub value: EpicsValue,
9    pub status: i16,
10    pub severity: i16,
11}
12
13impl Default for ChannelValueSlot {
14    fn default() -> Self {
15        Self {
16            value: EpicsValue::Double(0.0),
17            status: 0,
18            severity: 0,
19        }
20    }
21}
22
23/// Central channel value storage shared between monitor callbacks and state sets.
24///
25/// Each channel has its own `Mutex<ChannelValueSlot>`. Contention is low because
26/// monitors write rarely and state sets read only during sync.
27pub struct ChannelStore {
28    slots: Vec<Mutex<ChannelValueSlot>>,
29}
30
31impl ChannelStore {
32    pub fn new(num_channels: usize) -> Self {
33        let slots = (0..num_channels)
34            .map(|_| Mutex::new(ChannelValueSlot::default()))
35            .collect();
36        Self { slots }
37    }
38
39    /// Update a channel's value (called from monitor callback or pvGet).
40    pub fn set(&self, ch_id: usize, value: EpicsValue) {
41        if let Some(slot) = self.slots.get(ch_id) {
42            let mut s = slot.lock().unwrap();
43            s.value = value;
44        }
45    }
46
47    /// Update a channel's value with status/severity.
48    pub fn set_full(&self, ch_id: usize, value: EpicsValue, status: i16, severity: i16) {
49        if let Some(slot) = self.slots.get(ch_id) {
50            let mut s = slot.lock().unwrap();
51            s.value = value;
52            s.status = status;
53            s.severity = severity;
54        }
55    }
56
57    /// Read a channel's current value.
58    pub fn get(&self, ch_id: usize) -> Option<EpicsValue> {
59        self.slots
60            .get(ch_id)
61            .map(|slot| slot.lock().unwrap().value.clone())
62    }
63
64    /// Read a channel's full slot (value + metadata).
65    pub fn get_full(&self, ch_id: usize) -> Option<ChannelValueSlot> {
66        self.slots
67            .get(ch_id)
68            .map(|slot| slot.lock().unwrap().clone())
69    }
70
71    pub fn num_channels(&self) -> usize {
72        self.slots.len()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_set_and_get() {
82        let store = ChannelStore::new(3);
83        store.set(0, EpicsValue::Double(42.0));
84        match store.get(0).unwrap() {
85            EpicsValue::Double(v) => assert!((v - 42.0).abs() < 1e-10),
86            _ => panic!("wrong type"),
87        }
88    }
89
90    #[test]
91    fn test_default_value() {
92        let store = ChannelStore::new(1);
93        match store.get(0).unwrap() {
94            EpicsValue::Double(v) => assert!((v - 0.0).abs() < 1e-10),
95            _ => panic!("wrong type"),
96        }
97    }
98
99    #[test]
100    fn test_set_full() {
101        let store = ChannelStore::new(2);
102        store.set_full(1, EpicsValue::Long(7), 1, 2);
103        let slot = store.get_full(1).unwrap();
104        assert_eq!(slot.status, 1);
105        assert_eq!(slot.severity, 2);
106        match slot.value {
107            EpicsValue::Long(v) => assert_eq!(v, 7),
108            _ => panic!("wrong type"),
109        }
110    }
111
112    #[test]
113    fn test_invalid_channel() {
114        let store = ChannelStore::new(1);
115        assert!(store.get(99).is_none());
116        // set on invalid channel is a no-op
117        store.set(99, EpicsValue::Double(1.0));
118    }
119
120    #[test]
121    fn test_concurrent_access() {
122        use std::sync::Arc;
123        use std::thread;
124
125        let store = Arc::new(ChannelStore::new(1));
126        let store2 = store.clone();
127
128        let writer = thread::spawn(move || {
129            for i in 0..100 {
130                store2.set(0, EpicsValue::Double(i as f64));
131            }
132        });
133
134        let reader = thread::spawn(move || {
135            for _ in 0..100 {
136                let _ = store.get(0);
137            }
138        });
139
140        writer.join().unwrap();
141        reader.join().unwrap();
142    }
143}