12using fsw::script::Expr;
15using fsw::script::ScriptProgram;
16using fsw::script::Stmt;
68 std::lock_guard<std::mutex>
lk(
mtx_);
69 return std::chrono::duration<double>(Clock::now() -
started_at_).count();
80 std::lock_guard<std::mutex>
lk(
mtx_);
81 ran_for = std::chrono::duration<double>(Clock::now() -
started_at_).count();
83 std::cerr <<
"[ScriptRunner] " << ds.
name <<
": re-entered while still running — "
84 <<
"discarding a run " << std::fixed << std::setprecision(2) << ran_for
85 <<
" s in and restarting from the top" << std::endl;
91 std::lock_guard<std::mutex>
lk(
mtx_);
103 std::lock_guard<std::mutex>
lk(
mtx_);
113 if (
thread_.get_id() == std::this_thread::get_id())
133 std::condition_variable&
cv;
137 std::map<std::string, double>
vars;
172 return std::chrono::duration<double>(ScriptRunner::Clock::now() -
started_at).count();
176 return ScriptRunner::Clock::now() >=
deadline;
184 auto until = ScriptRunner::Clock::now() +
185 std::chrono::microseconds(
static_cast<long long>(seconds * 1e6));
188 std::unique_lock<std::mutex>
lk(
mtx);
208 const Expr& e =
prog.exprs[
static_cast<size_t>(idx)];
209 if (e.
kind != ExprKind::Binary)
214 const std::string saved =
error;
221 std::ostringstream os;
222 os << std::fixed << std::setprecision(1) <<
a <<
' ' << op <<
' ' <<
b;
227 bool eval(int32_t idx,
double& out) {
229 error =
"malformed expression";
232 const Expr& e =
prog.exprs[
static_cast<size_t>(idx)];
234 case ExprKind::Number:
238 case ExprKind::Var: {
239 const std::string& name =
prog.variables[
static_cast<size_t>(e.
var)];
240 auto it =
vars.find(name);
241 if (it ==
vars.end()) {
244 error =
"variable '" + name +
"' was read before it was assigned";
251 case ExprKind::Elapsed:
255 case ExprKind::Pressure: {
256 const std::string& role =
ds.slug_names[
static_cast<size_t>(e.
slug)];
257 if (!
env.read_pressure) {
258 error =
"pressure(" + role +
") is not available in this build";
262 if (!
env.read_pressure(role, psi)) {
266 error =
"no fresh calibrated reading for \"" + role +
"\"";
273 case ExprKind::Unary: {
277 out = (e.
op == Op::Neg) ? -v : (v != 0.0 ? 0.0 : 1.0);
281 case ExprKind::Binary: {
287 if (e.
op == Op::And &&
a == 0.0) {
291 if (e.
op == Op::Or &&
a != 0.0) {
310 error =
"division by zero";
316 out = (
a <
b) ? 1.0 : 0.0;
319 out = (
a <=
b) ? 1.0 : 0.0;
322 out = (
a >
b) ? 1.0 : 0.0;
325 out = (
a >=
b) ? 1.0 : 0.0;
328 out = (
a ==
b) ? 1.0 : 0.0;
331 out = (
a !=
b) ? 1.0 : 0.0;
334 out = (
b != 0.0) ? 1.0 : 0.0;
337 out = (
b != 0.0) ? 1.0 : 0.0;
340 error =
"unsupported operator";
343 if (!std::isfinite(out)) {
346 error =
"arithmetic produced a non-finite value";
352 error =
"unsupported expression";
358 for (int32_t k =
begin; k <
end; k++) {
365 const Flow f =
exec(
prog.block_items[
static_cast<size_t>(k)]);
373 const Stmt& s =
prog.stmts[
static_cast<size_t>(idx)];
375 case StmtKind::Assign: {
379 vars[
prog.variables[
static_cast<size_t>(s.
var)]] = v;
383 case StmtKind::OpenValve:
384 case StmtKind::CloseValve: {
385 const std::string& role =
ds.slug_names[
static_cast<size_t>(s.
slug)];
387 env.set_valve(role, s.
kind == StmtKind::OpenValve ? 1 : 0);
391 case StmtKind::Delay: {
392 double seconds = 0.0;
395 if (!(seconds > 0.0)) {
396 error =
"delay(" + std::to_string(seconds) +
") is not a positive duration";
399 if (!
sleep(seconds)) {
407 case StmtKind::TransitionTo: {
410 error =
"transition target did not resolve";
413 std::cout <<
"[ScriptRunner] " <<
env.state_name <<
": transition_to "
431 case StmtKind::While: {
432 bool announced =
false;
441 error =
"loop iteration budget exhausted";
449 const std::string paren =
458 std::cout <<
"[ScriptRunner] " <<
env.state_name <<
": while "
459 << (c != 0.0 ?
"true" :
"false") <<
" at entry" << paren
460 << (c != 0.0 ?
" — entering loop"
461 :
" — body never runs, 0 iterations")
470 std::cout <<
"[ScriptRunner] " <<
env.state_name <<
": iteration "
479 error =
"unsupported statement";
487 const auto t0 = Clock::now();
492 <<
" ms, " <<
static_cast<int>(kStartLeadInSeconds * 1000) <<
" ms lead-in)"
496 in.sleep(kStartLeadInSeconds);
503 const std::string progress =
504 " after " + std::to_string(in.loop_passes) +
" iteration(s)" +
505 (in.last_condition.empty() ? std::string{} :
", condition " + in.last_condition);
510 std::cout <<
"[ScriptRunner] " << env.
state_name <<
": stopped by transition" << progress
516 if (f == Flow::Terminated && in.error.empty()) {
521 if (!in.error.empty()) {
525 std::cerr <<
"[ScriptRunner] " << env.
state_name <<
": " << in.error << progress
536 std::cout <<
"[ScriptRunner] " << env.
state_name <<
": script complete" << progress
static std::string name(State s)
Human-readable name for a State (e.g.
std::atomic< bool > active_
std::atomic< bool > stop_
void stop()
Cancel and reap.
std::condition_variable cv_
void start(const DynamicState &ds, ScriptEnv env)
Begin running ds.
void run(DynamicState ds, ScriptEnv env)
double elapsedSeconds() const
Seconds since start(), which is what elapsed() reports.
Clock::time_point started_at_
static std::string name(State s)
Human-readable name for a State (e.g.
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
*brief append another JSON pointer at the end of this JSON pointer *sa ptr reference_tokens begin()
*brief create JSON pointer *sa reference_tokens const string_t & a
*brief create JSON pointer *sa reference_tokens end()
Flow
How a statement ended.
@ Terminated
transition_to ran, or something failed.
constexpr double kMinDelaySeconds
constexpr double kStartLeadInSeconds
Shortest delay actually honoured.
constexpr uint64_t kMaxIterations
Total loop iterations across the whole script.
State
System state IDs — must match the TypeScript SystemState enum in web-gui/shared/types....
static bool elapsed(uint32_t now, uint32_t then, uint32_t interval)
static uint32_t until(uint32_t now, uint32_t t)
A parsed script, as a flat arena.
int32_t body_begin
If/While body, and If's else arm: [begin, end) into ScriptProgram::block_items.
int32_t expr
Assign value, Delay seconds, If/While condition.
int32_t slug
OpenValve/CloseValve/TransitionTo target.
int32_t var
Assign: index into ScriptProgram::variables.
One dynamic state, resolved and proven usable.
State return_target
Where the state lands when the script runs off its end.
State timeout_target
Where it lands when timeout_ms expires.
fsw::script::ScriptProgram program
What a running script is allowed to do to the world.
std::function< void(State target)> transition
Leave the state.
std::string state_name
Where the script's log lines are attributed.
Everything one run needs, so the recursive walk can be free functions over one struct.
std::string describeCondition(int32_t idx)
The condition's operands as text, e.g.
std::string last_condition
bool eval(int32_t idx, double &out)
ScriptRunner::Clock::time_point started_at
ScriptRunner::Clock::time_point deadline
static const char * opText(Op op)
Comparison operators, for the condition text in the log.
std::map< std::string, double > vars
bool sleep(double seconds)
Wait seconds, or until stopped / the deadline, whichever comes first.
const ScriptProgram & prog
std::string error
Set when something went wrong; names it for the log.
bool pastDeadline() const
uint64_t loop_passes
Completed passes of the outermost loop, and the last condition rendered — both exist so the end-of-ru...
Flow execBlock(int32_t begin, int32_t end)
std::atomic< bool > & stop
std::condition_variable & cv