tutorial/
tutorial.rs

1//! The tutorial example from:
2//! https://docs.rs/stakker/*/stakker/#tutorial-example
3
4use stakker::*;
5use std::time::{Duration, Instant};
6
7// An actor is represented as a struct which holds the actor state
8struct Light {
9    start: Instant,
10    on: bool,
11}
12
13impl Light {
14    // This is a "Prep" method which is used to create a Self value
15    // for the actor.  `cx` is the actor context and gives access to
16    // Stakker `Core`.  (`CX![]` expands to `&mut Cx<'_, Self>`.)
17    //
18    // A "Prep" method doesn't have to return a Self value right away.
19    // For example it might asynchronously attempt a connection to a
20    // remote server first before arranging a call to another "Prep"
21    // function which returns the Self value.  Once a value is
22    // returned, the actor is "Ready" and any queued-up operations on
23    // the actor will be executed.
24    pub fn init(cx: CX![]) -> Option<Self> {
25        // Use cx.now() instead of Instant::now() to allow execution
26        // in virtual time if supported by the environment.
27        let start = cx.now();
28        Some(Self { start, on: false })
29    }
30
31    // Methods that may be called once the actor is "Ready" have a
32    // `&mut self` or `&self` first argument.
33    pub fn set(&mut self, cx: CX![], on: bool) {
34        self.on = on;
35        let time = cx.now() - self.start;
36        println!(
37            "{:04}.{:03} Light on: {}",
38            time.as_secs(),
39            time.subsec_millis(),
40            on
41        );
42    }
43
44    // A `Fwd` or `Ret` allows passing data to arbitrary destinations,
45    // like an async callback.  Here we use it to return a value.
46    pub fn query(&self, _cx: CX![], ret: Ret<bool>) {
47        ret!([ret], self.on);
48    }
49}
50
51// This is another actor that holds a reference to a Light actor.
52struct Flasher {
53    light: Actor<Light>,
54    interval: Duration,
55    count: usize,
56}
57
58impl Flasher {
59    pub fn init(cx: CX![], light: Actor<Light>, interval: Duration, count: usize) -> Option<Self> {
60        // Defer first switch to the queue
61        call!([cx], switch(true));
62        Some(Self {
63            light,
64            interval,
65            count,
66        })
67    }
68
69    pub fn switch(&mut self, cx: CX![], on: bool) {
70        // Change the light state
71        call!([self.light], set(on));
72
73        self.count -= 1;
74        if self.count != 0 {
75            // Call switch again after a delay
76            after!(self.interval, [cx], switch(!on));
77        } else {
78            // Terminate the actor successfully, causing StopCause handler to run
79            cx.stop();
80        }
81
82        // Query the light state, receiving the response in the method
83        // `recv_state`, which has both fixed and forwarded arguments.
84        let ret = ret_some_to!([cx], recv_state(self.count) as (bool));
85        call!([self.light], query(ret));
86    }
87
88    fn recv_state(&self, _: CX![], count: usize, state: bool) {
89        println!("  (at count {} received: {})", count, state);
90    }
91}
92
93fn main() {
94    // Contains all the queues and timers, and controls access to the
95    // state of all the actors.
96    let mut stakker0 = Stakker::new(Instant::now());
97    let stakker = &mut stakker0;
98
99    // Create and initialise the Light and Flasher actors.  The
100    // Flasher actor is given a reference to the Light.  Use a
101    // StopCause handler to shutdown when the Flasher terminates.
102    let light = actor!(stakker, Light::init(), ret_nop!());
103
104    let _flasher = actor!(
105        stakker,
106        Flasher::init(light.clone(), Duration::from_secs(1), 6),
107        ret_shutdown!(stakker)
108    );
109
110    // Since we're not in virtual time, we use `Instant::now()` in
111    // this loop, which is then passed on to all the actors as
112    // `cx.now()`.  (If you want to run time faster or slower you
113    // could use another source of time.)  So all calls in a batch of
114    // processing get the same `cx.now()` value.  Also note that
115    // `Instant::now()` uses a Mutex on some platforms so it saves
116    // cycles to call it less often.
117    stakker.run(Instant::now(), false);
118    while stakker.not_shutdown() {
119        // Wait for next timer to expire.  Here there's no I/O polling
120        // required to wait for external events, so just `sleep`
121        let maxdur = stakker.next_wait_max(Instant::now(), Duration::from_secs(60), false);
122        std::thread::sleep(maxdur);
123
124        // Run queue and timers
125        stakker.run(Instant::now(), false);
126    }
127}