[wpilib] Remove LinearOpMode (#8638)

Linear OpModes have several major downsides with no obvious solutions:

- Some things stop working automatically--e.g. in a linear opmode,
simulation will not work out-of-the-box; the user must explicitly call
sim themselves.  there's a few other things we do periodically, but this
is the big one (it also forces some decisions on other parts of the
library—eg if we want Tunable to work in linear without the user
manually calling refresh, we have to run it on a background thread,
which means it must be thread safe throughout).  We can help in some
areas (e.g. have sleep functions call background things), but if the
user is writing a loop that waits to drive a certain distance with no
sleep, it's an easy footgun
- Writing code with no sleeps is easy to do, and can hog an entire
processing core easily--yes, there's more than one core, but it could
still easily impact e.g. vision processing
- Many people I've talked to want robot-level periodic and periodic sim
functions.  Given linear opmodes, we have two options, neither of which
is great: (1) don't provide robot-level periodic functions, and the
users who want those must set those up themselves and remember to call
them explicitly from every periodic opmode, or (2) provide them, but
only call them automatically from periodic opmodes, which could be
confusing for linear opmode users (they'd have to call them manually if
they wanted them).  Currently we do (1) but someone in the community
already opened a change to do (2).
- Restarting the robot program fixes the "stuck in auto for the rest of
the match" problem but still feels like an ugly hack because the startup
time is not unlikely to make the robot not immediately ready for teleop

Removing LinearOpMode resolves these issues by moving to a periodic-only
structure. We can address the few notable use cases of LinearOpMode
(e.g. very basic autonomous sequences) in other ways such as Blocks
generated code, better state machine tutorials/documentation, etc.
This commit is contained in:
Peter Johnson
2026-02-27 14:26:27 -08:00
committed by GitHub
parent 7513846c72
commit b566814bae
9 changed files with 4 additions and 213 deletions

View File

@@ -742,16 +742,6 @@ def wpilib_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includ
("wpi::internal::DriverStationModeThread", "wpi__internal__DriverStationModeThread.hpp"),
],
),
struct(
class_name = "LinearOpMode",
yml_file = "semiwrap/LinearOpMode.yml",
header_root = "$(execpath :robotpy-native-wpilib.copy_headers)",
header_file = "$(execpath :robotpy-native-wpilib.copy_headers)/wpi/opmode/LinearOpMode.hpp",
tmpl_class_names = [],
trampolines = [
("wpi::LinearOpMode", "wpi__LinearOpMode.hpp"),
],
),
struct(
class_name = "OpMode",
yml_file = "semiwrap/OpMode.yml",

View File

@@ -1,30 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/opmode/LinearOpMode.hpp"
#include "wpi/driverstation/DriverStation.hpp"
#include "wpi/hal/DriverStation.h"
#include "wpi/internal/DriverStationModeThread.hpp"
using namespace wpi;
void LinearOpMode::OpModeRun(int64_t opModeId) {
auto word = wpi::hal::GetControlWord();
word.SetOpModeId(opModeId);
internal::DriverStationModeThread bgThread{word};
Run();
// Wait for opmode to be stopped or disabled, otherwise OpModeRobot will
// recreate and re-run the opmode immediately.
while (IsRunning() && DriverStation::IsEnabled() &&
DriverStation::GetOpModeId() == opModeId) {
using namespace std::chrono_literals;
std::this_thread::sleep_for(20ms);
}
}
void LinearOpMode::OpModeStop() {
m_running = false;
}

View File

@@ -1,68 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <atomic>
#include "wpi/opmode/OpMode.hpp"
namespace wpi {
/**
* An opmode structure for "linear" operation. The user is responsible for
* implementing any looping behavior; after Run() returns it will not be called
* again on the same object.
*
* Lifecycle:
*
* - Constructed when opmode selected on driver station
*
* - DisabledPeriodic() called periodically as long as DS is disabled
*
* - When DS transitions from disabled to enabled, Run() is called exactly once
*
* - When DS transitions from enabled to disabled, or a different opmode is
* selected on the driver station, object is destroyed and not reused
*
* The user is responsible for exiting Run() when the opmode is directed to stop
* executing. This is indicated by IsRunning() returning false. All other
* methods should be written to return as quickly as possible when IsRunning()
* returns false.
*/
class LinearOpMode : public OpMode {
public:
/**
* Called periodically while the opmode is selected on the DS and the robot is
* disabled.
*/
void DisabledPeriodic() override {}
/**
* Called once when the robot is enabled. When it returns, it will not be
* called again on the same object.
*/
virtual void Run() = 0;
/**
* Returns true while this opmode is selected (regardless of enable state).
* All other functions should be written to return as quickly as possible when
* this returns false.
*
* @return True if opmode selected, false otherwise
*/
bool IsRunning() const { return m_running; }
// implements OpMode interface
void OpModeRun(int64_t opModeId) final;
void OpModeStop() final;
private:
std::atomic_bool m_running{true};
};
} // namespace wpi

View File

@@ -10,8 +10,8 @@ namespace wpi {
/**
* Top-level interface for opmode classes. Users should generally extend one of
* the abstract implementations of this interface (e.g. PeriodicOpMode or
* LinearOpMode) rather than directly implementing this interface.
* the abstract implementations of this interface (e.g. PeriodicOpMode) rather
* than directly implementing this interface.
*/
class OpMode {
public:

View File

@@ -198,7 +198,6 @@ Encoder = "wpi/hardware/rotation/Encoder.hpp"
DriverStationModeThread = "wpi/internal/DriverStationModeThread.hpp"
# wpi/opmode
LinearOpMode = "wpi/opmode/LinearOpMode.hpp"
OpMode = "wpi/opmode/OpMode.hpp"
PeriodicOpMode = "wpi/opmode/PeriodicOpMode.hpp"

View File

@@ -1,8 +0,0 @@
classes:
wpi::LinearOpMode:
methods:
DisabledPeriodic:
Run:
IsRunning:
OpModeRun:
OpModeStop:

View File

@@ -39,7 +39,6 @@ from ._wpilib import (
Joystick,
Koors40,
LEDPattern,
LinearOpMode,
MecanumDrive,
Mechanism2d,
MechanismLigament2d,
@@ -143,7 +142,6 @@ __all__ = [
"Joystick",
"Koors40",
"LEDPattern",
"LinearOpMode",
"MecanumDrive",
"Mechanism2d",
"MechanismLigament2d",

View File

@@ -1,90 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.opmode;
import java.util.concurrent.atomic.AtomicBoolean;
import org.wpilib.driverstation.DriverStation;
import org.wpilib.hardware.hal.ControlWord;
import org.wpilib.internal.DriverStationModeThread;
/**
* An opmode structure for "linear" operation. The user is responsible for implementing any looping
* behavior; after run() returns it will not be called again on the same object.
*
* <p>Lifecycle:
*
* <ul>
* <li>constructed when opmode selected on driver station
* <li>disabledPeriodic() called periodically as long as DS is disabled
* <li>when DS transitions from disabled to enabled, run() will be called exactly once
* <li>when DS transitions from enabled to disabled, or a different opmode is selected on the
* driver station, close() is called; the object is not reused
* </ul>
*
* <p>The user is responsible for exiting run() when the opmode is directed to stop executing. This
* is indicated by isRunning() returning false. All other methods should be written to return as
* quickly as possible when isRunning() returns false.
*/
public abstract class LinearOpMode implements OpMode {
/** Constructor. */
public LinearOpMode() {}
/** Called periodically while the opmode is selected on the DS and the robot is disabled. */
@Override
public void disabledPeriodic() {}
/**
* Called when the opmode is de-selected on the DS. The object is not reused even if the same
* opmode is selected again (a new object will be created).
*/
public void close() {}
/**
* Called once when the robot is enabled. It will not be called a second time on the same object.
*
* @throws InterruptedException when interrupted
*/
public abstract void run() throws InterruptedException;
/**
* Returns true while this opmode is selected (regardless of enable state). All other functions
* should be written to return as quickly as possible when this returns false.
*
* @return True if opmode selected, false otherwise
*/
public boolean isRunning() {
return m_running.get();
}
// implements OpMode interface
@Override
public final void opModeRun(long opModeId) throws InterruptedException {
ControlWord word = new ControlWord();
DriverStation.refreshControlWordFromCache(word);
word.setOpModeId(opModeId);
try (DriverStationModeThread bgThread = new DriverStationModeThread(word)) {
run();
// Wait for opmode to be stopped or disabled, otherwise OpModeRobot will recreate and re-run
// the opmode immediately.
while (isRunning() && DriverStation.isEnabled() && DriverStation.getOpModeId() == opModeId) {
Thread.sleep(20);
}
}
}
@Override
public final void opModeStop() {
m_running.set(false);
}
@Override
public final void opModeClose() {
close();
}
private final AtomicBoolean m_running = new AtomicBoolean(true);
}

View File

@@ -6,8 +6,8 @@ package org.wpilib.opmode;
/**
* Top-level interface for opmode classes. Users should generally extend one of the abstract
* implementations of this interface (e.g. {@link PeriodicOpMode} or {@link LinearOpMode}) rather
* than directly implementing this interface.
* implementations of this interface (e.g. {@link PeriodicOpMode}) rather than directly implementing
* this interface.
*/
public interface OpMode {
/**