226 explore using java for autos - #227
Conversation
Replace the Python auto-serialization layer with a Java generator that builds the Autos object graph and serializes it via the robot's own coppercore Gson config, so the JSON round-trips through the robot by construction. - auto_generator_java/: Dsl (builders + sequence/parallel/race + geometry and autopilot helpers), AutosGen (all autos/routines ported from autos.py/routines.py/constants.py), TypeTagger (sets the @jsontype discriminator the polymorph adapter doesn't write), Field (loads the AprilTag layout headlessly, honoring the fieldType deploy constant, and reuses FieldConstants.Tower for climb poses). - generate.sh + dump-classpath.gradle: compile/run via javac+java against the cached classpath, bypassing gradle on the hot path (~1.8s vs gradle's ~4.3s warm floor). - build_autos.py now invokes the Java generator; publish + structural round-trip verification are unchanged. - check_auto_roundtrip.py bootstraps the generator before launching the sim. - Add authoring constructors to Auto, AutoPilotAction, XBasedAutoPilotAction, NetworkConfigurableWait; make FollowPathPlannerPath's RobotConfig lazy so the class loads without NetworkTables. - Remove the Python-serialization layer: frc/robot/util/ts/* (PythonGenerator, Python*/Generated* annotations), the SIM codegen block in JsonConstants, and auto_generator/src/*.py. Output is structurally identical to the previous Autos.json except the two climb-routine poses, which now use the WELDED layout from the deploy constant instead of the andymark layout the Python generator had hardcoded. The committed Autos.json is left unchanged in this baseline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Structurally identical to the previous file except the LeftClimb/RightClimb target poses, which now use the WELDED AprilTag layout (the deploy fieldType constant) instead of the andymark layout the old Python generator hardcoded. The remaining line churn is formatting only: the Java/gson output uses 2-space indentation and always emits doubles (e.g. -90.0, 1.0), whereas the Python output used 4-space indentation and bare int literals. Autos.json is excluded from spotless and the round-trip check is structural, so the format is inconsequential. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add auto_generator_java to the spotless Java target so the generator sources are held to the same googleJavaFormat standard as the robot code, and apply the formatter. Pure formatting; generator output is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the intent comments, TODOs, and commented-out alternatives from the old Python autos.py/routines.py/constants.py into AutosGen (cycle markers, the high-speed trench constraint rationale, the "beeline to trench exit" note, the autopilot-panic waits in the depot routine, the Follower "don't deploy into the trench" note, and the climb lineup notes). No effect on generated output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
coppercore's @JsonType/@JsonSubtype only choose the subclass on deserialize; PolymorphTypeAdapterFactory.write() does not emit the discriminator, so the "type" field must be populated on the object (see ExampleJsonSyncClass, where each subtype passes its name to the base constructor). Previously this was done by a separate generator-side TypeTagger pass. Instead, AutoAction now resolves its own discriminator from the @JsonSubtype table at construction (single source of truth, no per-subclass boilerplate). Gson still overwrites it from JSON on load (Unsafe skips the initializer), so this only takes effect when actions are built directly in Java. TypeTagger is removed; generator output is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generator now runs with the WPILib native libraries on its library path, so auto code has unrestricted access to the rest of the robot code (HAL, Filesystem, NetworkTables) exactly as on the robot — rather than being constrained to a headless subset. This removes the previous workarounds: - Field.java no longer reflects into AprilTagConstants.cachedLayout. It initializes HAL, loads AprilTagConstants for the environment via the normal coppercore path, and uses FieldConstants.Tower directly (no geometry duplication, no reflection). - FollowPathPlannerPath's eager static initializer is restored (the lazy-init workaround was only needed when NetworkTables couldn't load headlessly). generate.sh: bootstrap now also runs extractReleaseNative; the run sets LD_LIBRARY_PATH + -Djava.library.path to build/jni/release. Since initializing HAL prints native chatter to stdout, the generator writes JSON to a file and generate.sh emits that file to stdout, keeping stdout clean JSON. Output is structurally identical; spotlessCheck and the auto round-trip CI pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The agent confirmed that the old and new .json were identical, but testing is needed. As is, this shaves 3,222 of lines of code off in total: The logic of the autos is mostly retained (in all its glory). Some ergonomics is lost (maybe?) because of the lack of a context for sequential lists, etc. In the Java version, all auto objects are explicitly built. That said, if this tests ok, I would prefer using this approach. |
On success the tuning server's POST response is just the full autos JSON echoed back, which is noise on stdout. Drain it silently and only print the status line; failures still surface the response body via the raised PublishError. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generate.sh was bash-only and could not run on native Windows (no bash; ':' classpath separator; ./gradlew; LD_LIBRARY_PATH). Replace it with generate.py, which branches per-OS: - gradlew.bat vs ./gradlew - os.pathsep for the classpath - native loader var: PATH (Windows), DYLD_LIBRARY_PATH (macOS), LD_LIBRARY_PATH (Linux) - java/javac resolved from JAVA_HOME when set (WPILib's JDK), else PATH build_autos.py imports generate.py as a module and calls generate() directly (no second Python process), returning the JSON in-memory; generator progress and HAL native chatter stream to stderr so stdout stays clean. Verified on Linux: bootstrap + hot path produce byte-for-structure-identical output, build_autos.py and the auto round-trip CI pass, hot path ~2s. The Windows code paths are explicit but not executed here; the one unverified assumption is that GradleRIO's extractReleaseNative populates build/jni/release with the Windows DLLs (standard GradleRIO layout). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the varargs seq(...)/parallel(...)/race(...) helpers with fluent
combinators on AutoAction:
action.andThen(b, c) // Sequence of action, b, c
action.inParallelWith(x) // Parallel of action, x
a.andThen(b).andThen(c) // chained, flattened
race.racingWith(x, y) // Race of x, y
andThen/inParallelWith/racingWith live on AutoAction; Sequence/Parallel/Race
override them to flatten and gain static of(...)/of(List) factories. Dsl exposes
empty seq/par/race starters; the combinators return new (flattened) containers,
so the shared starters are never mutated.
AutosGen is rewritten in this style: parallels start from the action
(stowIntake().inParallelWith(followPath(...))), and the conditional builders
(aggressive/conservative/cycleIntake) accumulate with `result = result.andThen(...)`
instead of a mutable List + add(), so there are no more List/a.add() calls. Code
logic is otherwise unchanged.
Generated Autos.json is structurally identical; spotlessCheck and the auto
round-trip CI pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A trailing `.inParallelWith(x)` on a chain read ambiguously — it was unclear
whether x ran alongside the whole preceding sequence or just the last action.
Remove the .inParallelWith / .racingWith decorators and instead make grouping
explicit:
- inParallel(a, b, c) / inRace(a, b, c) factories (Dsl), used inside andThen:
seq.andThen(x, inParallel(a, b), y)
- action.andThenInParallel(a, b) / andThenRacing(a, b) sugar on AutoAction
(= andThen(Parallel.of(...)) / andThen(Race.of(...))).
The empty `par`/`race` starters are removed; `seq` remains for andThen chains.
AutosGen's parallels are now written as inParallel(...) groups. Generated
Autos.json is structurally identical; spotlessCheck and the round-trip CI pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generator Java (AutosGen, Dsl, Field) now lives under src/main/java like the rest of the robot code, so it's compiled by gradle as part of main and covered by spotless automatically (it's inert at robot runtime — never invoked). The driver tooling (generate.py, dump-classpath.gradle) moves next to build_autos.py in auto_generator/, and auto_generator_java/ is removed. generate.py now fast-compiles only the autogen package (src/main/java/frc/robot/ autogen) on the hot path and puts that output first on the run classpath so it takes precedence over the copy gradle compiles into build/classes/java/main. build.gradle's spotless java target drops the now-redundant auto_generator_java entry. Generated Autos.json is structurally identical; spotlessCheck and the round-trip CI pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Class AutosGen renamed to GenerateAutos (file, class, constructor, javadoc, and MAIN_CLASS in generate.py). - The AutoPilotAction builder terminal Dsl.Ap.ap() is renamed to toAutoAction() so it no longer collides in name with the Dsl.ap() entry point. Generated Autos.json is structurally identical; spotlessCheck passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consistent with Ap.toAutoAction(); the XBased autopilot builder terminal now reads clearly. Generated Autos.json is structurally identical; spotlessCheck passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace `ap().pose(x, y, a)` with an `autoPilotTo(x, y, a)` entry point (overloads for (x, y), (x, y, angle), and Pose2d); the target pose is now required at construction (Ap.reference is final). - Rename all builder setters to the with* convention: withVelocity, withEntryAngle, withConstraints, withPidGains, and also withProfile, withRotationRadius, withCanMirror. Reads e.g. autoPilotTo(3.5, 7.55, -90).withVelocity(4.2).withConstraints(...) .toAutoAction(). Generated Autos.json is structurally identical; spotlessCheck passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
After a few iterations and discussions about ergonomics, this version looks fine. Claude checked that the generated Autos.json is structurally identical to what we started with. The ergonomics (with builders, etc.) now resembles that of some of the coppercode code. Some decisions are a bit arbitrary, such as having immutable sequences, but should be fine. Overall, the autos themselves have the same flavor as before, but all the Python language crossing is gone. |
Nothing outside these classes referenced `actions` — construction goes through of(...)/andThen(...) and it's read only by toCommand(). They were public only by historical data-holder convention. Gson (de)serializes private fields reflectively (the JSON key stays "actions"), so this changes nothing on the wire. Verified: generated Autos.json structurally identical; round-trip CI passes (robot deserializes into the private fields); spotlessCheck passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Field.initialize() previously hand-rolled AprilTagConstants loading (deploy path resolution + JSONSync), duplicating JsonConstants. Extract the robot's full constant-loading sequence into JsonConstants.loadConstants() (no tuning server, no RobotContainer) and have both robot startup and the generator call it: - JsonConstants.loadConstants() loads every constant for the config.json-selected environment; loadConstants(RobotContainer) delegates to it then starts the tuning server when enabled (unchanged robot behavior). - Field.initialize() now just does HAL.initialize() + JsonConstants.loadConstants(), so FieldConstants and all other frc.robot.constants are available to autos with zero duplicated loading logic. Because loadConstants() is config.json-driven (like the robot), the generator now selects its environment from config.json; the generator no longer takes an env argument (build_autos still uses env only to choose the output directory). Generated Autos.json is structurally identical; spotlessCheck and the round-trip CI pass. Cost: the hot path grows to ~2.7s since loading all constants also runs Phoenix sim init and the @AfterJsonLoad hooks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First attempt by Claude