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
/*!
# actorscript
A scripting micro-language for [gmt_dos-actors].
The `actorscript` procedural macro is a [Domain Specific Language] to write [gmt_dos-actors] models.
`actorscript` parses **flows**.
A **flow** consists in a sampling rate followed by a **chain**.
A **chain** is a series of pairs of actor's client and an actor output separated by the token `->`.
As an example:
```
actorscript! {
1: a[A2B] -> b
};
```
is a **flow** connecting the output `A2B` of client `a` to an input of client `b` at the nominal sampling rate.
This example will be expanded by the compiler to
```
let mut a: Actor<_,1,1> = a.into();
let mut b: Actor<_,1,1> = b.into();
a.add_output().build::<A2B>().into_input(&mut b)?;
let model = model!(a,b).name("model").flowchart().check()?;
```
For the code above to compile succesfully, the traits [`Write<A2B>`] and [`Read<A2B>`]
must have been implemented for the clients `a` and `b`, respectively.
The [gmt_dos-actors] model is written in the `ready` state meaning that in order to run the model to completion
the following line of code is needed after `actorscript`
```
model.run().await?;
```
The state the model is written into can be altered with the `state` parameter of the `model` attribute.
Beside `ready`, two other states can be specified:
* `running`
```
actorscript! {
#[model(state = running)]
1: a[A2B] -> b
};
```
will execute the model and waiting for completion of the model is left to the user by calling
```
model.await?;
```
* and `completed`
```
actorscript! {
#[model(state = completed)]
1: a[A2B] -> b
};
```
will execute the model and wait for its completion.
Clients are consumed by their namesake actors and are no longer available after `actorscript`.
If access to a client is still required after `actorscript`, the token `&` can be inserted before the client e.g.
```
actorscript! {
#[model(state = completed)]
1: a[A2B] -> &b
};
```
Here the client `b` is wrapped into an [`Arc`]`<`[`Mutex`]`<_>>` container, cloned and passed to the associated actor.
A reference to client `b` can then be retrieved latter with:
```
let b_ref = b.lock().await.deref();
```
## Model growth
A model grows by expanding **chains** with new links and adding new **flows**.
A **chain** grows by adding new clients and ouputs e.g.
```
actorscript! {
1: a[A2B] -> b[B2C] -> c
};
```
where the output `B2C` is added to `b` and connected to the client `c`.
A new **flow** is added with
```
actorscript! {
1: a[A2B] -> b[B2C] -> c
10: c[C2D] -> d
};
```
Here the new **flow** is downsampled with a sampling rate that is 1/10th of the nominal sampling rate.
Upsampling can be obtained in a similar manner:
```
actorscript! {
1: a[A2B] -> b[B2C] -> c
10: c[C2D] -> d
5: d[D2E] -> e
};
```
In the model above, `C2D` is send to `d` from `c` every 10 samples
and `D2E` is send consecutively twice to `e` from `d` within intervals of 10 samples.
The table below gives the sampling rate for the inputs and outputs of each client:
| | `a` | `b` | `c` | `d` | `e` |
|--------|:---:|:---:|:---:|:---:|:---:|
| inputs | 0 | 1 | 1 | 10 | 5 |
| outputs| 1 | 1 | 10 | 5 | 0 |
## Rate transitions
The former examples illustrates how rate transitions can happen "naturally" between client by
relying on the upsampling and downsampling implementations within the actors.
However this works only if the inputs and/or outputs of a client are only used once per **flow**.
Considering the following example:
```
actorscript! {
1: a[A2B] -> b[B2C] -> d
10: c[C2D] -> b
};
```
The table of inputs and outputs sampling rate is in this case
| | `a` | `b` | `c` | `d` |
|--------|:---:|:---:|:---:|:---:|
| inputs | 0 | 1 | 0 | 1 |
| outputs| 1 | 1 | 10 | 0 |
Here there is a mismatch between the `C2D` output with a 1/10th sampling rate
and `b` inputs that have inherited a sampling rate of 1 from the 1st **flow**.
`actorscript` is capable of detecting such mismatch and it will introduce a rate transition client
between `c` and `b`, effectively rewritting the model as
```
actorscript! {
1: a[A2B] -> b[B2C] -> d
10: c[C2D] -> r
1: r[C2D] -> b
};
```
where `r` is the upsampling rate transiton client [Sampler].
## Feedback loop
An example of a feedback loop is a closed **chain** within a **flow** e.g.:
```
actorscript! {
1: a[A2B] -> b[B2C] -> c[C2B]! -> b
};
```
The flow of data is initiated by the leftmost client (`a`)
and `b` is blocking until it receives `A2B` and `C2B` but `c` cannot send `C2B` until he has received `B2C` from `b`,
so both `b` and `c` are waiting for each other.
To break this kind of stalemate, one can instruct a client to send the data of a given output immediately by appending
the output with the token `!`.
In the above example, `c` is sending `C2B` at the same time than `a` is sending `A2B` hence allowing `b` to proceed.
Another example of a feedback loop across 2 **flows**:
```
actorscript! {
1: a[A2B] -> b[B2C] -> c
10: c[C2D]! -> d[D2B] -> b
};
```
This version would work as well:
```
actorscript! {
1: a[A2B] -> b[B2C] -> c
10: c[C2D] -> d[D2B]! -> b
};
```
## Output data logging
Logging the data of and output is triggered by appending the token `$` after the output like so
```
actorscript! {
1: a[A2B]$ -> b[B2C]$ -> c
10: c[C2D]$ -> d[DD]$
};
```
where `A2B` and `B2C` will be saved into the [parquet] file `data_1.parquet` and
`C2D` and `DD` will be saved into the [parquet] file `data_10.parquet`.
For logging purposes, `actorscript` rewrites the model as
```
actorscript! {
1: a[A2B] -> b[B2C] -> c
10: c[C2D] -> d[DD]
1: a[A2B] -> l1
1: b[B2C] -> l1
10: c[C2D] -> l10
10: d[DD] -> l10
};
```
where `l1` and `l10` and 2 [Arrow] logging clients.
[gmt_dos-actors]: https://docs.rs/gmt_dos-actors
[Domain Specific Language]: https://en.wikipedia.org/wiki/Domain-specific_language
[`Write<A2B>`]: https://docs.rs/gmt_dos-clients/latest/gmt_dos_clients/interface/trait.Write.html
[`Read<A2B>`]: https://docs.rs/gmt_dos-clients/latest/gmt_dos_clients/interface/trait.Read.html
[`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
[`Mutex`]: https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#
[Sampler]: https://docs.rs/gmt_dos-clients/latest/gmt_dos_clients/struct.Sampler.html
[parquet]: https://parquet.apache.org/
[Arrow]: https://docs.rs/gmt_dos-clients_arrow/latest/gmt_dos_clients_arrow/
*/
use TokenStream;
use quote;
use ;
use Model;
pub type Expanded = TokenStream;
/// Source code expansion
pub
/// Faillible source code expansion
pub
/// Script parser
///
/// The script parser holds the code of the actors model