State And Config Snapshots¶
TopoExec state is explicit, snapshot-based, and committed on epoch boundaries. It is not a hidden mutable global.
State edges¶
state edges are feedback-safe edge kinds:
- the publisher stages a next snapshot during the current epoch;
- readers continue to observe the currently committed snapshot during that epoch;
- the staged value becomes visible at the next epoch boundary;
- multiple state edges writing the same target endpoint are rejected by validation until an explicit merge policy exists.
This makes slow-to-fast crossings and retained controller/estimator state deterministic: downstream components cannot observe a half-updated state edge in the same transaction that produced it.
RuntimeStateStore¶
topoexec/runtime/state.hpp provides an optional in-process blackboard:
topoexec::RuntimeStateStore store;
store.stage_write("robot", "pose", "estimator.state",
topoexec::make_shared_payload(topoexec::make_text_payload("p1")));
auto before = store.snapshot("robot"); // no pending value yet
store.commit_epoch_boundary();
auto after = store.snapshot("robot"); // pose is now visible
Contract:
- namespaces and keys must be non-empty;
- writers must be explicit strings, conventionally
component.port; - snapshots copy immutable
RuntimePayloadPtrhandles and are not mutated by later commits; - the v1 merge policy is single-writer per namespace/key;
- rejected writes increment state metrics instead of silently overwriting another writer.
RuntimeRunner wires a store into GraphContext::state_store for components that need this lower-level surface.
ConfigSnapshotStore¶
graph.config is parsed into GraphSpec::config and loaded into ConfigSnapshotStore by RuntimeRunner. Each component's YAML config is also copied into that store.
Components may stage component config updates:
topoexec::ConfigView next;
next.values["gain"] = "2";
ctx.config_store->stage_component_config_update("controller", next);
By default, staged config updates form one transaction and apply at the next
epoch boundary. Components that read the config snapshot during the publishing
epoch still see the previously committed values. At the boundary, EventRuntime
validates every pending component config with Component::validate_config(),
applies each one with Component::apply_config(), and only then commits the
store version. If validation fails, or an apply hook fails and rollback is
attempted, the pending transaction is discarded and the previous committed
config remains active.
ConfigSnapshotStore::last_transaction() records the applied transaction id,
version, epoch, timestamp, and component ids. Explicit immediate updates are
still exposed for setup/test code, but runtime hot-reload code should prefer the
epoch-boundary path so component execution never observes a mid-iteration
mutation.
Metrics¶
State/config snapshot work is observable through:
runtime.publication.stateandruntime.publication.state_committedfor state-edge staging/commit;runtime.state.*for blackboard staged/committed/rejected/snapshot-read counts;runtime.config.*for config version, transaction id, staged/committed/rolled-back/rejected/snapshot-read counts.