Class ScenarioRunner
DrivetrainSimTest, Batch 2.2's SlideSimTest)
re-typed the same ~20 lines: create the RLOG directory, reset/wire/start PsiKit's Logger, tick a
FakeTimer inside a periodicBeforeUser()/periodicAfterUser() bracket, call
Logger.end(), then re-check by hand that the writer thread didn't overflow or emit nothing. This
class is that boilerplate, written once.
What stays with the caller. This class is season-agnostic core (architecture §3, domain R7): it
knows nothing about a specific robot's motors, plants, or subsystems. Building the fake hardware,
commanding the robot, advancing its plants, and deciding what to log each tick is scenario-specific
behavior the caller supplies as a ScenarioRunner.ScenarioBody. The SimRobotConfig parameter is not read by this
class at all — it is recorded as PsiKit metadata purely so a human opening the RLOG in AdvantageScope (or
a future Phase-3 scorer) can see which robot config a run used, the same way opModeName is.
Trustworthy by construction. run(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody) throws if PsiKit's async writer queue overflowed
(BACKLOG B3 — cycles silently dropped) or if the resulting RLOG is missing or empty (BACKLOG B11) — a
scenario whose log cannot be trusted for replay is a defect the moment it happens, not something every
caller should have to re-check after the fact.
One logged run at a time per JVM. PsiKit's Logger is one static object, so two runs
logging at once would write into each other's RLOG, and a run could return normally with a log that is
mostly the other's. run(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody) therefore takes a JVM-wide lock: a second run started on another thread
waits for the first to finish, and one started from inside a run's own tick fails at once. Runs also share
OpMode.blackboard, which OpModeRunner clears at the start of each run, so tests that run
OpModes must not run in parallel within one JVM; Gradle's separate test JVMs are fine.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceOne scenario's per-tick behavior: command the robot, advance its fake hardware/plants, log fields.static interfaceDecides, before each tick, whether that tick runs.static interfaceRuns after each tick has been logged, and may make the run wait. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final ScenarioRunner.TickPacerNo pacing at all: run as fast as the machine allows. -
Method Summary
Modifier and TypeMethodDescriptionstatic ScenarioRunner.TickGatefixedTickCount(int ticks) A gate that runs exactlyticksticks, which is whatrun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody)uses.static voidrun(String opModeName, SimRobotConfig config, FakeTimer timer, int ticks, double deltaTimeSeconds, Path rlogPath, ScenarioRunner.ScenarioBody body) Runsticksfixed-timestep sim ticks againsttimer, writing everything PsiKit logs torlogPath.static intrunUntil(String opModeName, SimRobotConfig config, FakeTimer timer, double deltaTimeSeconds, Path rlogPath, ScenarioRunner.TickGate gate, ScenarioRunner.TickPacer pacer, List<? extends org.psilynx.psikit.core.LogDataReceiver> extraReceivers, ScenarioRunner.ScenarioBody body) Likerun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody), for a run whose length is not known in advance: a live session that ends when the driver stops, or the replay of one.
-
Field Details
-
NO_PACING
No pacing at all: run as fast as the machine allows. What every headless scenario and replay uses.
-
-
Method Details
-
fixedTickCount
A gate that runs exactlyticksticks, which is whatrun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody)uses.- Parameters:
ticks- how many ticks to run; must not be negative- Returns:
- the gate
-
run
public static void run(String opModeName, SimRobotConfig config, FakeTimer timer, int ticks, double deltaTimeSeconds, Path rlogPath, ScenarioRunner.ScenarioBody body) Runsticksfixed-timestep sim ticks againsttimer, writing everything PsiKit logs torlogPath.- Parameters:
opModeName- recorded as PsiKit metadata ("OpMode"), shown in AdvantageScopeconfig- the robot's sim config for this scenario; recorded as metadata only (see class javadoc) — this class does not read a robot's motors/plants from ittimer- the deterministic clock (domain R5) this scenario runs on; the caller constructs it (and must wire any sim-side infra to it, e.g.HeadlessRobotInitInfra) BEFORE calling this method, since that wiring has to happen before the robot under test is builtticks- number of sim ticks to rundeltaTimeSeconds- fixed per-tick time step (sim-harness §3);timeradvances by this once per tick, beforebody.tick(deltaTimeSeconds)runsrlogPath- caller-chosen output file, e.g.Paths.get("build", "sim", "run1.rlog")body- the scenario's per-tick behavior (seeScenarioRunner.ScenarioBody)- Throws:
IllegalArgumentException- if any argument is unusable, checked before anything else happens:ticksnot positive;deltaTimeSecondsnot a finite number greater than zero;config,timer,rlogPathorbodynull; orrlogPathnot ending in.rlogIllegalStateException- if this is called from inside another run'sbodyon the same thread (a run on another thread waits instead); if an old file atrlogPathcannot be deleted; or if the RLOG this run produced cannot be trusted, for any of four reasons: PsiKit's async writer queue overflowed (BACKLOG B3); the file is missing or empty (BACKLOG B11); it decodes to a different number of frames than ticks were run (BACKLOG B32); or the writer thread stopped making progress while the producer waited for it. All four are the same statement - the log is not a record of the run - and none of them is affected by what the caller's own trajectory checks found
-
runUntil
public static int runUntil(String opModeName, SimRobotConfig config, FakeTimer timer, double deltaTimeSeconds, Path rlogPath, ScenarioRunner.TickGate gate, ScenarioRunner.TickPacer pacer, List<? extends org.psilynx.psikit.core.LogDataReceiver> extraReceivers, ScenarioRunner.ScenarioBody body) Likerun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody), for a run whose length is not known in advance: a live session that ends when the driver stops, or the replay of one. Everythingrun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody)guarantees about the RLOG holds here too, measured against the number of ticks that actually ran.run(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody)itself comes through here, so the argument checks, the one-run-at-a-time guard and the delete of an old RLOG apply to both.Two different "no ticks" refusals. A bad argument is refused up front, before PsiKit or the file system is touched, with
IllegalArgumentException- forrun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody), that includes aticksbelow 1. A gate that says "stop" before tick 0 is only discovered once the run has started, and is refused afterwards withIllegalStateException: the arguments were fine, but the run produced no record of anything.- Parameters:
opModeName- recorded as PsiKit metadata ("OpMode")config- recorded as metadata only, as inrun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody)timer- the deterministic clock, wired to any sim-side infra before this is calleddeltaTimeSeconds- fixed per-tick step;timeradvances by this once per tickrlogPath- where the RLOG goes; must end in.rlog. A file already there is deletedgate- asked before every tick whether it runs; the first "no" ends the runpacer- called after every tick;NO_PACINGfor anything headlessextraReceivers- PsiKit receivers to add beside the RLOG file, for example anRLOGServerso AdvantageScope can watch a live session; empty for a headless run. They see every frame the file does.body- the scenario's per-tick behaviour- Returns:
- how many ticks ran, at least 1
- Throws:
IllegalArgumentException- if any argument is unusable, checked before anything else happens: any of them null;deltaTimeSecondsnot a finite number greater than zero; orrlogPathnot ending in.rlogIllegalStateException- if this is called from inside another run'sbodyon the same thread (a run on another thread waits instead), or is interrupted while it waits; if an old file atrlogPathcannot be deleted; for the same four untrustworthy-RLOG reasons asrun(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody); and when the gate ends the run before its first tick, because a run of nothing is not a record of anything
-