diff --git a/src/main/java/com/team766/framework/AutonomousMode.java b/src/main/java/com/team766/framework/AutonomousMode.java deleted file mode 100644 index c47eb661..00000000 --- a/src/main/java/com/team766/framework/AutonomousMode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.team766.framework; - -import com.team766.web.AutonomousSelector; -import java.util.function.Supplier; - -public class AutonomousMode implements AutonomousSelector.Selectable { - private final Supplier m_constructor; - private final String m_name; - - public AutonomousMode(final String name, final Supplier constructor) { - m_constructor = constructor; - m_name = name; - } - - public Procedure instantiate() { - return m_constructor.get(); - } - - public String name() { - return m_name; - } - - @Override - public String toString() { - return name(); - } - - public AutonomousMode clone() { - return new AutonomousMode(m_name, m_constructor); - } -} diff --git a/src/main/java/com/team766/framework/Context.java b/src/main/java/com/team766/framework/Context.java deleted file mode 100644 index e5fdf843..00000000 --- a/src/main/java/com/team766/framework/Context.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.team766.framework; - -import java.util.function.BooleanSupplier; - -/** - * Context is the framework's representation of a single thread of execution. - * - * We may want to have multiple procedures running at the same time on a robot. - * For example, the robot could be raising an arm mechanism while also driving. - * Each of those procedures would have a separate Context. Each of those - * procedures may call other procedures directly; those procedures would share - * the same Context. Each Context can only be running a single procedure at a - * time. If a procedure wants to call multiple other procedures at the same - * time, it has to create new Contexts for them (using the {@link #startAsync} - * method). - * - * Use the Context instance passed to your procedure whenever you want your - * procedure to wait for something. For example, to have your procedure pause - * for a certain amount of time, call context.waitForSeconds. Multiple Contexts - * run at the same time using cooperative multitasking, which means procedures - * have to explicitly indicate when another Context should be allowed to run. - * Using Context's wait* methods will allow other Contexts to run while this one - * is waiting. If your procedure will run for a while without needing to wait - * (this often happens if your procedure has a while loop), then it should - * periodically call context.yield() (for example, at the start of each - * iteration of the while loop) to still allow other Contexts to run. - * - * This cooperative multitasking paradigm is used by the framework to ensure - * that only one Context is actually running at a time, which allows us to avoid - * needing to deal with concurrency issues like data race conditions. Even - * though only one Context is running at once, it's still incredibly helpful to - * express the code using this separate-threads-of-execution paradigm, as it - * allows each procedure to be written in procedural style - * (https://en.wikipedia.org/wiki/Procedural_programming "procedural languages - * model execution of the program as a sequence of imperative commands"), rather - * than as state machines or in continuation-passing style, which can be much - * more complicated to reason about, especially for new programmers. - */ -public interface Context { - /** - * Pauses the execution of this Context until the given predicate returns true or until - * the timeout has elapsed. Yields to other Contexts in the meantime. - * - * Note that the predicate will be evaluated repeatedly (possibly on a different thread) while - * the Context is paused to determine whether it should continue waiting. - * - * @return True if the predicate succeeded, false if the wait timed out. - */ - boolean waitForConditionOrTimeout(final BooleanSupplier predicate, double timeoutSeconds); - - /** - * Pauses the execution of this Context until the given predicate returns true. Yields to other - * Contexts in the meantime. - * - * Note that the predicate will be evaluated repeatedly (possibly on a different thread) while - * the Context is paused to determine whether it should continue waiting. - */ - void waitFor(final BooleanSupplier predicate); - - /** - * Pauses the execution of this Context until the given LaunchedContext has finished running. - */ - void waitFor(final LaunchedContext otherContext); - - /** - * Pauses the execution of this Context until all of the given LaunchedContexts have finished - * running. - */ - void waitFor(final LaunchedContext... otherContexts); - - /** - * Momentarily pause execution of this Context to allow other Contexts to execute. Execution of - * this Context will resume as soon as possible after the other Contexts have been given a - * chance to run. - * - * Procedures should call this periodically if they wouldn't otherwise call one of the wait* - * methods for a while. - */ - void yield(); - - /** - * Pauses the execution of this Context for the given length of time. - */ - void waitForSeconds(final double seconds); - - /** - * Start running a new Context so the given procedure can run in parallel. - */ - LaunchedContext startAsync(final RunnableWithContext func); - - /** - * Start running a new Context so the given procedure can run in parallel. - */ - LaunchedContextWithValue startAsync(final RunnableWithContextWithValue func); - - /** - * Start running a new Context so the given procedure can run in parallel. - */ - LaunchedContext startAsync(final Runnable func); - - /** - * Run the given Procedure synchronously (the calling Procedure will not resume until this one - * has finished). - */ - void runSync(final RunnableWithContext func); - - /** - * Take ownership of the given Mechanism with this Context. - * - * Only one Context can own a Mechanism at one time. If any Context previously owned this - * Mechanism, it will be terminated. Ownership of this Mechanism can be released by calling - * releaseOwnership, or it will be automatically released when this Context finishes running. - * - * @see Mechanism#takeOwnership(Context, Context) - */ - void takeOwnership(final Mechanism mechanism); - - /** - * Release ownership of the given Mechanism. - * - * It is an error to call this method with a Mechanism that was not previously passed to - * takeOwnership. - * - * @see #takeOwnership(Mechanism) - * @see Mechanism#releaseOwnership(Context) - */ - void releaseOwnership(final Mechanism mechanism); -} diff --git a/src/main/java/com/team766/framework/ContextImpl.java b/src/main/java/com/team766/framework/ContextImpl.java deleted file mode 100644 index 70ffe955..00000000 --- a/src/main/java/com/team766/framework/ContextImpl.java +++ /dev/null @@ -1,485 +0,0 @@ -package com.team766.framework; - -import com.team766.hal.Clock; -import com.team766.hal.RobotProvider; -import com.team766.logging.Category; -import com.team766.logging.Logger; -import com.team766.logging.LoggerExceptionUtils; -import com.team766.logging.Severity; -import java.lang.StackWalker.StackFrame; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.BooleanSupplier; - -/** - * See {@link Context} for a general description of the Context concept. - * - * Currently, threads of execution are implemented using OS threads, but this - * should be considered an implementation detail and may change in the future. - * Even though the framework creates multiple OS threads, it uses Java's - * monitors to implement a "baton passing" pattern in order to ensure that only - * one of threads is actually running at once (the others will be sleeping, - * waiting for the baton to be passed to them). - */ -class ContextImpl implements Runnable, ContextWithValue, LaunchedContextWithValue { - private Optional m_lastYieldedValue = Optional.empty(); - - /** - * Represents the baton-passing state (see class comments). Instead of - * passing a baton directly from one Context's thread to the next, each - * Context has its own baton that gets passed from the program's main thread - * to the Context's thread and back. While this is less efficient (double - * the number of OS context switches required), it makes the code simpler - * and more modular. - */ - private enum ControlOwner { - MAIN_THREAD, - SUBROUTINE, - } - - /** - * Indicates the lifetime state of this Context. - */ - private enum State { - /** - * The Context has been started (a Context is started immediately upon - * construction). - */ - RUNNING, - /** - * stop() has been called on this Context (but it has not been allowed - * to respond to the stop request yet). - */ - CANCELED, - /** - * The Context's execution has come to an end. - */ - DONE, - } - - // package visible for testing - /* package */ static class TimedPredicate implements BooleanSupplier { - private final Clock clock; - private final BooleanSupplier predicate; - private final double deadlineSeconds; - private boolean succeeded = false; - - // package visible for testing - /* package */ TimedPredicate( - Clock clock, BooleanSupplier predicate, double timeoutSeconds) { - this.clock = clock; - this.deadlineSeconds = clock.getTime() + timeoutSeconds; - this.predicate = predicate; - } - - public TimedPredicate(BooleanSupplier predicate, double timeoutSeconds) { - this(RobotProvider.instance.getClock(), predicate, timeoutSeconds); - } - - public boolean getAsBoolean() { - if (predicate.getAsBoolean()) { - succeeded = true; - return true; - } - if (clock.getTime() >= deadlineSeconds) { - succeeded = false; - return true; - } else { - return false; - } - } - - public boolean succeeded() { - return succeeded; - } - } - - private static ContextImpl c_currentContext = null; - - /** - * Returns the currently-executing Context. - * - * This is maintained for things like checking Mechanism ownership, but - * intentionally only has package-private visibility - code outside of the - * framework should ideally pass around references to the current context - * object rather than cheating with this static accessor. - */ - static ContextImpl currentContext() { - return c_currentContext; - } - - /** - * The top-level procedure being run by this Context. - */ - private final RunnableWithContextWithValue m_func; - - /** - * If this Context was created by another context using - * {@link #startAsync}, this will contain a reference to that originating - * Context. - */ - private final ContextImpl m_parentContext; - - /** - * The OS thread that this Context is executing on. - */ - private final Thread m_thread; - - /** - * Used to synchronize access to this Context's state variable. - */ - private final Object m_threadSync; - - /** - * This Context's lifetime state. - */ - private State m_state; - - /** - * If one of the wait* methods has been called on this Context, this - * contains the predicate which should be checked to determine whether - * the Context's execution should be resumed. This makes it more efficient - * to poll completion criteria without needing to context-switch between - * threads. - */ - private BooleanSupplier m_blockingPredicate; - - /** - * Set to SUBROUTINE when this Context is executing and MAIN_THREAD - * otherwise. - */ - private ControlOwner m_controlOwner; - - /** - * Contains the method name and line number at which this Context most - * recently yielded. - */ - private String m_previousWaitPoint; - - /** - * The mechanisms that have been claimed by this Context using - * takeOwnership. These will be automatically released when the Context - * finishes executing. - */ - private Set m_ownedMechanisms = new HashSet(); - - /* - * Constructors are intentionally private or package-private. New contexts - * should be created with {@link Context#startAsync} or - * {@link Scheduler#startAsync}. - */ - - ContextImpl(final RunnableWithContextWithValue func, final ContextImpl parentContext) { - m_func = func; - m_parentContext = parentContext; - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.DEBUG, - "Starting context " + getContextName() + " for " + func.toString()); - m_threadSync = new Object(); - m_previousWaitPoint = null; - m_controlOwner = ControlOwner.MAIN_THREAD; - m_state = State.RUNNING; - m_thread = new Thread(this::threadFunction, getContextName()); - m_thread.start(); - Scheduler.getInstance().add(this); - } - - ContextImpl(final RunnableWithContextWithValue func) { - this(func, null); - } - - ContextImpl(final Runnable func, final ContextImpl parentContext) { - this((context) -> func.run()); - } - - ContextImpl(final Runnable func) { - this(func, null); - } - - /** - * Returns a string meant to uniquely identify this Context (e.g. for use in logging). - */ - public String getContextName() { - return "Context/" + Integer.toHexString(hashCode()) + "/" + m_func.toString(); - } - - @Override - public String toString() { - String repr = getContextName(); - if (currentContext() == this) { - repr += " running"; - } - repr += "\n"; - repr += StackTraceUtils.getStackTrace(m_thread); - return repr; - } - - /** - * Walks up the call stack until it reaches a frame that isn't from the Context class, then - * returns a string representation of that frame. This is used to generate a concise string - * representation of from where the user called into framework code. - */ - private String getExecutionPoint() { - StackWalker walker = StackWalker.getInstance(); - return walker.walk( - s -> - s.dropWhile(f -> f.getClassName() != ContextImpl.this.getClass().getName()) - .filter( - f -> - f.getClassName() - != ContextImpl.this.getClass().getName()) - .findFirst() - .map(StackFrame::toString) - .orElse(null)); - } - - /** - * Wait until the baton (see the class comments) has been passed to this thread. - * - * @param thisOwner the thread from which this function is being called (and thus the - * baton-passing state that should be waited for) - * @throws ContextStoppedException if stop() is called on this Context while waiting. - */ - private void waitForControl(final ControlOwner thisOwner) { - // If this is being called from the worker thread, log from where in the - // user's code that the context is waiting. This is provided as a - // convenience so the user can track the progress of execution through - // their procedures. - if (thisOwner == ControlOwner.SUBROUTINE) { - String waitPointTrace = getExecutionPoint(); - if (waitPointTrace != null && !waitPointTrace.equals(m_previousWaitPoint)) { - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.DEBUG, - getContextName() + " is waiting at " + waitPointTrace); - m_previousWaitPoint = waitPointTrace; - } - } - // Wait for the baton to be passed to us. - synchronized (m_threadSync) { - while (m_controlOwner != thisOwner && m_state != State.DONE) { - try { - m_threadSync.wait(); - } catch (InterruptedException e) { - } - } - m_controlOwner = thisOwner; - if (m_state != State.RUNNING && m_controlOwner == ControlOwner.SUBROUTINE) { - throw new ContextStoppedException(); - } - } - } - - /** - * Pass the baton (see the class comments) to the other thread and then wait for it to be passed - * back. - * - * @param thisOwner the thread from which this function is being called (and thus the - * baton-passing state that should be waited for) - * @param desiredOwner the thread to which the baton should be passed - * @throws ContextStoppedException if stop() is called on this Context while waiting. - */ - private void transferControl(final ControlOwner thisOwner, final ControlOwner desiredOwner) { - synchronized (m_threadSync) { - // Make sure we currently have the baton before trying to give it to - // someone else. - if (m_controlOwner != thisOwner) { - throw new IllegalStateException( - "Subroutine had control owner " - + m_controlOwner - + " but assumed control owner " - + thisOwner); - } - // Pass the baton. - m_controlOwner = desiredOwner; - if (m_controlOwner == ControlOwner.SUBROUTINE) { - c_currentContext = this; - } else { - c_currentContext = null; - } - m_threadSync.notifyAll(); - // Wait for the baton to be passed back. - waitForControl(thisOwner); - } - } - - /** - * This is the entry point for this Context's worker thread. - */ - private void threadFunction() { - try { - // OS threads run independently of one another, so we need to wait until - // the baton is passed to us before we can start running the user's code - waitForControl(ControlOwner.SUBROUTINE); - // Call into the user's code. - m_func.run(this); - Logger.get(Category.FRAMEWORK) - .logRaw(Severity.DEBUG, "Context " + getContextName() + " finished"); - } catch (ContextStoppedException ex) { - Logger.get(Category.FRAMEWORK) - .logRaw(Severity.WARNING, getContextName() + " was stopped"); - } catch (Exception ex) { - ex.printStackTrace(); - LoggerExceptionUtils.logException(ex); - Logger.get(Category.FRAMEWORK) - .logRaw(Severity.WARNING, "Context " + getContextName() + " died"); - } finally { - for (Mechanism m : m_ownedMechanisms) { - // Don't use this.releaseOwnership here, because that would cause a - // ConcurrentModificationException since we're iterating over m_ownedMechanisms - try { - m.releaseOwnership(this); - } catch (Exception ex) { - LoggerExceptionUtils.logException(ex); - } - } - synchronized (m_threadSync) { - m_state = State.DONE; - c_currentContext = null; - m_threadSync.notifyAll(); - } - m_ownedMechanisms.clear(); - } - } - - @Override - public boolean waitForConditionOrTimeout( - final BooleanSupplier predicate, double timeoutSeconds) { - TimedPredicate timedPredicate = new TimedPredicate(predicate, timeoutSeconds); - waitFor(timedPredicate); - return timedPredicate.succeeded(); - } - - @Override - public void waitFor(final BooleanSupplier predicate) { - if (!predicate.getAsBoolean()) { - m_blockingPredicate = predicate; - transferControl(ControlOwner.SUBROUTINE, ControlOwner.MAIN_THREAD); - } - } - - @Override - public void waitFor(final LaunchedContext otherContext) { - waitFor(otherContext::isDone); - } - - @Override - public void waitFor(final LaunchedContext... otherContexts) { - waitFor(() -> Arrays.stream(otherContexts).allMatch(LaunchedContext::isDone)); - } - - @Override - public void waitForSeconds(final double seconds) { - double startTime = RobotProvider.instance.getClock().getTime(); - waitFor(() -> RobotProvider.instance.getClock().getTime() - startTime > seconds); - } - - @Override - public void yield() { - m_blockingPredicate = null; - transferControl(ControlOwner.SUBROUTINE, ControlOwner.MAIN_THREAD); - } - - @Override - public void yield(final T valueToYield) { - m_lastYieldedValue = Optional.of(valueToYield); - this.yield(); - } - - @Override - public T lastYieldedValue() { - return m_lastYieldedValue.orElse(null); - } - - @Override - public boolean hasYieldedValue() { - return m_lastYieldedValue.isPresent(); - } - - @Override - public T getAndClearLastYieldedValue() { - final var result = m_lastYieldedValue; - m_lastYieldedValue = Optional.empty(); - return result.orElse(null); - } - - @Override - public LaunchedContext startAsync(final RunnableWithContext func) { - return new ContextImpl<>(func::run, this); - } - - @Override - public LaunchedContextWithValue startAsync(final RunnableWithContextWithValue func) { - return new ContextImpl(func, this); - } - - @Override - public LaunchedContext startAsync(final Runnable func) { - return new ContextImpl<>(func, this); - } - - @Override - public void runSync(final RunnableWithContext func) { - func.run(this); - } - - /** - * Interrupt the running of this Context and force it to terminate. - * - * A ContextStoppedException will be raised on this Context at the point where the Context most - * recently waited or yielded -- if this Context is currently executing, a - * ContextStoppedException will be raised immediately. - */ - @Override - public void stop() { - Logger.get(Category.FRAMEWORK) - .logRaw(Severity.DEBUG, "Stopping requested of " + getContextName()); - synchronized (m_threadSync) { - if (m_state != State.DONE) { - m_state = State.CANCELED; - } - if (m_controlOwner == ControlOwner.SUBROUTINE) { - throw new ContextStoppedException(); - } - } - } - - /** - * Entry point for the Scheduler to execute this Context. - * - * This should only be called from framework code; it is public only as an implementation - * detail. - */ - @Override - public void run() { - if (m_state == State.DONE) { - Scheduler.getInstance().cancel(this); - return; - } - if (m_state == State.CANCELED - || m_blockingPredicate == null - || m_blockingPredicate.getAsBoolean()) { - transferControl(ControlOwner.MAIN_THREAD, ControlOwner.SUBROUTINE); - } - } - - @Override - public boolean isDone() { - return m_state == State.DONE; - } - - @Override - public void takeOwnership(final Mechanism mechanism) { - mechanism.takeOwnership(this, m_parentContext); - m_ownedMechanisms.add(mechanism); - } - - @Override - public void releaseOwnership(final Mechanism mechanism) { - mechanism.releaseOwnership(this); - m_ownedMechanisms.remove(mechanism); - } -} diff --git a/src/main/java/com/team766/framework/ContextWithValue.java b/src/main/java/com/team766/framework/ContextWithValue.java deleted file mode 100644 index 14950ce1..00000000 --- a/src/main/java/com/team766/framework/ContextWithValue.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.team766.framework; - -/** - * A {@link Context} that also allows the ProcedureWithValues running on it to yield values when - * running asynchronously. - */ -public interface ContextWithValue extends Context { - /** - * Momentarily pause execution of this Context to allow other Contexts to execute. Execution of - * this Context will resume as soon as possible after the other Contexts have been given a - * chance to run. - * - * The most recent value passed to this yield(T) method will be returned by subsequent calls to - * LaunchedContextWithValue.lastYieldedValue(). - */ - void yield(final T valueToYield); -} diff --git a/src/main/java/com/team766/framework/LaunchedContext.java b/src/main/java/com/team766/framework/LaunchedContext.java deleted file mode 100644 index c4be6bdf..00000000 --- a/src/main/java/com/team766/framework/LaunchedContext.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.team766.framework; - -/** - * This interface can be used by the caller to manage Contexts created by startAsync. - */ -public interface LaunchedContext { - /** - * Returns a string meant to uniquely identify this Context (e.g. for use in - * logging). - */ - String getContextName(); - - /** - * Returns true if this Context has finished running, false otherwise. - */ - boolean isDone(); - - /** - * Interrupt the running of this Context and force it to terminate. - */ - void stop(); -} diff --git a/src/main/java/com/team766/framework/LaunchedContextWithValue.java b/src/main/java/com/team766/framework/LaunchedContextWithValue.java deleted file mode 100644 index 5539d052..00000000 --- a/src/main/java/com/team766/framework/LaunchedContextWithValue.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.team766.framework; - -/** - * This interface can be used by the caller to manage Contexts created by startAsync which are - * expected to yield values. - */ -public interface LaunchedContextWithValue extends LaunchedContext { - /** - * Return the most recent value that the Procedure passed to Context.yield(T). - * Return null if a value has never been yielded, or a value has not been yielded since the last - * call to getAndClearLastYieldedValue. - */ - T lastYieldedValue(); - - /** - * Return true if a has been yielded by the Procedure since since the last call to - * getAndClearLastYieldedValue. Return false otherwise. - */ - boolean hasYieldedValue(); - - /** - * Return the most recent value that the Procedure passed to Context.yield(T), and clear the - * recorded last yielded value such that subsequent calls to hasYieldedValue() will return - * false. - */ - T getAndClearLastYieldedValue(); -} diff --git a/src/main/java/com/team766/framework/LoggingBase.java b/src/main/java/com/team766/framework/LoggingBase.java deleted file mode 100644 index 5e6ac1bd..00000000 --- a/src/main/java/com/team766/framework/LoggingBase.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.team766.framework; - -import com.team766.logging.Category; -import com.team766.logging.Logger; -import com.team766.logging.Severity; - -public abstract class LoggingBase { - protected Category loggerCategory = Category.PROCEDURES; - - public abstract String getName(); - - protected void log(final String message) { - log(Severity.INFO, message); - } - - protected void log(final Severity severity, final String message) { - Logger.get(loggerCategory).logRaw(severity, getName() + ": " + message); - } - - protected void log(final String format, final Object... args) { - log(Severity.INFO, format, args); - } - - protected void log(final Severity severity, final String format, final Object... args) { - Logger.get(loggerCategory).logData(severity, getName() + ": " + format, args); - } -} diff --git a/src/main/java/com/team766/framework/Mechanism.java b/src/main/java/com/team766/framework/Mechanism.java deleted file mode 100644 index 28511786..00000000 --- a/src/main/java/com/team766/framework/Mechanism.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.team766.framework; - -import com.team766.logging.Category; -import com.team766.logging.Logger; -import com.team766.logging.LoggerExceptionUtils; -import com.team766.logging.Severity; - -public abstract class Mechanism extends LoggingBase { - private ContextImpl m_owningContext = null; - private Thread m_runningPeriodic = null; - - public Mechanism() { - loggerCategory = Category.MECHANISMS; - - Scheduler.getInstance() - .add( - new Runnable() { - @Override - public void run() { - try { - Mechanism.this.m_runningPeriodic = Thread.currentThread(); - Mechanism.this.run(); - } finally { - Mechanism.this.m_runningPeriodic = null; - } - } - - @Override - public String toString() { - String repr = Mechanism.this.getName(); - if (Mechanism.this.m_runningPeriodic != null) { - repr += - " running\n" - + StackTraceUtils.getStackTrace( - m_runningPeriodic); - } - return repr; - } - }); - } - - public String getName() { - return this.getClass().getName(); - } - - protected void checkContextOwnership() { - if (ContextImpl.currentContext() != m_owningContext && m_runningPeriodic == null) { - String message = - getName() - + " tried to be used by " - + ContextImpl.currentContext().getContextName(); - if (m_owningContext != null) { - message += " while owned by " + m_owningContext.getContextName(); - } else { - message += " without taking ownership of it"; - } - Logger.get(Category.FRAMEWORK).logRaw(Severity.ERROR, message); - throw new IllegalStateException(message); - } - } - - void takeOwnership(final ContextImpl context, final ContextImpl parentContext) { - if (m_owningContext != null && m_owningContext == parentContext) { - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.DEBUG, - context.getContextName() - + " is inheriting ownership of " - + getName() - + " from " - + parentContext.getContextName()); - } else { - if (m_owningContext != context) { - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.DEBUG, - context.getContextName() + " is taking ownership of " + getName()); - } - while (m_owningContext != null && m_owningContext != context) { - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.WARNING, - "Stopping previous owner of " - + getName() - + ": " - + m_owningContext.getContextName()); - m_owningContext.stop(); - var stoppedContext = m_owningContext; - context.yield(); - if (m_owningContext == stoppedContext) { - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.ERROR, - "Previous owner of " - + getName() - + ", " - + m_owningContext.getContextName() - + " did not release ownership when requested. Release will be forced."); - m_owningContext.releaseOwnership(this); - break; - } - } - } - m_owningContext = context; - } - - void releaseOwnership(final ContextImpl context) { - if (m_owningContext != context) { - LoggerExceptionUtils.logException( - new Exception( - context.getContextName() - + " tried to release ownership of " - + getName() - + " but it doesn't own it")); - return; - } - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.DEBUG, - context.getContextName() + " is releasing ownership of " + getName()); - m_owningContext = null; - } - - public void run() {} -} diff --git a/src/main/java/com/team766/framework/OIFragment.java b/src/main/java/com/team766/framework/OIFragment.java deleted file mode 100644 index 20336a7f..00000000 --- a/src/main/java/com/team766/framework/OIFragment.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.team766.framework; - -import java.util.LinkedList; -import java.util.List; -import java.util.function.BooleanSupplier; - -/** - * Fragment of an OI, with facilities to make it easy to set up {@link OICondition}s for usage in the fragment's - * {@link #handleOI} method. - * - * The overall OI for a robot will contain a set of fragments, typically one per set of controls (eg, driver, boxop, debug), - * and it will call {@link #runOI(Context)} once per its own loop. During each call to {@link #runOI}, the fragment - * will evaluate any {@link OICondition}s that were created for this fragment. This simplifies OI logic that checks if a - * specific condition is currently triggering (eg pressing or holding down a joystick button) or if a condition that had been triggering - * in a previous iteration of the OI loop is no longer triggering in this iteration. - */ -public abstract class OIFragment extends LoggingBase { - - protected class OICondition { - private final BooleanSupplier condition; - private boolean triggering = false; - private boolean newlyTriggering = false; - private boolean finishedTriggering = false; - - public OICondition(BooleanSupplier condition) { - this.condition = condition; - register(this); - } - - private void evaluate() { - boolean triggeringNow = condition.getAsBoolean(); - if (triggeringNow) { - newlyTriggering = !triggering; - finishedTriggering = false; - } else { - finishedTriggering = triggering; - newlyTriggering = false; - } - triggering = triggeringNow; - } - - public boolean isTriggering() { - return triggering; - } - - public boolean isNewlyTriggering() { - return newlyTriggering; - } - - public boolean isFinishedTriggering() { - return finishedTriggering; - } - } - - private final String name; - private final List conditions = new LinkedList(); - - /** - * Creates a new OIFragment. - * @param name The name of this part of the OI (eg, "BoxOpOI"). Used for logging. - */ - public OIFragment(String name) { - this.name = name; - } - - /** - * Creates a new OIFragment, using the name of the sub-class. - */ - public OIFragment() { - this.name = this.getClass().getSimpleName(); - } - - public final String getName() { - return name; - } - - private void register(OICondition condition) { - conditions.add(condition); - } - - /** - * Called at the beginning of {@link #runOI(Context)}, before evaluating any of the registered conditions - * and before calling {@link #handleOI(Context)}. Subclasses should override this if needed. - */ - protected void handlePre() {} - - /** - * OIFragments must override this method to implement their OI logic. Typically called via the overall - * OI's loop, once per iteration through the loop. Can use any {@link OICondition}s - * they have set up to simplify checking if the {@link OICondition} is {@link OICondition#isTriggering()}, - * or, if it had been triggering in a previous iteration of the loop, if it is now - * {@link OICondition#isFinishedTriggering()}. - * - * @param context The {@link Context} running the OI. - */ - protected abstract void handleOI(Context context); - - /** - * Called after {@link #handleOI}, at the end of {@link #runOI}. Subclasses should override this if needed. - */ - protected void handlePost() {} - - /** - * Called by a Robot's OI class, once per its loop. - * Calls {@link #handlePre()}, evaluates all conditions once per call, and calls {@link #handlePost()}. - * @param context The {@link Context} running the OI. - */ - public final void runOI(Context context) { - handlePre(); - for (OICondition condition : conditions) { - condition.evaluate(); - } - handleOI(context); - handlePost(); - } -} diff --git a/src/main/java/com/team766/framework/Procedure.java b/src/main/java/com/team766/framework/Procedure.java deleted file mode 100644 index 8bea8a10..00000000 --- a/src/main/java/com/team766/framework/Procedure.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.team766.framework; - -public abstract class Procedure extends ProcedureBase implements RunnableWithContext { - // A reusable Procedure that does nothing. - private static final class NoOpProcedure extends Procedure { - @Override - public void run(final Context context) {} - } - - public static final Procedure NO_OP = new NoOpProcedure(); -} diff --git a/src/main/java/com/team766/framework/ProcedureBase.java b/src/main/java/com/team766/framework/ProcedureBase.java deleted file mode 100644 index 9576c177..00000000 --- a/src/main/java/com/team766/framework/ProcedureBase.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.team766.framework; - -public abstract class ProcedureBase extends LoggingBase { - private static int c_idCounter = 0; - - private static synchronized int createNewId() { - return c_idCounter++; - } - - protected final int m_id; - - ProcedureBase() { - m_id = createNewId(); - } - - public String getName() { - return this.getClass().getName() + "/" + m_id; - } - - @Override - public String toString() { - return getName(); - } -} diff --git a/src/main/java/com/team766/framework/ProcedureWithValue.java b/src/main/java/com/team766/framework/ProcedureWithValue.java deleted file mode 100644 index 25b2a1b6..00000000 --- a/src/main/java/com/team766/framework/ProcedureWithValue.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.team766.framework; - -public abstract class ProcedureWithValue extends ProcedureBase - implements RunnableWithContextWithValue {} diff --git a/src/main/java/com/team766/framework/RunnableWithContext.java b/src/main/java/com/team766/framework/RunnableWithContext.java deleted file mode 100644 index bf76797e..00000000 --- a/src/main/java/com/team766/framework/RunnableWithContext.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.team766.framework; - -@FunctionalInterface -public interface RunnableWithContext { - void run(Context context); -} diff --git a/src/main/java/com/team766/framework/RunnableWithContextWithValue.java b/src/main/java/com/team766/framework/RunnableWithContextWithValue.java deleted file mode 100644 index 82d02a1b..00000000 --- a/src/main/java/com/team766/framework/RunnableWithContextWithValue.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.team766.framework; - -@FunctionalInterface -public interface RunnableWithContextWithValue { - void run(ContextWithValue context); -} diff --git a/src/main/java/com/team766/framework/Scheduler.java b/src/main/java/com/team766/framework/Scheduler.java deleted file mode 100644 index 68413a93..00000000 --- a/src/main/java/com/team766/framework/Scheduler.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.team766.framework; - -import com.team766.logging.Category; -import com.team766.logging.Logger; -import com.team766.logging.LoggerExceptionUtils; -import com.team766.logging.Severity; -import java.util.LinkedList; -import java.util.stream.Collectors; - -public class Scheduler implements Runnable { - private static final Scheduler c_instance; - private static final Thread c_monitor; - - static { - c_instance = new Scheduler(); - c_monitor = new Thread(Scheduler::monitor); - c_monitor.setDaemon(true); - c_monitor.start(); - } - - public static Scheduler getInstance() { - return c_instance; - } - - private static void monitor() { - int lastIterationCount = 0; - Runnable lastRunning = null; - while (true) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - - if (c_instance.m_running != null - && c_instance.m_iterationCount == lastIterationCount - && c_instance.m_running == lastRunning) { - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.ERROR, - "The code has gotten stuck in " - + c_instance.m_running.toString() - + ". You probably have an unintended infinite loop or need to add a call to context.yield()"); - Logger.get(Category.FRAMEWORK) - .logRaw( - Severity.INFO, - Thread.getAllStackTraces().entrySet().stream() - .map( - e -> - e.getKey().getName() - + ":\n" - + StackTraceUtils.getStackTrace( - e.getValue())) - .collect(Collectors.joining("\n"))); - } - - lastIterationCount = c_instance.m_iterationCount; - lastRunning = c_instance.m_running; - } - } - - private LinkedList m_runnables = new LinkedList(); - private int m_iterationCount = 0; - private Runnable m_running = null; - - public void add(final Runnable runnable) { - m_runnables.add(runnable); - } - - public void cancel(final Runnable runnable) { - m_runnables.remove(runnable); - } - - public void reset() { - m_runnables.clear(); - } - - public LaunchedContext startAsync(final RunnableWithContext func) { - return new ContextImpl<>(func::run); - } - - public LaunchedContextWithValue startAsync(final RunnableWithContextWithValue func) { - return new ContextImpl(func); - } - - public LaunchedContext startAsync(final Runnable func) { - return new ContextImpl<>(func); - } - - public void run() { - ++m_iterationCount; - for (Runnable runnable : new LinkedList(m_runnables)) { - try { - m_running = runnable; - runnable.run(); - } catch (Exception ex) { - ex.printStackTrace(); - LoggerExceptionUtils.logException(ex); - } finally { - m_running = null; - } - } - } -} diff --git a/src/main/java/com/team766/framework/WPILibCommandProcedure.java b/src/main/java/com/team766/framework/WPILibCommandProcedure.java deleted file mode 100644 index 90c67dc9..00000000 --- a/src/main/java/com/team766/framework/WPILibCommandProcedure.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.team766.framework; - -import edu.wpi.first.wpilibj2.command.Command; - -/** - * This wraps a class that confroms to WPILib's Command interface, and allows - * it to be used in the Maroon Framework as a Procedure. - */ -public class WPILibCommandProcedure extends Procedure { - - private final Command command; - private Mechanism[] requirements; - - /** - * @param command The WPILib Command to adapt - * @param requirements This Procedure will take ownership of the Mechanisms - * given here during the time it is executing. - */ - public WPILibCommandProcedure(final Command command_, final Mechanism... requirements_) { - this.command = command_; - this.requirements = requirements_; - } - - @Override - public void run(final Context context) { - for (Mechanism req : this.requirements) { - context.takeOwnership(req); - } - boolean interrupted = false; - try { - this.command.initialize(); - while (!this.command.isFinished()) { - this.command.execute(); - context.yield(); - } - } catch (Throwable ex) { - interrupted = true; - throw ex; - } finally { - this.command.end(interrupted); - for (Mechanism req : this.requirements) { - context.releaseOwnership(req); - } - } - } -} diff --git a/src/main/java/com/team766/hal/GenericRobotMain.java b/src/main/java/com/team766/hal/GenericRobotMain.java deleted file mode 100644 index e23e30d4..00000000 --- a/src/main/java/com/team766/hal/GenericRobotMain.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.team766.hal; - -import com.team766.framework.AutonomousMode; -import com.team766.framework.LaunchedContext; -import com.team766.framework.Procedure; -import com.team766.framework.Scheduler; -import com.team766.logging.Category; -import com.team766.logging.Logger; -import com.team766.logging.Severity; -import com.team766.web.AutonomousSelector; -import com.team766.web.ConfigUI; -import com.team766.web.Dashboard; -import com.team766.web.DriverInterface; -import com.team766.web.LogViewer; -import com.team766.web.ReadLogs; -import com.team766.web.WebServer; - -// Team 766 - Robot Interface Base class - -public final class GenericRobotMain implements GenericRobotMainBase { - private RobotConfigurator configurator; - private Procedure m_oi; - - private WebServer m_webServer; - private AutonomousSelector m_autonSelector; - private AutonomousMode m_autonMode = null; - private LaunchedContext m_autonomous = null; - private LaunchedContext m_oiContext = null; - - // Reset the autonomous routine if the robot is disabled for more than this - // number of seconds. - private static final double RESET_IN_DISABLED_PERIOD = 10.0; - private double m_disabledModeStartTime; - - private boolean faultInRobotInit = false; - private boolean faultInAutoInit = false; - private boolean faultInTeleopInit = false; - - public GenericRobotMain(RobotConfigurator configurator) { - Scheduler.getInstance().reset(); - - this.configurator = configurator; - m_autonSelector = new AutonomousSelector<>(configurator.getAutonomousModes()); - m_webServer = new WebServer(); - m_webServer.addHandler(new Dashboard()); - m_webServer.addHandler(new DriverInterface(m_autonSelector)); - m_webServer.addHandler(new ConfigUI()); - m_webServer.addHandler(new LogViewer()); - m_webServer.addHandler(new ReadLogs()); - m_webServer.addHandler(m_autonSelector); - m_webServer.start(); - } - - public void robotInit() { - try { - configurator.initializeMechanisms(); - - m_oi = configurator.createOI(); - } catch (Throwable ex) { - faultInRobotInit = true; - throw ex; - } - faultInRobotInit = false; - } - - public void disabledInit() { - m_disabledModeStartTime = RobotProvider.instance.getClock().getTime(); - } - - public void disabledPeriodic() { - if (faultInRobotInit) return; - - // The robot can enter disabled mode for two reasons: - // - The field control system set the robots to disabled. - // - The robot loses communication with the driver station. - // In the former case, we want to reset the autonomous routine, as there - // may have been a field fault, which would mean the match is going to - // be replayed (and thus we would want to run the autonomous routine - // from the beginning). In the latter case, we don't want to reset the - // autonomous routine because the communication drop was likely caused - // by some short-lived (less than a second long, or so) interference; - // when the communications are restored, we want to continue executing - // the routine that was interrupted, since it has knowledge of where the - // robot is on the field, the state of the robot's mechanisms, etc. - // Thus, we set a threshold on the amount of time spent in autonomous of - // 10 seconds. It is almost certain that it will take longer than 10 - // seconds to reset the field if a match is to be replayed, but it is - // also almost certain that a communication drop will be much shorter - // than 10 seconds. - double timeInState = RobotProvider.instance.getClock().getTime() - m_disabledModeStartTime; - if (timeInState > RESET_IN_DISABLED_PERIOD) { - resetAutonomousMode("time in disabled mode"); - } - Scheduler.getInstance().run(); - } - - public void resetAutonomousMode(final String reason) { - if (m_autonomous != null) { - m_autonomous.stop(); - m_autonomous = null; - m_autonMode = null; - Logger.get(Category.AUTONOMOUS) - .logRaw(Severity.INFO, "Resetting autonomus procedure from " + reason); - } - } - - public void autonomousInit() { - faultInAutoInit = true; - - if (m_oiContext != null) { - m_oiContext.stop(); - m_oiContext = null; - } - - if (m_autonomous != null) { - Logger.get(Category.AUTONOMOUS) - .logRaw( - Severity.INFO, - "Continuing previous autonomus procedure " - + m_autonomous.getContextName()); - } else if (m_autonSelector.getSelectedAutonMode() == null) { - Logger.get(Category.AUTONOMOUS).logRaw(Severity.WARNING, "No autonomous mode selected"); - } - faultInAutoInit = false; - } - - public void autonomousPeriodic() { - if (faultInRobotInit || faultInAutoInit) return; - - final AutonomousMode autonomousMode = m_autonSelector.getSelectedAutonMode(); - if (autonomousMode != null && m_autonMode != autonomousMode) { - final Procedure autonProcedure = autonomousMode.instantiate(); - m_autonomous = Scheduler.getInstance().startAsync(autonProcedure); - m_autonMode = autonomousMode; - Logger.get(Category.AUTONOMOUS) - .logRaw( - Severity.INFO, - "Starting new autonomus procedure " + autonProcedure.getName()); - } - Scheduler.getInstance().run(); - } - - public void teleopInit() { - faultInTeleopInit = true; - - if (m_autonomous != null) { - m_autonomous.stop(); - m_autonomous = null; - m_autonMode = null; - } - - if (m_oiContext == null && m_oi != null) { - m_oiContext = Scheduler.getInstance().startAsync(m_oi); - } - - faultInTeleopInit = false; - } - - public void teleopPeriodic() { - if (faultInRobotInit || faultInTeleopInit) return; - - if (m_oiContext != null && m_oiContext.isDone()) { - m_oiContext = Scheduler.getInstance().startAsync(m_oi); - Logger.get(Category.OPERATOR_INTERFACE) - .logRaw(Severity.WARNING, "Restarting OI context"); - } - Scheduler.getInstance().run(); - } -} diff --git a/src/main/java/com/team766/hal/GenericRobotMainBase.java b/src/main/java/com/team766/hal/GenericRobotMainBase.java deleted file mode 100644 index 7274513f..00000000 --- a/src/main/java/com/team766/hal/GenericRobotMainBase.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.team766.hal; - -public interface GenericRobotMainBase { - void robotInit(); - - void disabledInit(); - - void disabledPeriodic(); - - void resetAutonomousMode(final String reason); - - void autonomousInit(); - - void autonomousPeriodic(); - - void teleopInit(); - - void teleopPeriodic(); -} diff --git a/src/main/java/com/team766/hal/RobotConfigurator.java b/src/main/java/com/team766/hal/RobotConfigurator.java deleted file mode 100644 index a711ada1..00000000 --- a/src/main/java/com/team766/hal/RobotConfigurator.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.team766.hal; - -import com.team766.framework.AutonomousMode; -import com.team766.framework.Procedure; -import com.team766.logging.Category; -import com.team766.logging.Logger; -import com.team766.logging.Severity; - -/** - * Provides Robot-specific components: initializes {@link Mechanism}s, creates the Operator Interface (OI), - * and returns the {@link AutonomousMode}s. - * - * @see RobotSelector - */ -public interface RobotConfigurator extends RobotConfiguratorBase { - - /** - * Initializes the {@link Mechanism}s for this robot. - * - * Will only be called once by the framework. - */ - void initializeMechanisms(); - - /** - * Creates the Operator Interface (OI) for this robot. - */ - Procedure createOI(); - - /** - * Returns an array of {@link AutonomousMode}s available for this robot. - */ - AutonomousMode[] getAutonomousModes(); - - @Override - default GenericRobotMainBase createRobotMain() { - Logger.get(Category.FRAMEWORK).logRaw(Severity.INFO, "Instantiating GenericRobotMain"); - return new GenericRobotMain(this); - } -} diff --git a/src/main/java/com/team766/hal/RobotConfiguratorBase.java b/src/main/java/com/team766/hal/RobotConfiguratorBase.java deleted file mode 100644 index 22173e29..00000000 --- a/src/main/java/com/team766/hal/RobotConfiguratorBase.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.team766.hal; - -public interface RobotConfiguratorBase { - GenericRobotMainBase createRobotMain(); -} diff --git a/src/test/java/com/team766/TestCase.java b/src/test/java/com/team766/TestCase.java deleted file mode 100644 index 438438bc..00000000 --- a/src/test/java/com/team766/TestCase.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.team766; - -import com.team766.config.ConfigFileReader; -import com.team766.config.ConfigFileTestUtils; -import com.team766.framework.Scheduler; -import com.team766.hal.RobotProvider; -import com.team766.hal.TestClock; -import com.team766.hal.mock.TestRobotProvider; -import java.io.IOException; -import java.nio.file.Files; -import org.junit.jupiter.api.BeforeEach; - -public abstract class TestCase { - - @BeforeEach - public void setUp() { - ConfigFileTestUtils.resetStatics(); - Scheduler.getInstance().reset(); - - RobotProvider.instance = new TestRobotProvider(new TestClock()); - } - - protected void loadConfig(String configJson) throws IOException { - var configFilePath = Files.createTempFile("testConfig", ".txt"); - Files.writeString(configFilePath, configJson); - ConfigFileReader.instance = new ConfigFileReader(configFilePath.toString()); - } - - protected void step() { - Scheduler.getInstance().run(); - } -} diff --git a/src/test/java/com/team766/framework/ContextTest.java b/src/test/java/com/team766/framework/ContextTest.java deleted file mode 100644 index 147107f1..00000000 --- a/src/test/java/com/team766/framework/ContextTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.team766.framework; - -import static org.junit.jupiter.api.Assertions.*; - -import com.team766.TestCase; -import org.junit.jupiter.api.Test; - -public class ContextTest extends TestCase { - /// Regression test: calling stop() on a Context before it is allowed to - /// run the first time should not crash the program. - @Test - public void testStopOnFirstTick() { - var lc = Scheduler.getInstance().startAsync(Procedure.NO_OP); - lc.stop(); - - step(); - - assertTrue(lc.isDone()); - } -} diff --git a/src/test/java/com/team766/framework/OIFragmentTest.java b/src/test/java/com/team766/framework/OIFragmentTest.java deleted file mode 100644 index 94c9d92d..00000000 --- a/src/test/java/com/team766/framework/OIFragmentTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.team766.framework; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -public class OIFragmentTest { - - private static class TestFragment extends OIFragment { - @Override - protected void handleOI(Context context) {} - } - - @Test - public void testDefaultName() { - assertEquals("TestFragment", new TestFragment().getName()); - } -} diff --git a/src/test/java/com/team766/framework/YieldWithValueTest.java b/src/test/java/com/team766/framework/YieldWithValueTest.java deleted file mode 100644 index ef484f54..00000000 --- a/src/test/java/com/team766/framework/YieldWithValueTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.team766.framework; - -import static org.junit.jupiter.api.Assertions.*; - -import com.team766.TestCase; -import java.util.ArrayList; -import java.util.List; -import org.junit.jupiter.api.Test; - -public class YieldWithValueTest extends TestCase { - private static class ValueConsumer extends Procedure { - public final ArrayList values = new ArrayList<>(); - - @Override - public void run(Context context) { - var generator = context.startAsync(new ValueGenerator()); - - assertNull( - generator.lastYieldedValue(), - "lastYieldedValue should be null before the procedure yields a value"); - - while (generator.lastYieldedValue() == null || generator.lastYieldedValue() < 10) { - var value = generator.lastYieldedValue(); - if (value != null) { - values.add(value); - } - context.yield(); - } - } - } - - private static class ValueGenerator extends ProcedureWithValue { - @Override - public void run(ContextWithValue context) { - for (int i = 0; i <= 10; ++i) { - context.yield(i); - } - } - } - - @Test - public void testYieldWithValue() { - var consumer = new ValueConsumer(); - Scheduler.getInstance().startAsync(consumer); - - for (int i = 0; i < 50; ++i) { - step(); - } - - assertEquals(consumer.values, List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); - } -}