/******************************************************************************* * Copyright (c) 2015 - 2017 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.graphics.action; import jsettlers.common.menu.action.IAction; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.LinkedBlockingQueue; /** * This class lets you schedule the firing of actions in a separate thread. * * @author michael */ public class ActionFirerer implements ActionFireable { /** * How long the action thread may lack behind. */ private static final long ACTION_FIRERER_TIMEOUT = 1000; private final Thread thread; /** * The actions that are queued to fire. */ private final LinkedBlockingQueue<FireringAction> toFire = new LinkedBlockingQueue<FireringAction>(); private final Object toFireMutex = new Object(); /** * The object we should fire the actions to. */ private final ActionFireable fireTo; /** * A listener that listens if this action firerer is slow */ private ActionThreadBlockingListener blockingListener; /** * A flag indicating if we notified the {@link #blockingListener} that we are blocking. */ private boolean isBlockingSent; /** * Mutex for {@link #blockingListener} and {@link #isBlockingSent} */ private final Object blockingListenerMutex = new Object(); /** * The timer that watches for logic freezes. */ private final Timer watchdogTimer = new Timer("action firerer timer"); /** * The timer task that is currently active for the {@link #watchdogTimer}. */ private TimerTask watchdogTimerTask; /** * If we were stopped yet. */ private boolean stopped = false; /** * Creates a new action firerer and starts it. * * @param fireTo * The object we should fire to. */ public ActionFirerer(ActionFireable fireTo) { this.fireTo = fireTo; this.thread = new ActionFirererThread(); this.thread.setDaemon(true); this.thread.start(); } private class ActionFirererThread extends Thread { public ActionFirererThread() { super("action firerer"); } @Override public void run() { FireringAction action; while (!stopped) { try { synchronized (toFireMutex) { while (toFire.isEmpty() && !stopped) { toFireMutex.wait(); } if (stopped) { break; } action = toFire.poll(); } startWatchdog(action.startTime); fireTo.fireAction(action.action); stopWatchdog(); } catch (Throwable e) { System.err.println("Exception while handling action:"); e.printStackTrace(); if (blockingListener != null) { blockingListener.actionThreadCaughtException(e); } } if (toFire.isEmpty()) { disableWatchdog(); } } } } /** * An action in the queue. * * @author michael */ private static class FireringAction { private final long startTime; private final IAction action; FireringAction(IAction action, long startTime) { this.action = action; this.startTime = startTime; } } /** * Sets the listener to be notified on blocking state changes. * * @param listener */ public void setBlockingListener(ActionThreadBlockingListener listener) { synchronized (blockingListenerMutex) { this.blockingListener = listener; } } public void stopWatchdog() { if (watchdogTimerTask != null) { watchdogTimerTask.cancel(); } } protected void startWatchdog(long startTime) { long destTime = startTime + ACTION_FIRERER_TIMEOUT; long timeUntilFreezeState = System.currentTimeMillis() - destTime; if (timeUntilFreezeState <= 0) { sendIsBlocking(true); } else { watchdogTimerTask = new TimerTask() { @Override public void run() { sendIsBlocking(true); } }; watchdogTimer.schedule(watchdogTimerTask, timeUntilFreezeState); } } protected void disableWatchdog() { sendIsBlocking(false); } private void sendIsBlocking(boolean blocking) { synchronized (blockingListenerMutex) { if (isBlockingSent != blocking && blockingListener != null) { blockingListener.actionThreadSlow(blocking); } isBlockingSent = blocking; } } /** * Schedules an action to be fired. */ @Override public void fireAction(IAction action) { synchronized (toFireMutex) { toFire.offer(new FireringAction(action, System.currentTimeMillis())); toFireMutex.notifyAll(); } } /** * Stops this action firerer. The queue is not emptied by this operation. */ public void stop() { synchronized (toFireMutex) { stopped = true; toFireMutex.notifyAll(); } watchdogTimer.cancel(); } }