/*
 * JaamSim Discrete Event Simulation
 * Copyright (C) 2014 Ausenco Engineering Canada Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 */
package com.jaamsim.controllers;

import java.util.ArrayList;

/**
 * A class to limit updates to a set rate in real time
 * @author matt.chudleigh
 *
 */
public class RateLimiter implements Runnable {
	private final Thread refreshThread;

	private long lastCallbackTime = 0;
	private long scheduledTime = Long.MAX_VALUE;
	private final Object timingLock = new Object();
	private final double ups;
	private final long frameTime;
	private boolean running = true;

	private final ArrayList<Runnable> callbacks = new ArrayList<Runnable>();

	public static RateLimiter create(double updatesPerSecond) {
		RateLimiter ret = new RateLimiter(updatesPerSecond);
		ret.refreshThread.start();
		return ret;
	}

	private RateLimiter(double updatesPerSecond) {
		// Start the display timer
		ups = updatesPerSecond;
		frameTime = (long)(1000.0d / ups);

		refreshThread = new Thread(this, "RefreshThread");
		refreshThread.setDaemon(true);
	}

	@Override
	public void run() {
		synchronized(timingLock) {
			while(running) {
				// Is a redraw scheduled
				long currentTime = System.currentTimeMillis();
				if (scheduledTime > currentTime) {
					try {
						// We have a scheduled time, wait until then
						timingLock.wait(scheduledTime - currentTime);
					}
					catch(InterruptedException ex) {}
					continue;
				}

				lastCallbackTime = currentTime;
				scheduledTime = Long.MAX_VALUE;
				for (Runnable r : callbacks) {
					r.run();
				}
			}
		}
	}

	public void queueUpdate() {
		synchronized(timingLock) {
			// If we already are schedule to run, don't wake the thread
			if (scheduledTime != Long.MAX_VALUE)
				return;

			// Set a new target callback time based on the last time a callback was
			// actually done
			scheduledTime = lastCallbackTime + frameTime;
			timingLock.notify();
		}
	}

	public void registerCallback(Runnable r) {
		synchronized (timingLock) {
			callbacks.add(r);
		}
	}

	public void cancel() {
		synchronized (timingLock) {
			running = false;
		}
	}
}

@@ -656,16 +656,63 @@
 rayList;
+%0Aimport java.util.concurrent.atomic.AtomicLong;
 %0A%0A/**%0A *
@@ -923,25 +923,37 @@
 0;%0A%09private 
-l
+final AtomicL
 ong schedule
@@ -945,35 +945,46 @@
 icLong sched
-uled
 Time = 
+new AtomicLong(
 Long.MAX_VAL
@@ -985,16 +985,17 @@
 AX_VALUE
+)
 ;%0A%09priva
@@ -1767,39 +1767,79 @@
 %09%09%09%09
-if (scheduledTime %3E currentTime
+long waitLength = schedTime.get() - currentTime;%0A%09%09%09%09if (waitLength %3E 0
 ) %7B%0A
@@ -1926,35 +1926,18 @@
 ait(
-scheduledTime - currentTime
+waitLength
 );%0A%09
@@ -2048,27 +2048,25 @@
 %09%09%09sched
-uledTime = 
+Time.set(
 Long.MAX
@@ -2071,16 +2071,17 @@
 AX_VALUE
+)
 ;%0A%09%09%09%09fo
@@ -2175,38 +2175,8 @@
 ) %7B%0A
-%09%09synchronized(timingLock) %7B%0A%09
 %09%09//
@@ -2235,17 +2235,16 @@
 hread%0A%09%09
-%09
 if (sche
@@ -2244,24 +2244,26 @@
 f (sched
-uled
 Time
+.get()
  != Long
@@ -2277,17 +2277,16 @@
 LUE)%0A%09%09%09
-%09
 return;%0A
@@ -2286,16 +2286,45 @@
 eturn;%0A%0A
+%09%09synchronized(timingLock) %7B%0A
 %09%09%09// Se
@@ -2422,19 +2422,17 @@
 ched
-uledTime = 
+Time.set(
 last
@@ -2451,24 +2451,25 @@
  + frameTime
+)
 ;%0A%09%09%09timingL

move testing the RateLimiter wake condition outside the timingLock