Loopers and Multithreading
The concept of the Looper
comes from 254.
Loopers make it easy to start threads on the robot. They also automatically log and recover from exceptions.
If you were to throw an exception in Robot
, the whole program will crash and the robot code will restart.
Multithreading has a couple of benefits:
- It allows you to decouple subsystems. If there is delay introduced (say if something becomes disconnected) and calls are timing out, having subsystems on different threads lessens the "blast radius".
- It allows you to easily play with update rates (ex: testing 50Hz vs 100Hz on path following).
- It allows you to perform low-priority tasks at a slow rate (say once very 2 seconds) in a low-priority thread.
And some important considerations:
- You could run in to concurrency issues/race conditions.
Additional remark:
I've standardized on using Hz
throughout the robot code.
Setting a looper to run at "20Hz" means that it will call your code 20 times per second.
Use UnitUtil.hzToPeriod()
in order to convert from Hz to a period (dt) that you'd use when creating loopers.
Multithreading and Mutexes
In computer programs, we use Locks in order to ensure only one thread is in a particular region of code. We call this a "critical section".
In your class (as a field variable):
private final ReentrantLock mLock = new ReentrantLock();
Then, you can use the following in your methods:
mLock.lock();
try {
// Your critical section (non-thread-safe code) here
} finally {
mLock.unlock();
}
The try
-finally
block here protects us against deadlocking if we encounter (and don't handle) an exception while in our critical section.
Types
There are two types of Loopers:
InterruptLooper
: Uses the RoboRIOs FPGA in order to trigger each iterationThreadedLooper
: UsesThread.sleep()
(compensating for how long your code took to run)
Creating a Loopable
There are two types of Loopables: Loopable
and LoopableWithStartStop
Loopable:
public class YourLoopable implements Loopable {
@Override
public void loop(double timestamp) {
// Your code
}
}
LoopableWithStartStop (much less frequently used):
public class YourLoopable implements LoopableWithStartStop {
@Override
public void onStart(double timestamp) {
// Will be run once the first time this loopable is run
}
@Override
public void loop(double timestamp) {
// Your code
}
@Override
public void onStop(double timestamp) {
// Will be run once the last time this loopable is run
}
}
Starting Subsystems using Loopers
In Robot.java
, as field variables
InterruptLooper mSwerveDriveLooper = new InterruptLooper(UnitUtil.hzToPeriod(Constants.loopRates.kSwerveDriveHz));
InterruptLooper mVisionObserverLooper = new InterruptLooper(UnitUtil.hzToPeriod(50));
ThreadedLooper mLightsLooper = new ThreadedLooper(UnitUtil.hzToPeriod(20));
In Robot.java
: robotInit()
mSwerveDriveLooper.register(mSwerveDrive);
mVisionObserverLooper.register(mVisionObserver);