Browse Source

Add duration types to Rust action language lib

Joeri Exelmans 4 years ago
parent
commit
e32efc7212
3 changed files with 163 additions and 7 deletions
  1. 2 2
      python/sccd/test/codegen/rust.py
  2. 154 1
      rust/src/action_lang.rs
  3. 7 4
      rust/src/controller.rs

+ 2 - 2
python/sccd/test/codegen/rust.py

@@ -27,8 +27,8 @@ class TestRustGenerator(ClassDiagramRustGenerator):
         self.w.writeln("let mut output = |out: OutEvent| {")
         self.w.writeln("  raised.push(out);")
         self.w.writeln("};")
-        self.w.writeln("let mut controller = controller::Controller::<InEvent>::new();")
-        self.w.writeln("let mut sc: Statechart::<controller::TimerId<InEvent>> = Default::default();")
+        self.w.writeln("let mut controller = controller::Controller::<InEvent>::default();")
+        self.w.writeln("let mut sc = Statechart::<controller::TimerId<InEvent>>::default();")
         self.w.writeln()
         self.w.writeln("// Initialize statechart (execute actions of entering default states)")
         self.w.writeln("sc.init(&mut controller, &mut output);")

+ 154 - 1
rust/src/action_lang.rs

@@ -1,5 +1,158 @@
+
+// Requirements for working with time durations in SCCD:
+//  1) Use of integer types. Floating point types have complex behavior when it comes to precision, mathematical operations introducing roundoff errors that are hard to predict.
+//  2) The type system should prevent mixing up durations' units (e.g. assuming a value in milliseconds is a value in microsends).
+//  3) Under the hood, duration values should not all be converted to the same "base" unit (e.g. microseconds). There is always a tradeoff between the smallest duration expressable vs. the largest duration expressable, and the optimal tradeoff is model-specific.
+//  4) There should be no additional runtime cost when performing arithmetic on durations of the same unit.
+
+use std::marker::PhantomData;
+use std::ops::{Add, Sub};
+
+// 32 bit provides wide enough range for most applications, and has better performance than 64 bit, even on amd64 architectures
+pub type TimeType = i32;
+
+// Just a marker
+pub trait Unit{}
+
+// Our units are also just markers
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Femtos();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Picos();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Nanos();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Micros();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Millis();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Seconds();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Minutes();
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Hours();
+
+impl Unit for Femtos{}
+impl Unit for Picos{}
+impl Unit for Nanos{}
+impl Unit for Micros{}
+impl Unit for Millis{}
+impl Unit for Seconds{}
+impl Unit for Minutes{}
+impl Unit for Hours{}
+
+// Duration type
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
+pub struct Dur<U: Unit> {
+  value: TimeType,
+  unit: PhantomData<U>,
+}
+impl<U: Unit> Dur<U> {
+  fn new(value: TimeType) -> Self {
+    Self{
+      value,
+      unit: Default::default(),
+    }
+  }
+}
+impl<U: Unit> Add for Dur<U> {
+  type Output = Self;
+
+  fn add(self, other: Self) -> Self::Output {
+    Self::new(self.value + other.value)
+  }
+}
+impl<U: Unit> Sub for Dur<U> {
+  type Output = Self;
+
+  fn sub(self, other: Self) -> Self::Output {
+    Self::new(self.value - other.value)
+  }
+}
+
+impl Dur<Femtos> {
+  pub fn to_picos(&self) -> Dur<Picos> {
+    Dur::<Picos>::new(self.value / 1000)
+  }
+}
+impl Dur<Picos> {
+  pub fn to_femtos(&self) -> Dur<Femtos> {
+    Dur::<Femtos>::new(self.value * 1000)
+  }
+  pub fn to_nanos(&self) -> Dur<Nanos> {
+    Dur::<Nanos>::new(self.value / 1000)
+  }
+}
+impl Dur<Nanos> {
+  pub fn to_picos(&self) -> Dur<Picos> {
+    Dur::<Picos>::new(self.value * 1000)
+  }
+  pub fn to_micros(&self) -> Dur<Micros> {
+    Dur::<Micros>::new(self.value / 1000)
+  }
+}
+impl Dur<Micros> {
+  pub fn to_nanos(&self) -> Dur<Nanos> {
+    Dur::<Nanos>::new(self.value * 1000)
+  }
+  pub fn to_millis(&self) -> Dur<Millis> {
+    Dur::<Millis>::new(self.value / 1000)
+  }
+}
+impl Dur<Millis> {
+  pub fn to_micros(&self) -> Dur<Micros> {
+    Dur::<Micros>::new(self.value * 1000)
+  }
+  pub fn to_seconds(&self) -> Dur<Seconds> {
+    Dur::<Seconds>::new(self.value / 1000)
+  }
+}
+impl Dur<Seconds> {
+  pub fn to_millis(&self) -> Dur<Millis> {
+    Dur::<Millis>::new(self.value * 1000)
+  }
+  pub fn to_minutes(&self) -> Dur<Minutes> {
+    Dur::<Minutes>::new(self.value / 60)
+  }
+}
+impl Dur<Minutes> {
+  pub fn to_seconds(&self) -> Dur<Seconds> {
+    Dur::<Seconds>::new(self.value * 60)
+  }
+  pub fn to_hours(&self) -> Dur<Hours> {
+    Dur::<Hours>::new(self.value / 60)
+  }
+}
+impl Dur<Hours> {
+  pub fn to_minutes(&self) -> Dur<Minutes> {
+    Dur::<Minutes>::new(self.value * 60)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+
+  #[test]
+  fn duration_addition() {
+    let result = Dur::<Seconds>::new(42) + Dur::<Seconds>::new(10);
+    assert_eq!(result, Dur::<Seconds>::new(52))
+  }
+  #[test]
+  fn duration_subtraction() {
+    let result = Dur::<Seconds>::new(52) - Dur::<Seconds>::new(10);
+    assert_eq!(result, Dur::<Seconds>::new(42))
+  }
+  #[test]
+  fn duration_conversion() {
+    let result = Dur::<Millis>::new(42000).to_seconds();
+    assert_eq!(result, Dur::<Seconds>::new(42))
+  }
+}
+
+
 // Helpers used by from-action-lang-generated Rust code
- 
+
 #[allow(unused_imports)]
 use std::ops::Deref;
 

+ 7 - 4
rust/src/controller.rs

@@ -83,14 +83,17 @@ pub enum Until {
   Eternity,
 }
 
-impl<InEvent: Copy> Controller<InEvent> {
-  pub fn new() -> Self {
+impl<InEvent> Default for Controller<InEvent> {
+  fn default() -> Self {
     Self {
       simtime: 0,
       queue: BinaryHeap::with_capacity(16),
       idxs: HashMap::<Timestamp, TimerIndex>::with_capacity(16),
     }
   }
+}
+
+impl<InEvent: Copy> Controller<InEvent> {
   pub fn get_simtime(&self) -> Timestamp {
     self.simtime
   }
@@ -100,8 +103,8 @@ impl<InEvent: Copy> Controller<InEvent> {
       Some(Reverse(entry)) => Until::Timestamp(entry.cmp.timestamp),
     }
   }
-  fn cleanup_idx(map: &mut HashMap<Timestamp, TimerIndex>, entry: &QueueEntry<InEvent>) {
-    if let hash_map::Entry::Occupied(o) = map.entry(entry.cmp.timestamp) {
+  fn cleanup_idx(idxs: &mut HashMap<Timestamp, TimerIndex>, entry: &QueueEntry<InEvent>) {
+    if let hash_map::Entry::Occupied(o) = idxs.entry(entry.cmp.timestamp) {
       if *o.get() == entry.cmp.idx+1 {
         o.remove_entry();
       }