20 releases (6 stable)
2.1.2 | Oct 25, 2024 |
---|---|
2.1.1 | Mar 20, 2024 |
2.1.0 | Feb 1, 2024 |
1.0.0 | Jan 15, 2024 |
0.5.1 | Oct 5, 2023 |
#46 in #telescope
206 downloads per month
Used in 15 crates
(via gmt_dos-actors)
68KB
1.5K
SLoC
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 successfully, 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 completed
state meaning that the model is automatically run to completion
The state the model is written into can be altered with the state
parameter of the model
attribute.
Beside completed
, two other states can be specified:
ready
actorscript! {
#[model(state = ready)]
1: a[A2B] -> b
};
will build and check the model but running the model and waiting for completion of the model is left to the user by calling
model.run().await?;
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 down sampled with a sampling rate that is 1/10th of the nominal sampling rate.
Up sampling can be obtained similarly:
actorscript! {
1: a[A2B] -> b[B2C] -> c
10: c[C2D] -> d
5: d[D2E] -> e
};
In the model above, C2D
is sent to d
from c
every 10 samples
and D2E
is sent 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 example illustrates how rate transitions can happen "naturally" between client by relying on the up and down sampling 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 rewriting the model as
actorscript! {
1: a[A2B] -> b[B2C] -> d
10: c[C2D] -> r
1: r[C2D] -> b
};
where r
is the up sampling rate transition 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 as 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
output data are logged into the parquet file data_1.parquet
and
C2D
and DD
output data are logged 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
are two Arrow logging clients.
Dependencies
~0.8–1.3MB
~28K SLoC