/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
******************************************************************************/
package com.badlogic.gdx.utils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.MathUtils;
/**
* Executes tasks in the future on the main loop thread.
*
* @author Nathan Sweet
*/
public class Timer {
static final Array<Timer> instances = new Array(1);
static {
Thread thread = new Thread("Timer") {
public void run() {
while (true) {
synchronized (instances) {
float time = System.nanoTime() * MathUtils.nanoToSec;
float wait = Float.MAX_VALUE;
for (int i = 0, n = instances.size; i < n; i++)
wait = Math.min(wait, instances.get(i).update(time));
long waitMillis = (long) (wait * 1000);
try {
if (waitMillis > 0)
instances.wait(waitMillis);
} catch (InterruptedException ignored) {
}
}
}
}
};
thread.setDaemon(true);
thread.start();
}
/**
* Timer instance for general application wide usage. Static methods on {@link Timer} make convenient use of this
* instance.
*/
static public final Timer instance = new Timer();
static private final int CANCELLED = -1;
static private final int FOREVER = -2;
private final Array<Task> tasks = new Array(false, 8);
public Timer() {
start();
}
/** Schedules a task to occur once as soon as possible, but not sooner than the start of the next frame. */
public void postTask(Task task) {
scheduleTask(task, 0, 0, 0);
}
/** Schedules a task to occur once after the specified delay. */
public void scheduleTask(Task task, float delaySeconds) {
scheduleTask(task, delaySeconds, 0, 0);
}
/**
* Schedules a task to occur once after the specified delay and then repeatedly at the specified interval until
* cancelled.
*/
public void scheduleTask(Task task, float delaySeconds, float intervalSeconds) {
scheduleTask(task, delaySeconds, intervalSeconds, FOREVER);
}
/**
* Schedules a task to occur once after the specified delay and then a number of additional times at the specified
* interval.
*/
public void scheduleTask(Task task, float delaySeconds, float intervalSeconds, int repeatCount) {
if (task.repeatCount != CANCELLED)
throw new IllegalArgumentException("The same task may not be scheduled twice.");
task.executeTime = System.nanoTime() * MathUtils.nanoToSec + delaySeconds;
task.intervalSeconds = intervalSeconds;
task.repeatCount = repeatCount;
synchronized (tasks) {
tasks.add(task);
}
wake();
}
/** Stops the timer, tasks will not be executed and time that passes will not be applied to the task delays. */
public void stop() {
synchronized (instances) {
instances.removeValue(this, true);
}
}
/** Starts the timer if it was stopped. */
public void start() {
synchronized (instances) {
if (instances.contains(this, true))
return;
instances.add(this);
wake();
}
}
/** Cancels all tasks. */
public void clear() {
synchronized (tasks) {
for (int i = 0, n = tasks.size; i < n; i++)
tasks.get(i).cancel();
tasks.clear();
}
}
float update(float time) {
float wait = Float.MAX_VALUE;
synchronized (tasks) {
for (int i = 0, n = tasks.size; i < n; i++) {
Task task = tasks.get(i);
if (task.executeTime > time) {
wait = Math.min(wait, task.executeTime - time);
continue;
}
if (task.repeatCount != CANCELLED) {
if (task.repeatCount == 0)
task.repeatCount = CANCELLED; // Set cancelled before run so it may be rescheduled in run.
Gdx.app.postRunnable(task);
}
if (task.repeatCount == CANCELLED) {
tasks.removeIndex(i);
i--;
n--;
} else {
task.executeTime = time + task.intervalSeconds;
wait = Math.min(wait, task.executeTime - time);
if (task.repeatCount > 0)
task.repeatCount--;
}
}
}
return wait;
}
static private void wake() {
synchronized (instances) {
instances.notifyAll();
}
}
/**
* Schedules a task on {@link #instance}.
*
* @see #postTask(Task)
*/
static public void post(Task task) {
instance.postTask(task);
}
/**
* Schedules a task on {@link #instance}.
*
* @see #scheduleTask(Task, float)
*/
static public void schedule(Task task, float delaySeconds) {
instance.scheduleTask(task, delaySeconds);
}
/**
* Schedules a task on {@link #instance}.
*
* @see #scheduleTask(Task, float, float)
*/
static public void schedule(Task task, float delaySeconds, float intervalSeconds) {
instance.scheduleTask(task, delaySeconds, intervalSeconds);
}
/**
* Schedules a task on {@link #instance}.
*
* @see #scheduleTask(Task, float, float, int)
*/
static public void schedule(Task task, float delaySeconds, float intervalSeconds, int repeatCount) {
instance.scheduleTask(task, delaySeconds, intervalSeconds, repeatCount);
}
/**
* Runnable with a cancel method.
*
* @see Timer
* @author Nathan Sweet
*/
static abstract public class Task implements Runnable {
float executeTime;
float intervalSeconds;
int repeatCount = CANCELLED;
/**
* If this is the last time the task will be ran or the task is first cancelled, it may be scheduled again in
* this method.
*/
abstract public void run();
/**
* Cancels the task. It will not be executed until it is scheduled again. This method can be called at any time.
*/
public void cancel() {
executeTime = 0;
repeatCount = CANCELLED;
}
/** Returns true if this task is scheduled to be executed in the future by a timer. */
public boolean isScheduled() {
return repeatCount != CANCELLED;
}
}
}