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