/*
* Copyright 2013-2016 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.concurrency;
import ccre.channel.BooleanCell;
import ccre.channel.BooleanInput;
import ccre.channel.EventInput;
import ccre.channel.EventOutput;
import ccre.log.Logger;
import ccre.verifier.FlowPhase;
/**
* A worker thread that will allow other threads to trigger a predefined action
* in this thread. Multiple triggerings will be collapsed into a single trigger.
* If the action is executed while the thread is working, and
* shouldIgnoreWhileRunning was not set to true in the constructor, the trigger
* is ignored.
*
* This is an EventOutput - when it is fired, it will trigger this thread's
* work.
*
* @author skeggsc
*/
public abstract class CollapsingWorkerThread extends ReporterThread implements EventOutput {
/**
* Does this thread need to run its work?
*/
private volatile boolean needsToRun = false;
/**
* Should this thread ignore any triggers while it is working? This is set
* by the constructor.
*
* @see #CollapsingWorkerThread(java.lang.String, boolean)
*/
private final boolean shouldIgnoreWhileRunning;
/**
* The internal object to use for notification and synchronization.
*/
private final Object lockObject = new Object();
/**
* A BooleanCell that represents if work is currently running.
*/
private final BooleanCell runningCell = new BooleanCell();
/**
* Should this thread stop doing work now?
*/
private boolean terminated = false;
/**
* Create a new CollapsingWorkerThread with the given name. Will ignore any
* triggers while the work is running.
*
* @param name the thread's name
*/
public CollapsingWorkerThread(String name) {
super(name);
shouldIgnoreWhileRunning = true;
}
/**
* Create a new CollapsingWorkerThread with the given name. If
* shouldIgnoreWhileRunning is true, this will ignore any triggers while the
* work is running.
*
* @param name the thread's name
* @param shouldIgnoreWhileRunning should the thread ignore triggers while
* the work is running.
*/
public CollapsingWorkerThread(String name, boolean shouldIgnoreWhileRunning) {
super(name);
this.shouldIgnoreWhileRunning = shouldIgnoreWhileRunning;
}
/**
* Trigger the work. When possible, the thread will run its doWork method.
* This method exists for using this thread as an EventOutput. You may
* prefer trigger() instead, although there is no functional difference.
*
* @see #trigger()
*/
@Override
public void event() {
trigger();
}
/**
* When the given event is fired, trigger this thread's work as in the
* eventFired() method. This is equivalent to adding this object as a
* listener to the given EventInput.
*
* @param event when to trigger the work.
* @see #event()
*/
public void triggerWhen(EventInput event) {
event.send(this);
}
/**
* Trigger the work. When possible, the thread will run its doWork method.
*/
@FlowPhase
public void trigger() {
synchronized (lockObject) {
needsToRun = true;
startIfNotAlive();
lockObject.notifyAll();
}
}
/**
* Get a BooleanInput that represents whether or not this thread's work is
* currently running.
*
* @return a BooleanInput reflecting if work is happening.
*/
public BooleanInput getRunningStatus() {
return runningCell;
}
/**
* Checks if the CollapsingWorkerThread's work is currently running.
*
* @return if the thread's work is in progress.
*/
public boolean isDoingWork() {
return getRunningStatus().get();
}
@Override
protected final void threadBody() throws InterruptedException {
while (true) {
synchronized (lockObject) {
while (!needsToRun) {
if (terminated) {
return;
}
lockObject.wait();
}
if (terminated) {
return;
}
needsToRun = false;
}
try {
runningCell.set(true);
try {
doWork();
} finally {
runningCell.set(false);
}
} catch (Throwable t) {
Logger.severe("Uncaught exception in worker thread: " + this.getName(), t);
}
if (shouldIgnoreWhileRunning) {
needsToRun = false;
}
}
}
/**
* The implementation of the work to do when this worker is triggered.
*
* @throws Throwable if any error occurs. this will not end the thread, but
* just the current execution of work.
*/
protected abstract void doWork() throws Throwable;
/**
* Terminate this thread. Don't expect anything to happen after this point.
*/
public void terminate() {
synchronized (lockObject) {
this.terminated = true;
lockObject.notifyAll();
this.interrupt();
}
}
}