/* * Copyright 2015-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.time; import java.util.PriorityQueue; import ccre.channel.EventOutput; import ccre.log.Logger; import ccre.verifier.FlowPhase; /** * A "fake" implementation of time, in which the current time is controlled by * the {@link #forward(long)} method of this instance. * * WARNING: this contains complex and likely slightly broken synchronization * code. Do not use it in production! * * A time may be specified so that transitioning between fake time and real time * can be made seamless to the users. * * @author skeggsc */ public class FakeTime extends Time { private volatile long now = 0; /** * Fast-forward time by the specified number of milliseconds, including * waiting to attempt to synchronize threads. * * @param millis how far forward to send time. * @throws InterruptedException if the thread is interrupted while * synchronizing with other threads. */ public void forward(long millis) throws InterruptedException { if (closing) { throw new IllegalStateException("The FakeTime is closing! Don't try to control it!"); } if (millis <= 0) { throw new IllegalArgumentException("FakeTime.forward expects a positive delta!"); } synchronized (this) { now += millis; this.notifyAll(); } while (true) { Entry ent; synchronized (this) { if (queue.isEmpty() || queue.peek().time > nowNanos()) { break; } ent = queue.remove(); } try { ent.target.event(); } catch (Throwable thr) { Logger.severe("Top-level failure in scheduled event", thr); } } } private static class Entry implements Comparable<Entry> { public final EventOutput target; public final long time; @FlowPhase public Entry(EventOutput target, long time) { this.target = target; this.time = time; } @Override public int compareTo(Entry o) { return Long.compare(time, o.time); } } private final PriorityQueue<Entry> queue = new PriorityQueue<>(1024); @FlowPhase void schedule(EventOutput event, long time) { synchronized (FakeTime.this) { queue.add(new Entry(event, time)); } } @Override protected synchronized long nowMillis() { return now; } @Override protected synchronized long nowNanos() { return now * 1000000; } @Override protected synchronized void sleepFor(long millis) throws InterruptedException { if (closing) { throw new IllegalStateException("This FakeTime instance is shutting down! Don't try to wait on it!"); } long target = now + millis; while (now < target) { if (closing) { throw new RuntimeException("Time provider started closing during sleep!"); } this.wait(); } } @Override protected void waitOn(Object object, long timeout) throws InterruptedException { if (closing) { throw new IllegalStateException("This FakeTime instance is shutting down! Don't try to wait on it!"); } if (object == null) { throw new NullPointerException(); } if (!Thread.holdsLock(object)) { throw new IllegalMonitorStateException("Thread does not hold lock for object!"); } if (timeout == 0) { object.wait(); return; } else if (timeout < 0) { throw new IllegalArgumentException("Negative wait time!"); } schedule(() -> { synchronized (object) { object.notifyAll(); } }, timeout); // TODO: recomment this object.wait(1000); } private boolean closing = false; @Override protected void close() { synchronized (this) { closing = true; } synchronized (this) { now = 0; closing = false; // just in case something's still waiting on us this.notifyAll(); } } }