From b566814baea080a3d7013090253bbf25c5648095 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 27 Feb 2026 14:26:27 -0800 Subject: [PATCH] [wpilib] Remove LinearOpMode (#8638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- wpilibc/robotpy_pybind_build_info.bzl | 10 --- .../main/native/cpp/opmode/LinearOpMode.cpp | 30 ------- .../include/wpi/opmode/LinearOpMode.hpp | 68 -------------- .../main/native/include/wpi/opmode/OpMode.hpp | 4 +- wpilibc/src/main/python/pyproject.toml | 1 - .../src/main/python/semiwrap/LinearOpMode.yml | 8 -- wpilibc/src/main/python/wpilib/__init__.py | 2 - .../java/org/wpilib/opmode/LinearOpMode.java | 90 ------------------- .../main/java/org/wpilib/opmode/OpMode.java | 4 +- 9 files changed, 4 insertions(+), 213 deletions(-) delete mode 100644 wpilibc/src/main/native/cpp/opmode/LinearOpMode.cpp delete mode 100644 wpilibc/src/main/native/include/wpi/opmode/LinearOpMode.hpp delete mode 100644 wpilibc/src/main/python/semiwrap/LinearOpMode.yml delete mode 100644 wpilibj/src/main/java/org/wpilib/opmode/LinearOpMode.java diff --git a/wpilibc/robotpy_pybind_build_info.bzl b/wpilibc/robotpy_pybind_build_info.bzl index 82b66a143b..391555d0f6 100644 --- a/wpilibc/robotpy_pybind_build_info.bzl +++ b/wpilibc/robotpy_pybind_build_info.bzl @@ -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", diff --git a/wpilibc/src/main/native/cpp/opmode/LinearOpMode.cpp b/wpilibc/src/main/native/cpp/opmode/LinearOpMode.cpp deleted file mode 100644 index b0a818f6f9..0000000000 --- a/wpilibc/src/main/native/cpp/opmode/LinearOpMode.cpp +++ /dev/null @@ -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; -} diff --git a/wpilibc/src/main/native/include/wpi/opmode/LinearOpMode.hpp b/wpilibc/src/main/native/include/wpi/opmode/LinearOpMode.hpp deleted file mode 100644 index ccd07d38c0..0000000000 --- a/wpilibc/src/main/native/include/wpi/opmode/LinearOpMode.hpp +++ /dev/null @@ -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 - -#include - -#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 diff --git a/wpilibc/src/main/native/include/wpi/opmode/OpMode.hpp b/wpilibc/src/main/native/include/wpi/opmode/OpMode.hpp index 2bac4ff0f8..dec1a8b064 100644 --- a/wpilibc/src/main/native/include/wpi/opmode/OpMode.hpp +++ b/wpilibc/src/main/native/include/wpi/opmode/OpMode.hpp @@ -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: diff --git a/wpilibc/src/main/python/pyproject.toml b/wpilibc/src/main/python/pyproject.toml index 44fdc4435e..bf23e05a1c 100644 --- a/wpilibc/src/main/python/pyproject.toml +++ b/wpilibc/src/main/python/pyproject.toml @@ -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" diff --git a/wpilibc/src/main/python/semiwrap/LinearOpMode.yml b/wpilibc/src/main/python/semiwrap/LinearOpMode.yml deleted file mode 100644 index 657b8caca8..0000000000 --- a/wpilibc/src/main/python/semiwrap/LinearOpMode.yml +++ /dev/null @@ -1,8 +0,0 @@ -classes: - wpi::LinearOpMode: - methods: - DisabledPeriodic: - Run: - IsRunning: - OpModeRun: - OpModeStop: diff --git a/wpilibc/src/main/python/wpilib/__init__.py b/wpilibc/src/main/python/wpilib/__init__.py index 29c86c6e66..5c89e9fb29 100644 --- a/wpilibc/src/main/python/wpilib/__init__.py +++ b/wpilibc/src/main/python/wpilib/__init__.py @@ -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", diff --git a/wpilibj/src/main/java/org/wpilib/opmode/LinearOpMode.java b/wpilibj/src/main/java/org/wpilib/opmode/LinearOpMode.java deleted file mode 100644 index a70cf2e581..0000000000 --- a/wpilibj/src/main/java/org/wpilib/opmode/LinearOpMode.java +++ /dev/null @@ -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. - * - *

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() will be called exactly once - *
  • 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 - *
- * - *

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); -} diff --git a/wpilibj/src/main/java/org/wpilib/opmode/OpMode.java b/wpilibj/src/main/java/org/wpilib/opmode/OpMode.java index 9faed882e2..4c71f0fb15 100644 --- a/wpilibj/src/main/java/org/wpilib/opmode/OpMode.java +++ b/wpilibj/src/main/java/org/wpilib/opmode/OpMode.java @@ -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 { /**