STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
ScriptRunner.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <condition_variable>
5#include <cstdint>
6#include <functional>
7#include <mutex>
8#include <string>
9#include <thread>
10
12
13namespace sequencer {
14
15/**
16 * What a running script is allowed to do to the world.
17 *
18 * Everything the interpreter can reach is a callback supplied by its owner, so the language has no
19 * way to touch anything that was not handed to it — and so the whole runner is testable against
20 * recording stubs, with no sockets and no boards.
21 */
22struct ScriptEnv {
23 /** Command one valve by canonical config name. Expected to set the script position AND send
24 * immediately, exactly as debug manual control does. */
25 std::function<void(const std::string& role, int pos)> set_valve;
26
27 /**
28 * Leave the state. TERMINAL: the runner unwinds its whole stack after calling this and the
29 * thread exits.
30 *
31 * The owner must post this rather than perform it inline — the worker that handles the
32 * transition joins this thread, so doing the transition synchronously from here is a
33 * guaranteed deadlock (the same one documented for the fire timer's expiry callback).
34 */
35 std::function<void(State target)> transition;
36
37 /**
38 * Read a calibrated pressure by canonical role name.
39 *
40 * Returns false when the value must not be acted on — no reading yet, too old, or flagged
41 * uncalibrated. The script then FAILS rather than proceeding on a stand-in number: an
42 * uncalibrated PT publishes a smooth, plausible 0.0 PSI, and a "cycle until 90% of COPV" loop
43 * reading zero computes a target of zero and does nothing at all, successfully.
44 */
45 std::function<bool(const std::string& role, double& psi)> read_pressure;
46
47 /** Where the script's log lines are attributed. */
48 std::string state_name;
49};
50
51/**
52 * Runs one dynamic state's script on its own thread.
53 *
54 * Modelled on HoldTimer, and for its reasons rather than by analogy:
55 *
56 * - **Its own thread.** It must not run on the command worker. That worker serialises every
57 * command through one queue, so a 30 s script executing there would block every TRANSITION,
58 * ACTUATOR and abort for its whole runtime, and the command timeout would start firing on
59 * ordinary operator input.
60 *
61 * - **wait_until on a steady_clock deadline**, never sleep_for. HoldTimer records why: its
62 * predecessor counted sleep *calls* and overshot in one direction every iteration. A loop body
63 * with a delay(0.2) compounds exactly that error, a hundred times over.
64 *
65 * - **The program is passed by value into start()**, never stored on the runner. A duration that
66 * lives behind a setter is one a reload can forget to refresh, which is how a burn once ran the
67 * previous config's window.
68 *
69 * - **stop() is idempotent, reaps the thread, and fires no completion callback.** Cancellation is
70 * not an ending the script gets to react to.
71 *
72 * The stop flag is checked at every statement boundary, inside every wait, and at the top of every
73 * loop iteration, so a transition or an abort preempts a running script in well under a
74 * millisecond rather than at the next statement.
75 *
76 * Because it owns a thread, the interpreter is a plain recursive tree walk — the thread IS the
77 * continuation. No resumable VM, no stepped scheduler.
78 */
80public:
81 using Clock = std::chrono::steady_clock;
82
83 ScriptRunner() = default;
85
86 ScriptRunner(const ScriptRunner&) = delete;
88
89 /** Begin running `ds`. Supersedes anything already running (which is stopped first). */
90 void start(const DynamicState& ds, ScriptEnv env);
91
92 /**
93 * Cancel and reap. Safe when nothing is running, and safe to call from the transition the
94 * script itself requested — that path detaches rather than self-joining, which would be a
95 * std::terminate.
96 */
97 void stop();
98
99 bool isActive() const {
100 return active_.load();
101 }
102
103 /** Seconds since start(), which is what elapsed() reports. 0 when not running. */
104 double elapsedSeconds() const;
105
106private:
107 void run(DynamicState ds, ScriptEnv env);
108
109 mutable std::mutex mtx_;
110 std::condition_variable cv_;
111 std::thread thread_;
112 std::atomic<bool> active_{false};
113 std::atomic<bool> stop_{false};
114 Clock::time_point started_at_{};
115};
116
117} // namespace sequencer
std::atomic< bool > active_
std::atomic< bool > stop_
ScriptRunner(const ScriptRunner &)=delete
std::chrono::steady_clock Clock
ScriptRunner & operator=(const ScriptRunner &)=delete
void stop()
Cancel and reap.
std::condition_variable cv_
void start(const DynamicState &ds, ScriptEnv env)
Begin running ds.
double elapsedSeconds() const
Seconds since start(), which is what elapsed() reports.
Clock::time_point started_at_
long target
Definition main.cpp:38
Definition run.py:1
State
System state IDs — must match the TypeScript SystemState enum in web-gui/shared/types....
One dynamic state, resolved and proven usable.
What a running script is allowed to do to the world.
std::function< void(const std::string &role, int pos)> set_valve
Command one valve by canonical config name.
std::function< bool(const std::string &role, double &psi)> read_pressure
Read a calibrated pressure by canonical role name.
std::function< void(State target)> transition
Leave the state.
std::string state_name
Where the script's log lines are attributed.