diff --git a/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java b/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java
index 01d23c486..885da4c84 100644
--- a/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java
+++ b/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java
@@ -26,8 +26,8 @@ import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
-import org.photonvision.common.util.LoopingRunnable;
import org.photonvision.common.util.Platform;
+import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.common.util.file.JacksonUtils;
public class ScriptManager {
@@ -48,22 +48,17 @@ public class ScriptManager {
events.add(scriptEvent);
}
- new Thread(new ScriptRunner(10L)).start();
+ TimedTaskManager.getInstance().addTask("ScriptRunner", new ScriptRunner(), 10);
+
} else {
System.err.println("Something went wrong initializing scripts! Events will not run.");
}
}
- private static class ScriptRunner extends LoopingRunnable {
-
- ScriptRunner(Long loopTimeMs) {
- super(loopTimeMs);
- }
-
+ private static class ScriptRunner implements Runnable {
@Override
- protected void process() {
+ public void run() {
try {
-
handleEvent(queuedEvents.takeFirst());
} catch (InterruptedException e) {
e.printStackTrace();
diff --git a/photon-server/src/main/java/org/photonvision/common/util/LoopingRunnable.java b/photon-server/src/main/java/org/photonvision/common/util/LoopingRunnable.java
deleted file mode 100644
index 2323e4595..000000000
--- a/photon-server/src/main/java/org/photonvision/common/util/LoopingRunnable.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2020 Photon Vision.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.photonvision.common.util;
-
-/** A thread that tries to run at a specified loop time */
-public abstract class LoopingRunnable implements Runnable {
- protected volatile Long loopTimeMs;
-
- protected abstract void process();
-
- public LoopingRunnable(Long loopTimeMs) {
- this.loopTimeMs = loopTimeMs;
- }
-
- @Override
- public void run() {
- while (!Thread.interrupted()) {
- var now = System.currentTimeMillis();
-
- // Do the thing
- process();
-
- // sleep for the remaining time
- var timeElapsed = System.currentTimeMillis() - now;
- var delta = loopTimeMs - timeElapsed;
- try {
- if (delta > 0.0) {
-
- Thread.sleep(delta, 0);
-
- } else {
- Thread.sleep(1);
- }
- } catch (Exception ignored) {
- }
- }
- }
-}
diff --git a/photon-server/src/main/java/org/photonvision/common/util/TimedTaskManager.java b/photon-server/src/main/java/org/photonvision/common/util/TimedTaskManager.java
new file mode 100644
index 000000000..77acc3b19
--- /dev/null
+++ b/photon-server/src/main/java/org/photonvision/common/util/TimedTaskManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 Photon Vision.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.photonvision.common.util;
+
+import java.util.Arrays;
+import java.util.concurrent.*;
+import org.jetbrains.annotations.NotNull;
+import org.photonvision.common.logging.LogGroup;
+import org.photonvision.common.logging.Logger;
+
+public class TimedTaskManager {
+
+ private static final Logger logger = new Logger(TimedTaskManager.class, LogGroup.General);
+
+ private static class Singleton {
+ public static final TimedTaskManager INSTANCE = new TimedTaskManager();
+ }
+
+ public static TimedTaskManager getInstance() {
+ return Singleton.INSTANCE;
+ }
+
+ private static class TimedTask {
+ final String identifier;
+ final Runnable runnable;
+ final Future future;
+
+ TimedTask(String identifier, Runnable runnable, Future future) {
+ this.identifier = identifier;
+ this.runnable = runnable;
+ this.future = future;
+ }
+ }
+
+ private class CaughtThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(@NotNull Runnable r) {
+ String taskIdentifier = "Unknown";
+ for (TimedTask timedTask : activeTasks.values()) {
+ if (timedTask.runnable == r) {
+ taskIdentifier = timedTask.identifier;
+ break;
+ }
+ }
+
+ var errorString = "Exception running task \"" + taskIdentifier + "\": ";
+ return new Thread(
+ () -> {
+ try {
+ r.run();
+ } catch (Throwable t) {
+ logger.error(errorString);
+ logger.error(Arrays.toString(t.getStackTrace()));
+ }
+ });
+ }
+ }
+
+ private final ScheduledExecutorService taskExecutor =
+ Executors.newScheduledThreadPool(2, new CaughtThreadFactory());
+ private final ConcurrentHashMap activeTasks = new ConcurrentHashMap<>();
+
+ public void addTask(String identifier, Runnable runnable, long millisInterval) {
+ if (!activeTasks.containsKey(identifier)) {
+ var future =
+ taskExecutor.scheduleAtFixedRate(runnable, 0, millisInterval, TimeUnit.MILLISECONDS);
+ activeTasks.put(identifier, new TimedTask(identifier, runnable, future));
+ }
+ }
+
+ public void cancelTask(String identifier) {
+ var task = activeTasks.getOrDefault(identifier, null);
+ if (task != null) {
+ task.future.cancel(true);
+ activeTasks.remove(task.identifier);
+ }
+ }
+}