This provides an API for writing a finite state machine compatible with
the commands v3 framework. Individual states in the state machine are
wrappers around command objects (which may themselves be state
machines). Transitions between states are defined with a staged builder
DSL similar to command builders, and uses `@NoDiscard` to catch
partially configured transitions.
The FSM API is meant to handle highly complex cases that the fluent
command chaining DSL and coroutine-based imperative commands cannot
easily represent; specifically, where a command sequence may want to go
back to an arbitrary previous state or skip forward to an arbitrary
future state.
Here's an example from the design doc for a command that will drive to a
known scoring location, aim at a scoring target, and repeatedly shoot
balls until a storage hopper is empty. It also has conditions to stop
shooting and move back to the scoring location if it's jostled away, and
then automatically resume firing.
```java
public Command autoWithStateMachine() {
// Declare the state machine
StateMachine stateMachine = new StateMachine("Auto With State Machine");
// Define states
State getInPosition = stateMachine.addState(drivetrain.driveToScoringLocation());
State aiming = stateMachine.addState(turret.aimAtGoal());
State scoring = stateMachine.addState(shooter.fireOnce());
State celebrating = stateMachine.addState(leds.celebrate());
// Set the initial state. Neglecting this will cause a runtime exception when the state machine starts.
// Teams using the WPILib compiler plugin will get a compiler error if they do not set this
stateMachine.setInitialState(getInPosition);
// Switch to aiming when we reach the scoring location.
getInPosition.switchTo(aiming).whenComplete();
// Set the swerve wheels in an X shape after reaching the scoring location to resist being pushed away.
getInPosition.onExit(() -> Scheduler.getDefault().fork(drivetrain.setX()));
// Then start scoring once the turret is aimed at the goal.
aiming.switchTo(scoring).when(turret::aimedAtGoal);
// Loop the scoring state as long as the hopper has a ball.
scoring.switchTo(scoring).whenCompleteAnd(() -> hopper.hasBall());
// Automatically interrupt any part of the aiming or scoring sequence if
// the robot is moved away from the scoring location and move back into position.
stateMachine.switchFromAny(aiming, scoring).to(getInPosition).when(atScoringLocation.negate());
// Start celebrating once the final ball has been scored.
scoring.switchTo(celebrating).whenCompleteAnd(() -> !hopper.hasBall());
return stateMachine;
}
```
A compiler check is added to detect object construction that's not
followed by post-construction initializer methods (as defined by the
class by placing `@PostConstructionInitializer` on such methods).
`StateMachine.setInitialState` uses this to detect team code that
creates a state machine but does not set its initial state.
Adds a section on design philosophy so we have something to point to
when people suggest features that aren't compatible with the way WPILib
is designed. Fixes some missed reorg changes (although the native-utils
link intentionally points to main as to be up-to-date in the future) and
generally cleans up any outdated information. Also includes wording
about supporting FTC. Per discussion in Slack, the LabVIEW wording has
been removed, and anything to do with LabVIEW is going to have to be
NI's job. And pursuant to #2757 and #5331, additional (light) developer
documentation has been added to some subprojects, mostly being a quick
summary of the what the project does and what it's for (or not for).
---------
Co-authored-by: sciencewhiz <sciencewhiz@users.noreply.github.com>
Co-authored-by: Joseph Eng <91924258+KangarooKoala@users.noreply.github.com>
This primarily fixes up the bazel publishing to match the gradle
publishing again, as some new libraries were added but not hooked up to
the maven publishing.
During the process, I noticed that the 3rd party libraries (googletest,
catch2, and imgui_suite) were still getting published on the old
`edu.wpi` namespace. I tried to clean up all the other references to
that that I could. Note: opencv and libssh are handled outside
`allwpilib` so they need to be updated separately.
```
> Task :javacPlugin:javadoc
/home/tav/frc/wpilib/allwpilib/javacPlugin/src/main/java/org/wpilib/javacplugin/OpModeAnnotationValidator.java:31: warning: invalid input: '<'
* <li>Name must be <= 32 characters
^
/home/tav/frc/wpilib/allwpilib/javacPlugin/src/main/java/org/wpilib/javacplugin/OpModeAnnotationValidator.java:32: warning: invalid input: '<'
* <li>Group must be <= 12 characters
^
/home/tav/frc/wpilib/allwpilib/javacPlugin/src/main/java/org/wpilib/javacplugin/OpModeAnnotationValidator.java:33: warning: invalid input: '<'
* <li>Description must be <= 64 characters
^
3 warnings
```
These weren't caught by the `docs:generateJavaDocs` task because the
javacPlugin docs aren't included there.
Useful for eg OpModes, where names have a maximum length
Also includes validations for values in opmode annotations like
`@Autonomous(name = "...")`; name, group, and description all have
maximum allowable lengths
Also fixes the google compile-testing library to 0.23.0 (the latest
available at time of writing) instead of a wildcard
Jackson versions were inconsistent across projects; most were on 2.19.2,
but the fields subproject was on 2.15.2. All projects are now on 2.19.2
for consistency
This makes error messages point directly at the variable use, instead of
on the enclosing AST node:
```
error: `outerCoroutine` may not be in scope
outerCoroutine.yield()
^
error: `outerCoroutine` may not be in scope
consume(x, outerCoroutine);
^
```
instead of
```
error: `outerCoroutine` may not be in scope
outerCoroutine.yield()
^
error: `outerCoroutine` may not be in scope
consume(x, outerCoroutine);
^
```
CoroutineYieldInLoopDetector
This checks for while loops where coroutines are in scope but without calling a blocking method on at least one of those coroutines:
```
drivetrain.run(theCoroutine -> {
while (drivetrain.getDistance() < 10) { // ERROR: "Missing call to `theCoroutine.yield()` inside loop"
drivetrain.setSpeed(1);
}
});
```
Note that, because we assume most looping constructs in commands will use while loops, we don't check for-loops, for-each loops, or do-while loops.
This check can be disabled with `@SuppressWarnings("CoroutineYieldInLoop")`
CodeAfterCoroutineParkDetector
Essentially acts like the Java compiler's check for code after a while (true) loop, but for coroutine.park():
```
drivetrain.run(theCoroutine -> {
drivetrain.setSpeed(1.0);
theCoroutine.park();
drivetrain.setSpeed(0.0); // ERROR: "Unreachable statement: `theCoroutine.park()` will never exit"
});
```
This check can be disabled with `@SuppressWarnings("CodeAfterCoroutinePark")`
IncorrectCoroutineUseDetector
Checks for usage of captured (outer) coroutine parameters and assignments to fields.
```
drivetrain.run(outer -> {
outer.await(arm.run(inner -> {
outer.yield(); // ERROR: "Coroutine `outer` may not be in scope. Consider using `inner`"
}))
});
```
This check can be disabled with `@SuppressWarnings("CoroutineMayNotBeInScope")`
```
private Coroutine coroutineField;
drivetrain.run(co -> coroutineField = co); // ERROR: "Captured coroutines may not be stored in fields"
```
This check can be disabled with `@SuppressWarnings("CoroutineCapture")`
Adds a `@NoDiscard` annotation that can be placed on methods to guarantee their return values are used and on types to guarantee that any method returning that type uses the return value.
Methods that call `@NoDiscard`-annotated functions can add a `@SuppressWarnings("NoDiscard")` or `@SuppressWarnings("all")` annotation (or annotation on the class declaring that method) to silence the compiler error warnings.