Skip to content

Commit c2632d0

Browse files
committed
make lib199 periodic methods run asynchronously
1 parent ec05757 commit c2632d0

7 files changed

Lines changed: 77 additions & 13 deletions

File tree

src/main/java/org/carlmontrobotics/lib199/Lib199Subsystem.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
11
package org.carlmontrobotics.lib199;
22

33
import java.util.ArrayList;
4+
import java.util.concurrent.CopyOnWriteArrayList;
45
import java.util.function.Consumer;
56

7+
import edu.wpi.first.wpilibj.RobotBase;
68
import edu.wpi.first.wpilibj2.command.Subsystem;
79

810
public class Lib199Subsystem implements Subsystem {
911

1012
private static final Lib199Subsystem INSTANCE = new Lib199Subsystem();
1113
private static final ArrayList<Runnable> periodicMethods = new ArrayList<>();
1214
private static final ArrayList<Runnable> periodicSimulationMethods = new ArrayList<>();
15+
private static final CopyOnWriteArrayList<Runnable> asyncPeriodicMethods = new CopyOnWriteArrayList<>();
16+
private static final CopyOnWriteArrayList<Runnable> asyncPeriodicSimulationMethods = new CopyOnWriteArrayList<>();
1317
private static final Consumer<Runnable> RUN_RUNNABLE = Runnable::run;
1418

19+
private static final Thread asyncPeriodicThread;
20+
21+
public static final long asyncSleepTime = 20;
22+
1523
static {
1624
ensureRegistered();
25+
26+
asyncPeriodicThread = new Thread(() -> {
27+
while(true) {
28+
INSTANCE.asyncPeriodic();
29+
try {
30+
Thread.sleep(asyncSleepTime);
31+
} catch(InterruptedException e) {}
32+
}
33+
});
34+
asyncPeriodicThread.setDaemon(true);
35+
asyncPeriodicThread.start();
1736
}
18-
37+
1938
private static boolean registered = false;
2039

2140
private static void ensureRegistered() {
@@ -30,10 +49,27 @@ public static void registerPeriodic(Runnable method) {
3049
periodicMethods.add(method);
3150
}
3251

52+
@Deprecated
53+
/**
54+
* @deprecated Use registerSimulationPeriodic
55+
* @param method
56+
*/
3357
public static void simulationPeriodic(Runnable method) {
58+
registerSimulationPeriodic(method);
59+
}
60+
61+
public static void registerSimulationPeriodic(Runnable method) {
3462
periodicSimulationMethods.add(method);
3563
}
3664

65+
public static void registerAsyncPeriodic(Runnable method) {
66+
asyncPeriodicMethods.add(method);
67+
}
68+
69+
public static void registerAsyncSimulationPeriodic(Runnable method) {
70+
if(RobotBase.isSimulation()) asyncPeriodicSimulationMethods.add(method);
71+
}
72+
3773
@Override
3874
public void periodic() {
3975
periodicMethods.forEach(RUN_RUNNABLE);
@@ -44,6 +80,11 @@ public void simulationPeriodic() {
4480
periodicSimulationMethods.forEach(RUN_RUNNABLE);
4581
}
4682

83+
public void asyncPeriodic() {
84+
asyncPeriodicMethods.forEach(RUN_RUNNABLE);
85+
asyncPeriodicSimulationMethods.forEach(RUN_RUNNABLE);
86+
}
87+
4788
private Lib199Subsystem() {}
4889

4990
}

src/main/java/org/carlmontrobotics/lib199/Mocks.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public final class Mocks {
2424
private static final List<WeakReference<Object>> MOCKS = Collections.synchronizedList(new ArrayList<>());
2525
private static final Predicate<WeakReference<?>> IS_REFERENCE_CLEARED = reference -> reference.get() == null;
2626
private static final Consumer<WeakReference<Object>> CLEAR_INVOCATIONS_ON_REFERENCED_MOCK = reference -> Mockito.clearInvocations(reference.get());
27-
private static final Predicate<WeakReference<Object>> CLEAR_INVOCATIONS_ON_REFERENCED_MOCK_IF_REFERNCE_NOT_CLEARED = reference -> {
27+
private static final Predicate<WeakReference<Object>> CLEAR_INVOCATIONS_ON_REFERENCED_MOCK_IF_REFERENCE_NOT_CLEARED = reference -> {
2828
if(IS_REFERENCE_CLEARED.test(reference)) return true;
2929
CLEAR_INVOCATIONS_ON_REFERENCED_MOCK.accept(reference);
3030
return false;
@@ -37,7 +37,7 @@ public final class Mocks {
3737
// 2) Garbage collected references are removed
3838
// 3) Mock is garbage collected
3939
// 4) Mock invocations are cleared -> throws NullPointerException
40-
Lib199Subsystem.registerPeriodic(() -> MOCKS.removeIf(CLEAR_INVOCATIONS_ON_REFERENCED_MOCK_IF_REFERNCE_NOT_CLEARED));
40+
Lib199Subsystem.registerAsyncPeriodic(() -> MOCKS.removeIf(CLEAR_INVOCATIONS_ON_REFERENCED_MOCK_IF_REFERENCE_NOT_CLEARED));
4141
}
4242

4343
/**

src/main/java/org/carlmontrobotics/lib199/MotorErrors.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public final class MotorErrors {
2020
private static final HashMap<CANSparkMax, Short> stickyFlags = new HashMap<>();
2121

2222
static {
23-
Lib199Subsystem.registerPeriodic(MotorErrors::doReportSparkMaxTemp);
23+
Lib199Subsystem.registerAsyncPeriodic(MotorErrors::doReportSparkMaxTemp);
2424
}
2525

2626
public static void reportError(ErrorCode error) {

src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public MockedCANCoder(CANCoder canCoder) {
2929
position = device.createDouble("count", Direction.kInput, 0);
3030
gearing = device.createDouble("gearing", Direction.kOutput, 1);
3131
sim = canCoder.getSimCollection();
32-
Lib199Subsystem.registerPeriodic(this::update);
32+
Lib199Subsystem.registerAsyncSimulationPeriodic(this::update);
3333
sims.put(port, this);
3434
}
3535

src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkEncoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public MockedSparkEncoder(int id) {
3030
count = device.createDouble("count", Direction.kInput, 0);
3131
gearing = device.createDouble("gearing", Direction.kOutput, 1);
3232
sims.put(id, this);
33-
Lib199Subsystem.registerPeriodic(this);
33+
Lib199Subsystem.registerAsyncPeriodic(this);
3434
}
3535

3636
public double getPosition() {

src/test/java/org/carlmontrobotics/lib199/Lib199SubsystemTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void testPeriodic() {
2525
public void testSimulationPeriodic() {
2626
assumeTrue(RobotBase.isSimulation());
2727
AtomicInteger counter = new AtomicInteger(0);
28-
Lib199Subsystem.registerPeriodic(() -> counter.addAndGet(1));
28+
Lib199Subsystem.registerSimulationPeriodic(() -> counter.addAndGet(1));
2929
assertEquals("Simulation periodic method called before CommandScheduler.run", 0, counter.get());
3030
CommandScheduler.getInstance().run();
3131
assertEquals("Simulation periodic method called more than once or not at all", 1, counter.get());

src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.Assert.assertEquals;
44
import static org.junit.Assert.assertNotEquals;
5+
import static org.junit.Assume.assumeNoException;
56

67
import com.ctre.phoenix.ErrorCode;
78
import com.revrobotics.REVLibError;
@@ -12,7 +13,6 @@
1213
import org.junit.Test;
1314

1415
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
15-
import edu.wpi.first.wpilibj2.command.CommandScheduler;
1616

1717
public class MotorErrorsTest extends ErrStreamTest {
1818

@@ -77,6 +77,16 @@ public int getDeviceId() {
7777

7878
}
7979

80+
private static final Object asyncPeriodicNotifier = new Object();
81+
82+
static {
83+
Lib199Subsystem.registerAsyncPeriodic(() -> {
84+
synchronized(asyncPeriodicNotifier) {
85+
asyncPeriodicNotifier.notifyAll();
86+
}
87+
});
88+
}
89+
8090
@Test
8191
public void testOkErrors() {
8292
errStream.reset();
@@ -165,30 +175,43 @@ private void doTestReportSparkMaxTemp(int id) {
165175
MotorErrors.reportSparkMaxTemp((CANSparkMax)spark, 40);
166176
spark.setSmartCurrentLimit(50);
167177
spark.setTemperature(20);
168-
CommandScheduler.getInstance().run();
178+
runAsyncPeriodic();
169179
String smartDashboardKey = "Port " + id + " Spark Max Temp";
170180
assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01);
171181
assertEquals(50, spark.getSmartCurrentLimit());
172182
spark.setTemperature(20);
173-
CommandScheduler.getInstance().run();
183+
runAsyncPeriodic();
174184
assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01);
175185
assertEquals(50, spark.getSmartCurrentLimit());
176186
assertEquals(0, errStream.size());
177187
spark.setTemperature(40);
178-
CommandScheduler.getInstance().run();
188+
runAsyncPeriodic();
179189
assertEquals(40, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01);
180190
assertEquals(1, spark.getSmartCurrentLimit());
181191
assertNotEquals(0, errStream.size());
182192
errStream.reset();
183193
spark.setTemperature(50);
184-
CommandScheduler.getInstance().run();
194+
runAsyncPeriodic();
185195
assertEquals(50, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01);
186196
assertEquals(1, spark.getSmartCurrentLimit());
187197
spark.setTemperature(20);
188-
CommandScheduler.getInstance().run();
198+
runAsyncPeriodic();
189199
assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01);
190200
assertEquals(1, spark.getSmartCurrentLimit());
191201
assertEquals(0, errStream.size());
192202
}
193203

204+
// Ensures an update to the asynchronous periodic thread is run
205+
private void runAsyncPeriodic() {
206+
try {
207+
synchronized(asyncPeriodicNotifier) {
208+
// Run twice because we don't know in what order we're called, so make sure all periodic methods are run twice
209+
asyncPeriodicNotifier.wait();
210+
asyncPeriodicNotifier.wait();
211+
}
212+
} catch(InterruptedException e) {
213+
assumeNoException(e);
214+
}
215+
}
216+
194217
}

0 commit comments

Comments
 (0)