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);
    }
}