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
//! The tutorial example from:
//! https://docs.rs/stakker/*/stakker/#tutorial-example
use stakker::*;
use std::time::{Duration, Instant};
// An actor is represented as a struct which holds the actor state
struct Light {
start: Instant,
on: bool,
}
impl Light {
// This is a "Prep" method which is used to create a Self value
// for the actor. `cx` is the actor context and gives access to
// Stakker `Core`. (`CX![]` expands to `&mut Cx<'_, Self>`.)
//
// A "Prep" method doesn't have to return a Self value right away.
// For example it might asynchronously attempt a connection to a
// remote server first before arranging a call to another "Prep"
// function which returns the Self value. Once a value is
// returned, the actor is "Ready" and any queued-up operations on
// the actor will be executed.
pub fn init(cx: CX![]) -> Option<Self> {
// Use cx.now() instead of Instant::now() to allow execution
// in virtual time if supported by the environment.
let start = cx.now();
Some(Self { start, on: false })
}
// Methods that may be called once the actor is "Ready" have a
// `&mut self` or `&self` first argument.
pub fn set(&mut self, cx: CX![], on: bool) {
self.on = on;
let time = cx.now() - self.start;
println!(
"{:04}.{:03} Light on: {}",
time.as_secs(),
time.subsec_millis(),
on
);
}
// A `Fwd` or `Ret` allows passing data to arbitrary destinations,
// like an async callback. Here we use it to return a value.
pub fn query(&self, _cx: CX![], ret: Ret<bool>) {
ret!([ret], self.on);
}
}
// This is another actor that holds a reference to a Light actor.
struct Flasher {
light: Actor<Light>,
interval: Duration,
count: usize,
}
impl Flasher {
pub fn init(cx: CX![], light: Actor<Light>, interval: Duration, count: usize) -> Option<Self> {
// Defer first switch to the queue
call!([cx], switch(true));
Some(Self {
light,
interval,
count,
})
}
pub fn switch(&mut self, cx: CX![], on: bool) {
// Change the light state
call!([self.light], set(on));
self.count -= 1;
if self.count != 0 {
// Call switch again after a delay
after!(self.interval, [cx], switch(!on));
} else {
// Terminate the actor successfully, causing StopCause handler to run
cx.stop();
}
// Query the light state, receiving the response in the method
// `recv_state`, which has both fixed and forwarded arguments.
let ret = ret_some_to!([cx], recv_state(self.count) as (bool));
call!([self.light], query(ret));
}
fn recv_state(&self, _: CX![], count: usize, state: bool) {
println!(" (at count {} received: {})", count, state);
}
}
fn main() {
// Contains all the queues and timers, and controls access to the
// state of all the actors.
let mut stakker0 = Stakker::new(Instant::now());
let stakker = &mut stakker0;
// Create and initialise the Light and Flasher actors. The
// Flasher actor is given a reference to the Light. Use a
// StopCause handler to shutdown when the Flasher terminates.
let light = actor!(stakker, Light::init(), ret_nop!());
let _flasher = actor!(
stakker,
Flasher::init(light.clone(), Duration::from_secs(1), 6),
ret_shutdown!(stakker)
);
// Since we're not in virtual time, we use `Instant::now()` in
// this loop, which is then passed on to all the actors as
// `cx.now()`. (If you want to run time faster or slower you
// could use another source of time.) So all calls in a batch of
// processing get the same `cx.now()` value. Also note that
// `Instant::now()` uses a Mutex on some platforms so it saves
// cycles to call it less often.
stakker.run(Instant::now(), false);
while stakker.not_shutdown() {
// Wait for next timer to expire. Here there's no I/O polling
// required to wait for external events, so just `sleep`
let maxdur = stakker.next_wait_max(Instant::now(), Duration::from_secs(60), false);
std::thread::sleep(maxdur);
// Run queue and timers
stakker.run(Instant::now(), false);
}
}