feat(daemon): GetVariables + read_sync — the standard mutable-read pattern
EquipmentRuntime::read_sync establishes THE pattern for reading mutable engine state from gRPC/binding threads (Phase 0 item 6): post the read onto the io thread (the model's single owner), wait on a future with a deadline, nullopt => UNAVAILABLE at the RPC edge. Always truthful, no cache to invalidate; milliseconds are irrelevant at SECS rates. GetVariables: name resolution against the service snapshot (empty query = all; unknown name => INVALID_ARGUMENT naming the offender), values read via read_sync, converted by the new from_item reverse conversion (single-element numeric arrays => scalars, multi-element => List; Boolean/Binary/text per format; C2-as-integer and U8>2^63 wrap documented as TODOs). Tests run the engine in run_async — the daemon's PRODUCTION threading mode, previously untested — and round-trip through both conversions: SetVariables (declared-format write) then GetVariables (read) over a real in-process channel. Daemon suite 41 -> 61 assertions. daemon_interop.py gains a live GetVariables round-trip check vs the running daemon (verified green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,15 @@
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -72,6 +75,29 @@ class EquipmentRuntime {
|
||||
void set_alarm(uint32_t alid);
|
||||
void clear_alarm(uint32_t alid);
|
||||
|
||||
// ---- reading mutable engine state from outside the io thread -------------
|
||||
// THE standard pattern for every read of mutable state (variable values,
|
||||
// alarm activity, spool depth, ...) from gRPC/binding threads: post the
|
||||
// read onto the io thread — the model's single owner — and wait with a
|
||||
// deadline. Always truthful (no cache invalidation), and the milliseconds
|
||||
// of latency are irrelevant at SECS message rates. Returns nullopt if the
|
||||
// io thread doesn't service the post in time (not running, or stalled).
|
||||
// Requires run()/run_async(); in poll() mode there is nobody to serve the
|
||||
// post, so a same-thread caller should read the model directly instead.
|
||||
template <typename Fn>
|
||||
auto read_sync(Fn&& fn,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds(2000))
|
||||
-> std::optional<std::invoke_result_t<std::decay_t<Fn>&>> {
|
||||
using R = std::invoke_result_t<std::decay_t<Fn>&>;
|
||||
auto prom = std::make_shared<std::promise<R>>();
|
||||
auto fut = prom->get_future();
|
||||
asio::post(io_, [prom, fn = std::forward<Fn>(fn)]() mutable {
|
||||
prom->set_value(fn());
|
||||
});
|
||||
if (fut.wait_for(timeout) != std::future_status::ready) return std::nullopt;
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
// ---- host-command behaviour hook -----------------------------------------
|
||||
void on_command(std::string rcmd, HostCommandRegistry::Handler h) {
|
||||
model_->commands.set_handler(std::move(rcmd), std::move(h));
|
||||
|
||||
Reference in New Issue
Block a user