1pub use crate::State;
20use crate::io::{
21 duration_from_str, duration_to_str, maybe_duration_from_str, maybe_duration_to_str,
22};
23use der::{Decode, Encode, Enumerated, Reader};
24use hifitime::{Duration, Unit};
25use serde::Deserialize;
26use serde::Serialize;
27use std::fmt::Debug;
28use typed_builder::TypedBuilder;
29
30#[cfg(feature = "python")]
31use pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes, types::PyType};
32
33#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize, Default, Enumerated)]
35#[repr(u8)]
36#[cfg_attr(feature = "python", pyclass(from_py_object, eq, eq_int))]
37pub enum Handoff {
38 #[default]
40 Eager = 0,
41 Greedy = 1,
43 Overlap = 2,
45}
46
47#[cfg(feature = "python")]
48#[cfg_attr(feature = "python", pymethods)]
49impl Handoff {
50 fn __repr__(&self) -> String {
51 format!("{self:?}")
52 }
53
54 fn __str__(&self) -> String {
55 format!("{self:?}")
56 }
57}
58
59#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize, TypedBuilder)]
66#[cfg_attr(feature = "python", pyclass(from_py_object))]
67#[builder(doc)]
68pub struct Scheduler {
69 #[builder(default)]
71 pub handoff: Handoff,
72 #[builder(default)]
74 pub cadence: Cadence,
75 #[builder(default = 10)]
77 pub min_samples: u32,
78 #[builder(default = Some(Unit::Second * 1.0), setter(strip_option))]
81 #[serde(
82 serialize_with = "maybe_duration_to_str",
83 deserialize_with = "maybe_duration_from_str"
84 )]
85 pub sample_alignment: Option<Duration>,
86}
87
88#[derive(Copy, Clone, Deserialize, PartialEq, Serialize, Default)]
90pub enum Cadence {
91 #[default]
92 Continuous,
93 Intermittent {
95 #[serde(
96 serialize_with = "duration_to_str",
97 deserialize_with = "duration_from_str"
98 )]
99 on: Duration,
100 #[serde(
101 serialize_with = "duration_to_str",
102 deserialize_with = "duration_from_str"
103 )]
104 off: Duration,
105 },
106}
107
108impl<'a> Decode<'a> for Cadence {
109 fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
110 let tag = decoder.decode::<u8>()?;
111 match tag {
112 0 => Ok(Self::Continuous),
113 1 => {
114 let on_ns = decoder.decode::<i128>()?;
115 let off_ns = decoder.decode::<i128>()?;
116 Ok(Self::Intermittent {
117 on: Duration::from_total_nanoseconds(on_ns),
118 off: Duration::from_total_nanoseconds(off_ns),
119 })
120 }
121 _ => Err(der::ErrorKind::Value {
122 tag: der::Tag::Integer,
123 }
124 .into()),
125 }
126 }
127}
128
129impl Encode for Cadence {
130 fn encoded_len(&self) -> der::Result<der::Length> {
131 match self {
132 Self::Continuous => 0u8.encoded_len(),
133 Self::Intermittent { on, off } => {
134 1u8.encoded_len()?
135 + on.total_nanoseconds().encoded_len()?
136 + off.total_nanoseconds().encoded_len()?
137 }
138 }
139 }
140
141 fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
142 match self {
143 Self::Continuous => 0u8.encode(encoder),
144 Self::Intermittent { on, off } => {
145 1u8.encode(encoder)?;
146 on.total_nanoseconds().encode(encoder)?;
147 off.total_nanoseconds().encode(encoder)
148 }
149 }
150 }
151}
152
153#[cfg(feature = "python")]
154#[pyclass(from_py_object, name = "Cadence")]
155#[derive(Clone, Debug)]
156pub struct PyCadence {
157 pub inner: Cadence,
158}
159
160#[cfg(feature = "python")]
161#[pymethods]
162impl PyCadence {
163 #[classmethod]
165 fn continuous(_cls: &Bound<'_, PyType>) -> Self {
166 Self {
167 inner: Cadence::Continuous,
168 }
169 }
170
171 #[classmethod]
177 fn intermittent(_cls: &Bound<'_, PyType>, on: Duration, off: Duration) -> Self {
178 Self {
179 inner: Cadence::Intermittent { on, off },
180 }
181 }
182
183 fn __repr__(&self) -> String {
184 format!("{:?}", self.inner)
185 }
186
187 fn __str__(&self) -> String {
188 format!("{:?}", self.inner)
189 }
190
191 #[classmethod]
196 pub fn from_asn1(_cls: &Bound<'_, PyType>, data: &[u8]) -> PyResult<Self> {
197 match Cadence::from_der(data) {
198 Ok(obj) => Ok(Self { inner: obj }),
199 Err(e) => Err(PyValueError::new_err(format!("ASN.1 decoding error: {e}"))),
200 }
201 }
202
203 pub fn to_asn1<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
207 let mut buf = Vec::new();
208 match self.inner.encode_to_vec(&mut buf) {
209 Ok(_) => Ok(PyBytes::new(py, &buf)),
210 Err(e) => Err(PyValueError::new_err(format!("ASN.1 encoding error: {e}"))),
211 }
212 }
213}
214
215impl Debug for Cadence {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 Self::Continuous => write!(f, "Continuous"),
219 Self::Intermittent { on, off } => f
220 .debug_struct("Intermittent")
221 .field("on", &format!("{on}"))
222 .field("off", &format!("{off}"))
223 .finish(),
224 }
225 }
226}
227
228impl<'a> Decode<'a> for Scheduler {
229 fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
230 let handoff = decoder.decode()?;
231 let cadence = decoder.decode()?;
232 let min_samples = decoder.decode()?;
233 let sample_alignment_ns = if decoder.decode::<bool>()? {
234 Some(decoder.decode::<i128>()?)
235 } else {
236 None
237 };
238
239 Ok(Self {
240 handoff,
241 cadence,
242 min_samples,
243 sample_alignment: sample_alignment_ns.map(Duration::from_total_nanoseconds),
244 })
245 }
246}
247
248impl Encode for Scheduler {
249 fn encoded_len(&self) -> der::Result<der::Length> {
250 let mut len = (self.handoff.encoded_len()?
251 + self.cadence.encoded_len()?
252 + self.min_samples.encoded_len()?
253 + self.sample_alignment.is_some().encoded_len()?)?;
254
255 if let Some(sa) = self.sample_alignment {
256 len = (len + sa.total_nanoseconds().encoded_len()?)?;
257 }
258 Ok(len)
259 }
260
261 fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
262 self.handoff.encode(encoder)?;
263 self.cadence.encode(encoder)?;
264 self.min_samples.encode(encoder)?;
265 if let Some(sa) = self.sample_alignment {
266 true.encode(encoder)?;
267 sa.total_nanoseconds().encode(encoder)?;
268 } else {
269 false.encode(encoder)?;
270 }
271 Ok(())
272 }
273}
274
275#[cfg(feature = "python")]
276#[cfg_attr(feature = "python", pymethods)]
277impl Scheduler {
278 #[new]
279 #[pyo3(signature = (handoff=Handoff::Eager, cadence=None, min_samples=10, sample_alignment=None))]
280 fn py_new(
281 handoff: Handoff,
282 cadence: Option<PyCadence>,
283 min_samples: u32,
284 sample_alignment: Option<Duration>,
285 ) -> Self {
286 Self {
287 handoff,
288 cadence: cadence.map(|c| c.inner).unwrap_or_default(),
289 min_samples,
290 sample_alignment,
291 }
292 }
293
294 #[getter]
295 fn get_handoff(&self) -> Handoff {
296 self.handoff
297 }
298
299 #[setter]
300 fn set_handoff(&mut self, handoff: Handoff) {
301 self.handoff = handoff;
302 }
303
304 #[getter]
305 fn get_cadence(&self) -> PyCadence {
306 PyCadence {
307 inner: self.cadence,
308 }
309 }
310
311 #[setter]
312 fn set_cadence(&mut self, cadence: PyCadence) {
313 self.cadence = cadence.inner;
314 }
315
316 #[getter]
317 fn get_min_samples(&self) -> u32 {
318 self.min_samples
319 }
320
321 #[setter]
322 fn set_min_samples(&mut self, min_samples: u32) {
323 self.min_samples = min_samples;
324 }
325
326 #[getter]
327 fn get_sample_alignment(&self) -> Option<Duration> {
328 self.sample_alignment
329 }
330
331 #[setter]
332 fn set_sample_alignment(&mut self, sample_alignment: Option<Duration>) {
333 self.sample_alignment = sample_alignment;
334 }
335
336 fn __repr__(&self) -> String {
337 format!("{self:?}")
338 }
339
340 fn __str__(&self) -> String {
341 format!("{self:?}")
342 }
343
344 #[classmethod]
349 pub fn from_asn1(_cls: &Bound<'_, PyType>, data: &[u8]) -> PyResult<Self> {
350 match Self::from_der(data) {
351 Ok(obj) => Ok(obj),
352 Err(e) => Err(PyValueError::new_err(format!("ASN.1 decoding error: {e}"))),
353 }
354 }
355
356 pub fn to_asn1<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
360 let mut buf = Vec::new();
361 match self.encode_to_vec(&mut buf) {
362 Ok(_) => Ok(PyBytes::new(py, &buf)),
363 Err(e) => Err(PyValueError::new_err(format!("ASN.1 encoding error: {e}"))),
364 }
365 }
366}
367
368#[cfg(test)]
369mod scheduler_ut {
370 use process::simulator::scheduler::Handoff;
371
372 use crate::od::prelude::*;
373
374 use super::Scheduler;
375
376 #[test]
377 fn serde_cadence() {
378 use hifitime::TimeUnits;
379 use serde_yml;
380
381 let cont: Cadence = serde_yml::from_str("!Continuous").unwrap();
382 assert_eq!(cont, Cadence::Continuous);
383
384 let int: Cadence =
385 serde_yml::from_str("!Intermittent {on: 1 h 35 min, off: 15 h 02 min 3 s}").unwrap();
386 assert_eq!(
387 int,
388 Cadence::Intermittent {
389 on: 1.hours() + 35.0.minutes(),
390 off: 15.hours() + 2.minutes() + 3.seconds()
391 }
392 );
393 assert_eq!(
394 format!("{int:?}"),
395 r#"Intermittent { on: "1 h 35 min", off: "15 h 2 min 3 s" }"#
396 );
397
398 let serialized = serde_yml::to_string(&int).unwrap();
399 let deserd: Cadence = serde_yml::from_str(&serialized).unwrap();
400 assert_eq!(deserd, int);
401 }
402
403 #[test]
404 fn api_and_serde_scheduler() {
405 use hifitime::TimeUnits;
406 use serde_yml;
407
408 let scheduler = Scheduler::default();
409 let serialized = serde_yml::to_string(&scheduler).unwrap();
410 assert_eq!(
411 serialized,
412 "handoff: Eager\ncadence: Continuous\nmin_samples: 0\nsample_alignment: null\n"
413 );
414 let deserd: Scheduler = serde_yml::from_str(&serialized).unwrap();
415 assert_eq!(deserd, scheduler);
416
417 let scheduler = Scheduler::builder()
418 .handoff(Handoff::Eager)
419 .cadence(Cadence::Intermittent {
420 on: 0.2.hours(),
421 off: 17.hours() + 5.minutes(),
422 })
423 .build();
424
425 let serialized = serde_yml::to_string(&scheduler).unwrap();
426 assert_eq!(
427 serialized,
428 "handoff: Eager\ncadence: !Intermittent\n 'on': '12 min'\n 'off': '17 h 5 min'\nmin_samples: 10\nsample_alignment: '1 s'\n"
429 );
430 let deserd: Scheduler = serde_yml::from_str(&serialized).unwrap();
431 assert_eq!(deserd, scheduler);
432 }
433
434 #[test]
435 fn defaults() {
436 let sched = Scheduler::default();
437
438 assert_eq!(sched.cadence, Cadence::Continuous);
439
440 assert_eq!(sched.handoff, Handoff::Eager);
441 }
442}