Skip to main content

tango/server/
command.rs

1//! Implementation of command instantiations.
2
3use std::sync::{Arc, Mutex};
4use std::time::{Duration, SystemTime};
5
6use crate::error::{Error, ErrorKind, Result};
7use crate::value::{CommandDataKind, Value};
8use crate::dev::{AttrRead, CommandInfo, DevSource, DisplayLevel, RecordedResult};
9
10use super::dev::{some_doc, Access, Dev, ModelRef, ReadCtx};
11use super::def::{CommandDef, ExecFn};
12use super::attr::{Attr, AttrRole};
13use super::history::History;
14use super::{Model, INIT_NAME};
15
16
17/// A command of a single device, which is what the server runs when a client asks for it:
18/// one of its interface, around the definition it was defined with, or one of the three
19/// every device has whatever its class: the State, the Status and the Init.
20///
21/// The definition is shared with the class when the class defines it, and owned by the
22/// device alone when the device added it for itself, see [`Dev::add_command`].
23pub struct Command<M: Model>(CommandRole<M>);
24
25/// What a command is on its device, which is how it is executed.
26enum CommandRole<M: Model> {
27    /// A command of the interface of the device, see [`InterfaceCommand`].
28    Interface(InterfaceCommand<M>),
29    /// The State or the Status, which gives the value of that attribute as a read of it
30    /// gives it, see [`Command::exec`]. The command reads the attribute itself,
31    /// and therefore its history: a reading of either is always of a valid quality, so its
32    /// value alone is all the command result there is to it.
33    Attr(Arc<Attr<M>>),
34    /// The Init, which deletes the model of the device and builds a new one.
35    Init,
36}
37
38/// A command of the interface of a device, executed through its definition, with the
39/// results recorded of it, see [`Command::push_history`].
40struct InterfaceCommand<M: Model> {
41    /// The definition, shared with the class when the class defines it.
42    def: Arc<CommandDef<M>>,
43    /// The results recorded of that command.
44    history: Mutex<History<Value>>,
45}
46
47impl<M: Model> Command<M> {
48
49    /// A new command of the interface of a device, from its definition.
50    pub(super) fn new(def: Arc<CommandDef<M>>) -> Self {
51        Self(CommandRole::Interface(InterfaceCommand { 
52            def, 
53            history: Mutex::new(History::new()),
54        }))
55    }
56
57    /// The command of the State or the Status of a device, reading that attribute.
58    pub(super) fn new_with_attr(attr: Arc<Attr<M>>) -> Self {
59        debug_assert_ne!(attr.role(), AttrRole::Interface, "only the State and the Status are commands");
60        Self(CommandRole::Attr(attr))
61    }
62
63    /// The Init command of a device.
64    pub(super) fn new_with_init() -> Self {
65        Self(CommandRole::Init)
66    }
67
68    /// The name of that command, as its definition gave it.
69    #[inline]
70    pub fn name(&self) -> &str {
71        match &self.0 {
72            CommandRole::Interface(InterfaceCommand { def, .. }) => &def.name,
73            CommandRole::Attr(attr) => attr.name(),
74            CommandRole::Init => INIT_NAME,
75        }
76    }
77
78    /// The period the definition of that command asks it to be polled at, which the
79    /// database has the last word on, see [`CommandDef::polled`].
80    pub(super) fn poll_period(&self) -> Option<Duration> {
81        match &self.0 {
82            CommandRole::Interface(InterfaceCommand { def, .. }) => def.poll_period,
83            CommandRole::Attr(_) |
84            CommandRole::Init => None,
85        }
86    }
87
88    /// Whether that command can be polled, which is whether its results can be recorded,
89    /// see [`Self::push_history`]: one taking no argument, the Init aside.
90    pub(super) fn is_pollable(&self) -> bool {
91        match &self.0 {
92            CommandRole::Interface(InterfaceCommand { def, .. }) => matches!(def.in_kind, None | Some(CommandDataKind::Void)),
93            CommandRole::Attr(_) => true,
94            CommandRole::Init => false,
95        }
96    }
97
98    /// What that command reads of its device: the State or the Status for their command,
99    /// nothing of the kind for the others, see [`Dev::no_model_answer`].
100    pub(super) fn attr_role(&self) -> AttrRole {
101        match &self.0 {
102            CommandRole::Attr(attr) => attr.role(),
103            CommandRole::Interface(_) |
104            CommandRole::Init => AttrRole::Interface,
105        }
106    }
107
108    /// Whether that command is the Init, which is executed apart from the others, see
109    /// [`Dev::exec_command`].
110    pub(super) fn is_init(&self) -> bool {
111        matches!(self.0, CommandRole::Init)
112    }
113
114    /// The access executing that command needs to the model of its device, see
115    /// [`Self::exec`], as [`Attr::read_access`] is for reading an attribute.
116    pub(super) fn exec_access(&self, dev: &Dev<M>, ctx: &ReadCtx<M>) -> Access {
117        match &self.0 {
118            CommandRole::Interface(InterfaceCommand { def, .. }) => match &def.exec {
119                Some(ExecFn::ExclusiveSync(_) | ExecFn::ExclusiveAsync(_)) => Access::Exclusive,
120                _ => Access::Shared,
121            },
122            CommandRole::Attr(attr) => attr.read_access(dev, ctx),
123            CommandRole::Init => Access::Exclusive,
124        }
125    }
126
127    /// Whether executing that command may have to derive the state of the device, see
128    /// [`ReadCtx::new`].
129    pub(super) fn involves_state(&self) -> bool {
130        matches!(self.0, CommandRole::Attr(_))
131    }
132
133    /// Whether executing that command may block, its execution function being synchronous,
134    /// see [`crate::pool::run`], or reading its attribute or initializing the device may.
135    pub(super) fn may_block(&self, dev: &Dev<M>, ctx: &ReadCtx<M>) -> bool {
136        match &self.0 {
137            CommandRole::Interface(InterfaceCommand { def, .. }) => 
138                matches!(&def.exec, Some(ExecFn::SharedSync(_) | ExecFn::ExclusiveSync(_))),
139            CommandRole::Attr(attr) => 
140                attr.read_may_block(dev, ctx),
141            CommandRole::Init => 
142                dev.init_may_block(),
143        }
144    }
145
146    /// What a client is told of that command: what its definition advertises, a side of it
147    /// no execution function derived a kind for being void. The three every device has are
148    /// described by the server itself, the State and the Status as their attribute is.
149    pub fn info(&self) -> CommandInfo {
150        match &self.0 {
151            CommandRole::Interface(InterfaceCommand { def, .. }) => CommandInfo {
152                display_level: def.display_level,
153                in_kind: def.in_kind.unwrap_or(CommandDataKind::Void),
154                out_kind: def.out_kind.unwrap_or(CommandDataKind::Void),
155                in_doc: some_doc(&def.in_doc),
156                out_doc: some_doc(&def.out_doc),
157            },
158            CommandRole::Attr(attr) => CommandInfo {
159                display_level: attr.display_level(),
160                in_kind: CommandDataKind::Void,
161                out_kind: match attr.role() {
162                    AttrRole::State => CommandDataKind::State,
163                    AttrRole::Status => CommandDataKind::String,
164                    AttrRole::Interface => unreachable!("only the State and the Status are commands"),
165                },
166                in_doc: None,
167                out_doc: attr.config().doc,
168            },
169            CommandRole::Init => CommandInfo {
170                display_level: DisplayLevel::Operator,
171                in_kind: CommandDataKind::Void,
172                out_kind: CommandDataKind::Void,
173                in_doc: None,
174                out_doc: None,
175            },
176        }
177    }
178
179    /// Record that result in the history of that command, the oldest one being dropped
180    /// when the history is full, see [`Self::set_history_depth`]. A run that failed is
181    /// recorded as well, as the error it failed with.
182    /// 
183    /// The latest result is what a request from the cache is answered with, for that long
184    /// when given a validity, after which the command is executed again, or a request from
185    /// the cache alone fails as outdated; without one, it answers until the next is
186    /// recorded.
187    /// 
188    /// Only a command taking no argument is recorded, as with the polling of cppTango: the
189    /// result of one taking an argument depends on it. The State and the Status are
190    /// recorded in the history of their attribute, see [`Attr::push_history`], and the Init
191    /// is never recorded.
192    pub fn push_history(&self, result: Result<Value>, valid_for: Option<Duration>) -> Result<()> {
193        match &self.0 {
194            CommandRole::Interface(InterfaceCommand { def, history }) => {
195                if !matches!(def.in_kind, None | Some(CommandDataKind::Void)) {
196                    return Err(Error::new(ErrorKind::InvalidData)
197                        .with_description(format!("Command {:?} cannot be recorded, it takes an argument", 
198                            self.name())));
199                }
200                history.lock().unwrap().push(SystemTime::now(), result, valid_for);
201                Ok(())
202            }
203            CommandRole::Attr(attr) => attr.push_history(result.map(AttrRead::new), valid_for),
204            CommandRole::Init => Err(Error::new(ErrorKind::InvalidData)
205                .with_description(format!("Command {:?} cannot be recorded", self.name()))),
206        }
207    }
208
209    /// The latest results recorded in the history of that command, oldest first and at
210    /// most `max` of them, see [`Self::push_history`].
211    pub fn history(&self, max: usize) -> Vec<RecordedResult<Value>> {
212        match &self.0 {
213            CommandRole::Interface(InterfaceCommand { history, .. }) => history.lock().unwrap().entries(max),
214            // Every reading of the State and the Status has a value, see [`Attr::push_history`].
215            CommandRole::Attr(attr) => attr.history(max).into_iter()
216                .map(|entry| entry.map(|read| read.map(|read| read.value
217                    .expect("the State and the Status are always recorded with a value"))))
218                .collect(),
219            CommandRole::Init => Vec::new(),
220        }
221    }
222
223    /// Forget every result recorded in the history of that command, so that a request from
224    /// the cache finds none.
225    pub fn clear_history(&self) {
226        match &self.0 {
227            CommandRole::Interface(InterfaceCommand { history, .. }) => history.lock().unwrap().clear(),
228            CommandRole::Attr(attr) => attr.clear_history(),
229            CommandRole::Init => {}
230        }
231    }
232
233    /// How many results the history of that command keeps at most, none for the Init.
234    pub fn history_depth(&self) -> usize {
235        match &self.0 {
236            CommandRole::Interface(InterfaceCommand { history, .. }) => history.lock().unwrap().depth(),
237            CommandRole::Attr(attr) => attr.history_depth(),
238            CommandRole::Init => 0,
239        }
240    }
241
242    /// Set how many results the history of that command keeps at most, the oldest ones
243    /// beyond that being dropped now. A depth of zero records nothing.
244    pub fn set_history_depth(&self, depth: usize) {
245        match &self.0 {
246            CommandRole::Interface(InterfaceCommand { history, .. }) => history.lock().unwrap().set_depth(depth),
247            CommandRole::Attr(attr) => attr.set_history_depth(depth),
248            CommandRole::Init => {}
249        }
250    }
251
252    /// The error a request from the cache alone fails with when nothing is recorded of that
253    /// command.
254    pub(super) fn not_polled_err(&self, dev: &Dev<M>) -> Error {
255        Error::new(ErrorKind::CommandNotPolled)
256            .with_description(format!("Command {:?} of device {:?} has no result recorded to be \
257                answered from the cache", self.name(), dev.name()))
258    }
259
260    /// Execute that command on the model, borrowed with the access [`Self::exec_access`]
261    /// asked for, and give its result, or answer with the latest result recorded when asked
262    /// from the cache, see [`Self::push_history`], as [`Attr::read`] reads an attribute.
263    ///
264    /// The State and the Status give the value of their attribute as a read of it gives
265    /// it, history included. The Init cannot be executed on a borrowed model, see
266    /// [`Dev::exec_command`].
267    pub(super) async fn exec(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, input: Value, ctx: &mut ReadCtx<M>, source: DevSource) -> Result<Value> {
268
269        let (def, history) = match &self.0 {
270            CommandRole::Interface(InterfaceCommand { def, history }) => (def, history),
271            CommandRole::Attr(attr) => {
272                return attr.read(dev, model, ctx, source).await?.value
273                    .ok_or_else(|| Error::new(ErrorKind::Internal)
274                        .with_description(format!("Attribute {:?} of device {:?} was read with no value",
275                            attr.name(), dev.name())));
276            }
277            CommandRole::Init => {
278                return Err(Error::new(ErrorKind::Internal)
279                    .with_description(format!("Command {:?} of device {:?} cannot be executed on a borrowed model",
280                        self.name(), dev.name())));
281            }
282        };
283
284        // A request from the cache is answered with the latest result recorded, and only a
285        // request from the cache alone fails without one, the other executing the command.
286        if let DevSource::CacheDev | DevSource::Cache = source {
287            
288            let history = history.lock().unwrap();
289            if let Some(result) = history.latest() {
290                return result.clone();
291            }
292
293            if source == DevSource::Cache {
294                return Err(if history.is_empty() {
295                    self.not_polled_err(dev)
296                } else {
297                    Error::new(ErrorKind::ValueOutdated)
298                        .with_description(format!("The latest result recorded of command {:?} of device {:?} is outdated", 
299                            self.name(), dev.name()))
300                });
301            }
302            
303        }
304
305        match &def.exec {
306            Some(ExecFn::SharedSync(func)) => func(model.shared(), dev, input),
307            Some(ExecFn::ExclusiveSync(func)) => func(model.exclusive()?, dev, input),
308            Some(ExecFn::SharedAsync(func)) => func(model.shared(), dev, input).await,
309            Some(ExecFn::ExclusiveAsync(func)) => func(model.exclusive()?, dev, input).await,
310            None => Err(Error::not_implemented()
311                .with_description(format!("Command {:?} of device {:?} has no execution function", self.name(), dev.name()))),
312        }
313
314    }
315
316}