Add ability to end startCompetition() main loop (#2032)

This is useful for both cleanly exiting from simulation and for unit testing
at a framework level.

This change required removing move constructor/assignment from IterativeRobot.
This commit is contained in:
Peter Johnson
2019-11-05 21:33:09 -08:00
committed by GitHub
parent f5b4be16db
commit 7508aada93
14 changed files with 185 additions and 35 deletions

View File

@@ -473,6 +473,13 @@ double DriverStation::GetBatteryVoltage() const {
return voltage; return voltage;
} }
void DriverStation::WakeupWaitForData() {
std::scoped_lock waitLock(m_waitForDataMutex);
// Nofify all threads
m_waitForDataCounter++;
m_waitForDataCond.notify_all();
}
void DriverStation::GetData() { void DriverStation::GetData() {
{ {
// Compute the pressed and released buttons // Compute the pressed and released buttons
@@ -494,13 +501,7 @@ void DriverStation::GetData() {
} }
} }
{ WakeupWaitForData();
std::scoped_lock waitLock(m_waitForDataMutex);
// Nofify all threads
m_waitForDataCounter++;
m_waitForDataCond.notify_all();
}
SendMatchData(); SendMatchData();
} }

View File

@@ -30,7 +30,13 @@ void IterativeRobot::StartCompetition() {
while (true) { while (true) {
// Wait for driver station data so the loop doesn't hog the CPU // Wait for driver station data so the loop doesn't hog the CPU
DriverStation::GetInstance().WaitForData(); DriverStation::GetInstance().WaitForData();
if (m_exit) break;
LoopFunc(); LoopFunc();
} }
} }
void IterativeRobot::EndCompetition() {
m_exit = true;
DriverStation::GetInstance().WakeupWaitForData();
}

View File

@@ -43,6 +43,11 @@ void TimedRobot::StartCompetition() {
} }
} }
void TimedRobot::EndCompetition() {
int32_t status = 0;
HAL_StopNotifier(m_notifier, &status);
}
units::second_t TimedRobot::GetPeriod() const { units::second_t TimedRobot::GetPeriod() const {
return units::second_t(m_period); return units::second_t(m_period);
} }

View File

@@ -394,6 +394,11 @@ class DriverStation : public ErrorBase {
*/ */
void InTest(bool entering) { m_userInTest = entering; } void InTest(bool entering) { m_userInTest = entering; }
/**
* Forces WaitForData() to return immediately.
*/
void WakeupWaitForData();
protected: protected:
/** /**
* Copy data from the DS task for the user. * Copy data from the DS task for the user.

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/
/* Copyright (c) 2008-2018 FIRST. All Rights Reserved. */ /* Copyright (c) 2008-2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */ /* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */ /* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */ /* the project. */
@@ -7,6 +7,8 @@
#pragma once #pragma once
#include <atomic>
#include "frc/IterativeRobotBase.h" #include "frc/IterativeRobotBase.h"
namespace frc { namespace frc {
@@ -28,9 +30,6 @@ class IterativeRobot : public IterativeRobotBase {
IterativeRobot(); IterativeRobot();
virtual ~IterativeRobot() = default; virtual ~IterativeRobot() = default;
IterativeRobot(IterativeRobot&&) = default;
IterativeRobot& operator=(IterativeRobot&&) = default;
/** /**
* Provide an alternate "main loop" via StartCompetition(). * Provide an alternate "main loop" via StartCompetition().
* *
@@ -38,6 +37,14 @@ class IterativeRobot : public IterativeRobotBase {
* with the DS packets. * with the DS packets.
*/ */
void StartCompetition() override; void StartCompetition() override;
/**
* Ends the main loop in StartCompetition().
*/
void EndCompetition() override;
private:
std::atomic<bool> m_exit{false};
}; };
} // namespace frc } // namespace frc

View File

@@ -7,9 +7,12 @@
#pragma once #pragma once
#include <chrono>
#include <thread> #include <thread>
#include <hal/Main.h> #include <hal/Main.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/raw_ostream.h> #include <wpi/raw_ostream.h>
#include "frc/Base.h" #include "frc/Base.h"
@@ -23,9 +26,13 @@ int RunHALInitialization();
namespace impl { namespace impl {
template <class Robot> template <class Robot>
void RunRobot() { void RunRobot(wpi::mutex& m, Robot** robot) {
static Robot robot; static Robot theRobot;
robot.StartCompetition(); {
std::scoped_lock lock{m};
*robot = &theRobot;
}
theRobot.StartCompetition();
} }
} // namespace impl } // namespace impl
@@ -36,20 +43,50 @@ int StartRobot() {
if (halInit != 0) { if (halInit != 0) {
return halInit; return halInit;
} }
static wpi::mutex m;
static wpi::condition_variable cv;
static Robot* robot = nullptr;
static bool exited = false;
if (HAL_HasMain()) { if (HAL_HasMain()) {
std::thread([] { std::thread thr([] {
try { try {
impl::RunRobot<Robot>(); impl::RunRobot<Robot>(m, &robot);
} catch (...) { } catch (...) {
HAL_ExitMain(); HAL_ExitMain();
{
std::scoped_lock lock{m};
robot = nullptr;
exited = true;
}
cv.notify_all();
throw; throw;
} }
HAL_ExitMain(); HAL_ExitMain();
}) {
.detach(); std::scoped_lock lock{m};
robot = nullptr;
exited = true;
}
cv.notify_all();
});
HAL_RunMain(); HAL_RunMain();
// signal loop to exit
if (robot) robot->EndCompetition();
// prefer to join, but detach to exit if it doesn't exit in a timely manner
using namespace std::chrono_literals;
std::unique_lock lock{m};
if (cv.wait_for(lock, 1s, [] { return exited; }))
thr.join();
else
thr.detach();
} else { } else {
impl::RunRobot<Robot>(); impl::RunRobot<Robot>(m, &robot);
} }
return 0; return 0;
@@ -127,6 +164,8 @@ class RobotBase {
virtual void StartCompetition() = 0; virtual void StartCompetition() = 0;
virtual void EndCompetition() = 0;
static constexpr bool IsReal() { static constexpr bool IsReal() {
#ifdef __FRC_ROBORIO__ #ifdef __FRC_ROBORIO__
return true; return true;

View File

@@ -34,6 +34,11 @@ class TimedRobot : public IterativeRobotBase, public ErrorBase {
*/ */
void StartCompetition() override; void StartCompetition() override;
/**
* Ends the main loop in StartCompetition().
*/
void EndCompetition() override;
/** /**
* Get the time period between calls to Periodic() functions. * Get the time period between calls to Periodic() functions.
*/ */

View File

@@ -32,7 +32,7 @@ void Robot::StartCompetition() {
// Tell the DS that the robot is ready to be enabled // Tell the DS that the robot is ready to be enabled
HAL_ObserveUserProgramStarting(); HAL_ObserveUserProgramStarting();
while (true) { while (!m_exit) {
if (IsDisabled()) { if (IsDisabled()) {
m_ds.InDisabled(true); m_ds.InDisabled(true);
Disabled(); Disabled();
@@ -61,6 +61,8 @@ void Robot::StartCompetition() {
} }
} }
void Robot::EndCompetition() { m_exit = true; }
#ifndef RUNNING_FRC_TESTS #ifndef RUNNING_FRC_TESTS
int main() { return frc::StartRobot<Robot>(); } int main() { return frc::StartRobot<Robot>(); }
#endif #endif

View File

@@ -7,6 +7,8 @@
#pragma once #pragma once
#include <atomic>
#include <frc/RobotBase.h> #include <frc/RobotBase.h>
class Robot : public frc::RobotBase { class Robot : public frc::RobotBase {
@@ -18,4 +20,8 @@ class Robot : public frc::RobotBase {
void Test(); void Test();
void StartCompetition() override; void StartCompetition() override;
void EndCompetition() override;
private:
std::atomic<bool> m_exit{false};
}; };

View File

@@ -1008,6 +1008,16 @@ public class DriverStation {
m_matchDataSender.controlWord.setDouble(HAL.nativeGetControlWord()); m_matchDataSender.controlWord.setDouble(HAL.nativeGetControlWord());
} }
/**
* Forces waitForData() to return immediately.
*/
public void wakeupWaitForData() {
m_waitForDataMutex.lock();
m_waitForDataCount++;
m_waitForDataCond.signalAll();
m_waitForDataMutex.unlock();
}
/** /**
* Copy data from the DS task for the user. If no new data exists, it will just be returned, * Copy data from the DS task for the user. If no new data exists, it will just be returned,
* otherwise the data will be copied from the DS polling loop. * otherwise the data will be copied from the DS polling loop.
@@ -1061,11 +1071,7 @@ public class DriverStation {
m_cacheDataMutex.unlock(); m_cacheDataMutex.unlock();
} }
m_waitForDataMutex.lock(); wakeupWaitForData();
m_waitForDataCount++;
m_waitForDataCond.signalAll();
m_waitForDataMutex.unlock();
sendMatchData(); sendMatchData();
} }

View File

@@ -25,6 +25,7 @@ import edu.wpi.first.hal.HAL;
@Deprecated @Deprecated
public class IterativeRobot extends IterativeRobotBase { public class IterativeRobot extends IterativeRobotBase {
private static final double kPacketPeriod = 0.02; private static final double kPacketPeriod = 0.02;
private volatile boolean m_exit;
/** /**
* Create a new IterativeRobot. * Create a new IterativeRobot.
@@ -49,8 +50,20 @@ public class IterativeRobot extends IterativeRobotBase {
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
// Wait for new data to arrive // Wait for new data to arrive
m_ds.waitForData(); m_ds.waitForData();
if (m_exit) {
break;
}
loopFunc(); loopFunc();
} }
} }
/**
* Ends the main loop in startCompetition().
*/
@Override
public void endCompetition() {
m_exit = true;
m_ds.wakeupWaitForData();
}
} }

View File

@@ -12,6 +12,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier; import java.util.function.Supplier;
import edu.wpi.cscore.CameraServerJNI; import edu.wpi.cscore.CameraServerJNI;
@@ -189,6 +190,11 @@ public abstract class RobotBase implements AutoCloseable {
*/ */
public abstract void startCompetition(); public abstract void startCompetition();
/**
* Ends the main loop in startCompetition().
*/
public abstract void endCompetition();
@SuppressWarnings("JavadocMethod") @SuppressWarnings("JavadocMethod")
public static boolean getBooleanProperty(String name, boolean defaultValue) { public static boolean getBooleanProperty(String name, boolean defaultValue) {
String propVal = System.getProperty(name); String propVal = System.getProperty(name);
@@ -204,6 +210,10 @@ public abstract class RobotBase implements AutoCloseable {
} }
} }
private static final ReentrantLock m_runMutex = new ReentrantLock();
private static RobotBase m_robotCopy;
private static boolean m_suppressExitWarning;
/** /**
* Run the robot main loop. * Run the robot main loop.
*/ */
@@ -232,6 +242,10 @@ public abstract class RobotBase implements AutoCloseable {
return; return;
} }
m_runMutex.lock();
m_robotCopy = robot;
m_runMutex.unlock();
if (isReal()) { if (isReal()) {
try { try {
final File file = new File("/tmp/frc_versions/FRC_Lib_Version.ini"); final File file = new File("/tmp/frc_versions/FRC_Lib_Version.ini");
@@ -265,18 +279,32 @@ public abstract class RobotBase implements AutoCloseable {
throwable.getStackTrace()); throwable.getStackTrace());
errorOnExit = true; errorOnExit = true;
} finally { } finally {
// startCompetition never returns unless exception occurs.... m_runMutex.lock();
DriverStation.reportWarning("Robots should not quit, but yours did!", false); boolean suppressExitWarning = m_suppressExitWarning;
if (errorOnExit) { m_runMutex.unlock();
DriverStation.reportError( if (!suppressExitWarning) {
"The startCompetition() method (or methods called by it) should have " // startCompetition never returns unless exception occurs....
+ "handled the exception above.", false); DriverStation.reportWarning("Robots should not quit, but yours did!", false);
} else { if (errorOnExit) {
DriverStation.reportError("Unexpected return from startCompetition() method.", false); DriverStation.reportError(
"The startCompetition() method (or methods called by it) should have "
+ "handled the exception above.", false);
} else {
DriverStation.reportError("Unexpected return from startCompetition() method.", false);
}
} }
} }
} }
/**
* Suppress the "Robots should not quit" message.
*/
public static void suppressExitWarning(boolean value) {
m_runMutex.lock();
m_suppressExitWarning = value;
m_runMutex.unlock();
}
/** /**
* Starting point for the applications. * Starting point for the applications.
*/ */
@@ -299,6 +327,18 @@ public abstract class RobotBase implements AutoCloseable {
thread.setDaemon(true); thread.setDaemon(true);
thread.start(); thread.start();
HAL.runMain(); HAL.runMain();
suppressExitWarning(true);
m_runMutex.lock();
RobotBase robot = m_robotCopy;
m_runMutex.unlock();
if (robot != null) {
robot.endCompetition();
}
try {
thread.join(1000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
} else { } else {
runRobot(robotSupplier); runRobot(robotSupplier);
} }

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Copyright (c) 2017-2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */ /* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */ /* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */ /* the project. */
@@ -82,6 +82,14 @@ public class TimedRobot extends IterativeRobotBase {
} }
} }
/**
* Ends the main loop in startCompetition().
*/
@Override
public void endCompetition() {
NotifierJNI.stopNotifier(m_notifier);
}
/** /**
* Get time period between calls to Periodic() functions. * Get time period between calls to Periodic() functions.
*/ */

View File

@@ -33,6 +33,8 @@ public class Robot extends RobotBase {
public void test() { public void test() {
} }
private volatile boolean m_exit;
@SuppressWarnings("PMD.CyclomaticComplexity") @SuppressWarnings("PMD.CyclomaticComplexity")
@Override @Override
public void startCompetition() { public void startCompetition() {
@@ -41,7 +43,7 @@ public class Robot extends RobotBase {
// Tell the DS that the robot is ready to be enabled // Tell the DS that the robot is ready to be enabled
HAL.observeUserProgramStarting(); HAL.observeUserProgramStarting();
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted() && !m_exit) {
if (isDisabled()) { if (isDisabled()) {
m_ds.InDisabled(true); m_ds.InDisabled(true);
disabled(); disabled();
@@ -77,4 +79,9 @@ public class Robot extends RobotBase {
} }
} }
} }
@Override
public void endCompetition() {
m_exit = true;
}
} }