Repository navigation
Expand file tree
/
Copy pathticker.cpp
More file actions
102 lines (90 loc) · 3.74 KB
/
Copy pathticker.cpp
File metadata and controls
102 lines (90 loc) · 3.74 KB
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
// examples/ticker.cpp — a complete jaal program.
//
// $ ./ticker
// runs a pretend download on a background stream while ticking, then
// quits. Ctrl+C at any point prints "bye" and quits.
//
// Shows the whole shape of a program: one update per message, a timer, a
// keyed stream (cancelled the moment the program stops subscribing), a
// one-shot background job whose result comes back as a message (perform),
// a signal as a message, an effect of the program's own (`say`), and quit.
#include <jaal/jaal.hpp>
#include <cstdio>
#include <string>
#include <thread>
#include <variant>
using namespace std::chrono_literals;
// update is pure: it never prints. It returns a `say` effect instead, and
// the host prints it. That keeps the program replayable and testable
// (headless records every line it would have printed).
struct Say { std::string text; };
using say = jaal::pure_fx<Say, "say">;
struct Ticker {
struct Model {
int ticks = 0;
int percent = 0;
bool fetching = true;
};
struct Tick {};
struct Progress { int percent; };
struct Done {};
struct Checked { std::string sum; };
struct Interrupted {};
using Msg = std::variant<Tick, Progress, Done, Checked, Interrupted>;
using Cmd = jaal::Cmd<Msg, say>; // core effects + say
using Sub = jaal::Sub<Msg>; // timers and streams
static Cmd update(Model& m, Tick) {
++m.ticks;
return Say{"tick " + std::to_string(m.ticks) + " (" + std::to_string(m.percent) + "%)"};
}
static Cmd update(Model& m, Progress p) {
m.percent = p.percent;
return {};
}
static Cmd update(Model& m, Done) {
m.fetching = false;
// One-shot work: the body returns a value, and it arrives as
// Checked{value}. No Sink, no send. The key means a second Done
// would replace this check rather than run beside it.
return Cmd::batch(
Say{"done after " + std::to_string(m.ticks) + " ticks, checking..."},
Cmd::perform<Checked>("checksum", [](unsigned bytes) {
std::this_thread::sleep_for(50ms); // pretend hashing
return "sha:" + std::to_string(bytes * 2654435761u % 100000);
}, 4096u));
}
static Cmd update(Model&, Checked c) {
return Cmd::batch(Say{"checksum " + c.sum}, Cmd::quit(0));
}
static Cmd update(Model&, Interrupted) { return Cmd::batch(Say{"bye"}, Cmd::quit(0)); }
// Input, including signals, is an on() overload: the event and the
// model as it is right now. Nothing to subscribe, nothing to go stale.
static std::optional<Msg> on(const Model&, jaal::sig s) {
if (s == jaal::sig::interrupt) return Interrupted{};
return std::nullopt;
}
static Sub subscribe(const Model& m) {
auto tick = Sub::every(250ms, Tick{});
if (!m.fetching) return tick;
// A stream lives exactly as long as this subscription does. When
// `fetching` goes false, its stop_token fires and anything it still
// sends is dropped.
return Sub::batch(std::move(tick),
Sub::stream("fetch", [](jaal::Sink<Msg> out, std::stop_token st, int steps) {
for (int i = 1; i <= steps && !st.stop_requested(); ++i) {
std::this_thread::sleep_for(100ms); // pretend network
out.send(Progress{i * 100 / steps});
}
out.send(Done{});
}, 10));
}
};
// The host: runs `say` by printing. That's all a host has to be for a
// program with one effect of its own.
struct console {
void handle(Say s) { std::printf("%s\n", s.text.c_str()); }
};
int main() {
console c;
return jaal::run<Ticker>(c);
}