Skip to content

Commit 2a9f876

Browse files
committed
sim_derive, models: Group generated functions in SerializableModel trait
1 parent b5c166e commit 2a9f876

File tree

13 files changed

+79
-147
lines changed

13 files changed

+79
-147
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ serde_yaml = "0.8"
3535
thiserror = "1.0"
3636
wasm-bindgen = "0.2.63"
3737
web-sys = { version = "0.3", features = [ "console" ] }
38+
sim_derive = { path = "sim_derive" }
3839

3940
# The `console_error_panic_hook` crate provides better debugging of panics by
4041
# logging them with `console.error`. This is great for development, but requires

sim_derive/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ version = "0.7.1"
44

55
[lib]
66
proc-macro = true
7+
8+
[dependencies]
9+
syn = "*"
10+
quote = "*"

sim_derive/src/lib.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
1+
extern crate proc_macro;
2+
extern crate quote;
3+
extern crate syn;
4+
5+
use quote::quote;
6+
use syn::{parse_macro_input, DeriveInput};
17
use proc_macro::TokenStream;
28

3-
#[proc_macro]
4-
pub fn make_answer(_item: TokenStream) -> TokenStream {
5-
"fn answer() -> u32 { 42 }".parse().unwrap()
9+
#[proc_macro_derive(SerializableModel)]
10+
pub fn model(item: TokenStream) -> TokenStream {
11+
let input = parse_macro_input!(item as DeriveInput);
12+
let name = input.ident;
13+
let tokens = quote! {
14+
impl #name {
15+
pub fn from_value(value: serde_yaml::Value) -> Option<Box<dyn AsModel>> {
16+
match serde_yaml::from_value::<Self>(value) {
17+
Ok(model) => Some(Box::new(model)),
18+
Err(_) => None
19+
}
20+
}
21+
}
22+
impl SerializableModel for #name {
23+
fn get_type(&self) -> &'static str {
24+
stringify!(#name)
25+
}
26+
fn serialize(&self) -> serde_yaml::Value {
27+
serde_yaml::to_value(self).unwrap_or(serde_yaml::Value::Null)
28+
}
29+
}
30+
};
31+
tokens.into()
632
}

src/models/exclusive_gateway.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ use std::f64::INFINITY;
22

33
use serde::{Deserialize, Serialize};
44

5-
use super::model_trait::AsModel;
5+
use super::model_trait::{AsModel, SerializableModel};
66
use super::ModelMessage;
77
use crate::input_modeling::random_variable::IndexRandomVariable;
88
use crate::simulator::Services;
99
use crate::utils::error::SimulationError;
1010
use crate::utils::{populate_history_port, populate_snapshot_port};
1111

12+
use sim_derive::SerializableModel;
13+
1214
/// The exclusive gateway splits a process flow into a set of possible paths.
1315
/// The process will only follow one of the possible paths. Path selection is
1416
/// determined by Weighted Index distribution random variates, so this atomic
1517
/// model exhibits stochastic behavior. The exclusive gateway is a BPMN
1618
/// concept.
17-
#[derive(Debug, Clone, Deserialize, Serialize)]
19+
#[derive(Debug, Clone, Serialize, Deserialize, SerializableModel)]
1820
#[serde(rename_all = "camelCase")]
1921
pub struct ExclusiveGateway {
2022
ports_in: PortsIn,
@@ -111,13 +113,6 @@ impl ExclusiveGateway {
111113
}
112114
}
113115

114-
pub fn from_value(value: serde_yaml::Value) -> Option<Box<dyn AsModel>> {
115-
match serde_yaml::from_value::<Self>(value) {
116-
Ok(model) => Some(Box::new(model)),
117-
Err(_) => None
118-
}
119-
}
120-
121116
fn need_snapshot_metrics(&self) -> bool {
122117
self.ports_in.snapshot.is_some() && self.ports_out.snapshot.is_some()
123118
}
@@ -130,14 +125,6 @@ impl ExclusiveGateway {
130125
}
131126

132127
impl AsModel for ExclusiveGateway {
133-
fn get_type(&self) -> &'static str {
134-
"ExclusiveGateway"
135-
}
136-
137-
fn serialize(&self) -> serde_yaml::Value {
138-
serde_yaml::to_value(self).unwrap_or(serde_yaml::Value::Null)
139-
}
140-
141128
fn status(&self) -> String {
142129
String::from("Active")
143130
}

src/models/gate.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ use std::f64::INFINITY;
22

33
use serde::{Deserialize, Serialize};
44

5-
use super::model_trait::AsModel;
5+
use super::model_trait::{AsModel, SerializableModel};
66
use super::ModelMessage;
77
use crate::simulator::Services;
88
use crate::utils::error::SimulationError;
99
use crate::utils::{populate_history_port, populate_snapshot_port};
1010

11+
use sim_derive::SerializableModel;
12+
1113
/// The gate model passes or blocks jobs, when it is in the open or closed
1214
/// state, respectively. The gate can be opened and closed throughout the
1315
/// course of a simulation. This model contains no stochastic behavior - job
1416
/// passing/blocking is based purely on the state of the model at that time
1517
/// in the simulation. A blocked job is a dropped job - it is not stored,
1618
/// queued, or redirected.
17-
#[derive(Debug, Clone, Deserialize, Serialize)]
19+
#[derive(Debug, Clone, Serialize, Deserialize, SerializableModel)]
1820
#[serde(rename_all = "camelCase")]
1921
pub struct Gate {
2022
ports_in: PortsIn,
@@ -136,13 +138,6 @@ impl Gate {
136138
}
137139
}
138140

139-
pub fn from_value(value: serde_yaml::Value) -> Option<Box<dyn AsModel>> {
140-
match serde_yaml::from_value::<Self>(value) {
141-
Ok(model) => Some(Box::new(model)),
142-
Err(_) => None
143-
}
144-
}
145-
146141
fn need_snapshot_metrics(&self) -> bool {
147142
self.ports_in.snapshot.is_some() && self.ports_out.snapshot.is_some()
148143
}
@@ -155,14 +150,6 @@ impl Gate {
155150
}
156151

157152
impl AsModel for Gate {
158-
fn get_type(&self) -> &'static str {
159-
"Gate"
160-
}
161-
162-
fn serialize(&self) -> serde_yaml::Value {
163-
serde_yaml::to_value(self).unwrap_or(serde_yaml::Value::Null)
164-
}
165-
166153
fn status(&self) -> String {
167154
match self.state.phase {
168155
Phase::Open => String::from("Listening"),

src/models/generator.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ use std::f64::INFINITY;
22

33
use serde::{Deserialize, Serialize};
44

5-
use super::model_trait::AsModel;
5+
use super::model_trait::{AsModel, SerializableModel};
66
use super::ModelMessage;
77
use crate::input_modeling::random_variable::ContinuousRandomVariable;
88
use crate::input_modeling::Thinning;
99
use crate::simulator::Services;
1010
use crate::utils::error::SimulationError;
1111
use crate::utils::{populate_history_port, populate_snapshot_port};
1212

13+
use sim_derive::SerializableModel;
14+
1315
/// The generator produces jobs based on a configured interarrival
1416
/// distribution. A normalized thinning function is used to enable
1517
/// non-stationary job generation. For non-stochastic generation of jobs, a
@@ -18,7 +20,7 @@ use crate::utils::{populate_history_port, populate_snapshot_port};
1820
/// produce jobs through perpetuity, and the generator does not receive
1921
/// messages or otherwise change behavior throughout a simulation (except
2022
/// through the thinning function).
21-
#[derive(Debug, Clone, Serialize, Deserialize)]
23+
#[derive(Debug, Clone, Serialize, Deserialize, SerializableModel)]
2224
#[serde(rename_all = "camelCase")]
2325
pub struct Generator {
2426
// Time between job generations
@@ -123,13 +125,6 @@ impl Generator {
123125
history: Default::default(),
124126
}
125127
}
126-
127-
pub fn from_value(value: serde_yaml::Value) -> Option<Box<dyn AsModel>> {
128-
match serde_yaml::from_value::<Self>(value) {
129-
Ok(model) => Some(Box::new(model)),
130-
Err(_) => None
131-
}
132-
}
133128

134129
fn need_snapshot_metrics(&self) -> bool {
135130
self.ports_in.snapshot.is_some() && self.ports_out.snapshot.is_some()
@@ -143,14 +138,6 @@ impl Generator {
143138
}
144139

145140
impl AsModel for Generator {
146-
fn get_type(&self) -> &'static str {
147-
"Generator"
148-
}
149-
150-
fn serialize(&self) -> serde_yaml::Value {
151-
serde_yaml::to_value(self).unwrap_or(serde_yaml::Value::Null)
152-
}
153-
154141
fn status(&self) -> String {
155142
format!["Generating {}s", self.ports_out.job]
156143
}

src/models/load_balancer.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ use std::f64::INFINITY;
22

33
use serde::{Deserialize, Serialize};
44

5-
use super::model_trait::AsModel;
5+
use super::model_trait::{AsModel, SerializableModel};
66
use super::ModelMessage;
77
use crate::simulator::Services;
88
use crate::utils::error::SimulationError;
99
use crate::utils::{populate_history_port, populate_snapshot_port};
1010

11+
use sim_derive::SerializableModel;
12+
1113
/// The load balancer routes jobs to a set of possible process paths, using a
1214
/// round robin strategy. There is no stochastic behavior in this model.
13-
#[derive(Debug, Clone, Deserialize, Serialize)]
15+
#[derive(Debug, Clone, Serialize, Deserialize, SerializableModel)]
1416
#[serde(rename_all = "camelCase")]
1517
pub struct LoadBalancer {
1618
ports_in: PortsIn,
@@ -107,13 +109,6 @@ impl LoadBalancer {
107109
history: Default::default(),
108110
}
109111
}
110-
111-
pub fn from_value(value: serde_yaml::Value) -> Option<Box<dyn AsModel>> {
112-
match serde_yaml::from_value::<Self>(value) {
113-
Ok(model) => Some(Box::new(model)),
114-
Err(_) => None
115-
}
116-
}
117112

118113
fn need_snapshot_metrics(&self) -> bool {
119114
self.ports_in.snapshot.is_some() && self.ports_out.snapshot.is_some()
@@ -127,14 +122,6 @@ impl LoadBalancer {
127122
}
128123

129124
impl AsModel for LoadBalancer {
130-
fn get_type(&self) -> &'static str {
131-
"LoadBalancer"
132-
}
133-
134-
fn serialize(&self) -> serde_yaml::Value {
135-
serde_yaml::to_value(self).unwrap_or(serde_yaml::Value::Null)
136-
}
137-
138125
fn status(&self) -> String {
139126
format!["Listening for {}s", self.ports_in.job]
140127
}

src/models/model.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use serde::{Serialize, Serializer, Deserialize, Deserializer};
22
use serde::ser::SerializeMap;
33

4-
use super::model_trait::AsModel;
4+
use super::model_trait::{AsModel, SerializableModel};
55
use super::ModelMessage;
66
use crate::simulator::Services;
77
use crate::utils::error::SimulationError;
@@ -48,6 +48,8 @@ impl<'de> Deserialize<'de> for Model {
4848
}
4949
}
5050

51+
impl SerializableModel for Model {}
52+
5153
impl AsModel for Model {
5254
fn status(&self) -> String {
5355
self.inner.status()

src/models/model_trait.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@ impl Clone for Box<dyn AsModel> {
2121
}
2222
}
2323

24+
pub trait SerializableModel {
25+
fn get_type(&self) -> &'static str {
26+
"Model"
27+
}
28+
fn serialize(&self) -> serde_yaml::Value {
29+
serde_yaml::Value::Null
30+
}
31+
}
32+
2433
/// The `AsModel` trait defines everything required for a model to operate
2534
/// within the discrete event simulation. The simulator formalism (Discrete
2635
/// Event System Specification) requires `events_ext`, `events_int`,
2736
/// `time_advance`, and `until_next_event`. The additional `status` is for
2837
/// facilitation of simulation reasoning, reporting, and debugging.
2938
// #[enum_dispatch]
30-
pub trait AsModel: ModelClone {
31-
fn get_type(&self) -> &'static str {
32-
""
33-
}
34-
fn serialize(&self) -> serde_yaml::Value {
35-
serde_yaml::Value::Null
36-
}
39+
pub trait AsModel: ModelClone + SerializableModel {
3740
fn status(&self) -> String;
3841
fn events_ext(
3942
&mut self,

src/models/parallel_gateway.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ use std::f64::INFINITY;
33

44
use serde::{Deserialize, Serialize};
55

6-
use super::model_trait::AsModel;
6+
use super::model_trait::{AsModel, SerializableModel};
77
use super::ModelMessage;
88
use crate::simulator::Services;
99
use crate::utils::error::SimulationError;
1010
use crate::utils::{populate_history_port, populate_snapshot_port};
1111

12+
use sim_derive::SerializableModel;
13+
1214
/// The parallel gateway splits a job across multiple processing paths. The
1315
/// job is duplicated across every one of the processing paths. In addition
1416
/// to splitting the process, a second parallel gateway can be used to join
1517
/// the split paths. The parallel gateway is a BPMN concept.
16-
#[derive(Debug, Clone, Serialize, Deserialize)]
18+
#[derive(Debug, Clone, Serialize, Deserialize, SerializableModel)]
1719
#[serde(rename_all = "camelCase")]
1820
pub struct ParallelGateway {
1921
ports_in: PortsIn,
@@ -113,13 +115,6 @@ impl ParallelGateway {
113115
history: Default::default(),
114116
}
115117
}
116-
117-
pub fn from_value(value: serde_yaml::Value) -> Option<Box<dyn AsModel>> {
118-
match serde_yaml::from_value::<Self>(value) {
119-
Ok(model) => Some(Box::new(model)),
120-
Err(_) => None
121-
}
122-
}
123118

124119
fn need_snapshot_metrics(&self) -> bool {
125120
self.ports_in.snapshot.is_some() && self.ports_out.snapshot.is_some()
@@ -133,14 +128,6 @@ impl ParallelGateway {
133128
}
134129

135130
impl AsModel for ParallelGateway {
136-
fn get_type(&self) -> &'static str {
137-
"ParallelGateway"
138-
}
139-
140-
fn serialize(&self) -> serde_yaml::Value {
141-
serde_yaml::to_value(self).unwrap_or(serde_yaml::Value::Null)
142-
}
143-
144131
fn status(&self) -> String {
145132
String::from("Active")
146133
}

0 commit comments

Comments
 (0)