/* * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com. * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package jace.core; import jace.config.ConfigurableField; /** * A timed device is a device which executes so many ticks in a given time * interval. This is the core of the emulator timing mechanics. * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public abstract class TimedDevice extends Device { /** * Creates a new instance of TimedDevice * @param computer */ public TimedDevice(Computer computer) { super(computer); setSpeed(cyclesPerSecond); } @ConfigurableField(name = "Speed", description = "(in hertz)") public long cyclesPerSecond = defaultCyclesPerSecond(); @ConfigurableField(name = "Max speed") public boolean maxspeed = false; @Override public abstract void tick(); private static final double NANOS_PER_SECOND = 1000000000.0; // current cycle within the period private int cycleTimer = 0; // The actual worker that the device runs as public Thread worker; public static int TEMP_SPEED_MAX_DURATION = 1000000; private int tempSpeedDuration = 0; public boolean hasStopped = true; @Override public boolean suspend() { disableTempMaxSpeed(); boolean result = super.suspend(); if (worker != null && worker.isAlive()) { try { worker.interrupt(); worker.join(1000); } catch (InterruptedException ex) { } } worker = null; return result; } Thread timerThread; public boolean pause() { if (!isRunning()) { return false; } isPaused = true; try { // KLUDGE: Sleeping to wait for worker thread to hit paused state. We might be inside the worker (?) Thread.sleep(10); } catch (InterruptedException ex) { } return true; } @Override public void resume() { super.resume(); isPaused = false; if (worker != null && worker.isAlive()) { return; } worker = new Thread(() -> { while (isRunning()) { hasStopped = false; doTick(); while (isPaused) { hasStopped = true; try { Thread.sleep(10); } catch (InterruptedException ex) { return; } } resync(); } hasStopped = true; }); worker.setDaemon(false); worker.setPriority(Thread.MAX_PRIORITY); worker.start(); worker.setName("Timed device " + getDeviceName() + " worker"); } long nanosPerInterval; // How long to wait between pauses long cyclesPerInterval; // How many cycles to wait until a pause interval long nextSync; // When was the last pause? public final void setSpeed(long cyclesPerSecond) { cyclesPerInterval = cyclesPerSecond / 100L; nanosPerInterval = (long) (cyclesPerInterval * NANOS_PER_SECOND / cyclesPerSecond); // System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles"); cycleTimer = 0; resetSyncTimer(); } long skip = 0; long wait = 0; public final void resetSyncTimer() { nextSync = System.nanoTime() + nanosPerInterval; cycleTimer = 0; } public void enableTempMaxSpeed() { tempSpeedDuration = TEMP_SPEED_MAX_DURATION; } public void disableTempMaxSpeed() { tempSpeedDuration = 0; resetSyncTimer(); } protected void resync() { if (++cycleTimer >= cyclesPerInterval) { if (maxspeed || tempSpeedDuration > 0) { if (tempSpeedDuration > 0) { tempSpeedDuration -= cyclesPerInterval; } resetSyncTimer(); return; } long now = System.nanoTime(); if (now < nextSync) { cycleTimer = 0; long currentSyncDiff = nextSync - now; // Don't bother resynchronizing unless we're off by 10ms if (currentSyncDiff > 10000000L) { try { // System.out.println("Sleeping for " + currentSyncDiff / 1000000 + " milliseconds"); Thread.sleep(currentSyncDiff / 1000000L, (int) (currentSyncDiff % 1000000L)); } catch (InterruptedException ex) { System.err.println(getDeviceName() + " was trying to sleep for " + (currentSyncDiff / 1000000) + " millis but was woken up"); // Logger.getLogger(TimedDevice.class.getName()).log(Level.SEVERE, null, ex); } } else { // System.out.println("Sleeping for " + currentSyncDiff + " nanoseconds"); // LockSupport.parkNanos(currentSyncDiff); } } nextSync += nanosPerInterval; } } @Override public void reconfigure() { if (cyclesPerSecond == 0) { cyclesPerSecond = defaultCyclesPerSecond(); } setSpeed(cyclesPerSecond); } public abstract long defaultCyclesPerSecond(); }