/*
* This file is part of AoW (On Steroids), licensed under the Apache 2.0 License.
*
* Copyright (c) 2014 Agustin Alvarez <wolftein1@gmail.com>
*
* 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 me.wolftein.steroid.framework.scheduler;
import java.util.ArrayDeque;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* Encapsulate the main thread of the framework.
*/
public final class Scheduler implements TaskExecutor {
/**
* Define how many milliseconds are in a second.
*/
protected final static long SECOND_AS_MILLISECOND = 1000L;
protected final Executor mExecutor = Executors.newWorkStealingPool();
protected final Queue<Task> mQueue = new PriorityQueue<>();
protected final Queue<Task> mDirtyQueue = new ArrayDeque<>();
protected final AtomicBoolean mActive = new AtomicBoolean(false);
protected final AtomicBoolean mOverloaded = new AtomicBoolean(false);
protected final long mStartTime = System.currentTimeMillis();
protected final long mDesiredTicks;
protected long mLoopTickTime, mLoopFrameTime;
/**
* Default constructor for {@link Scheduler}.
*/
public Scheduler(long desiredTicks) {
this.mDesiredTicks = desiredTicks;
}
/**
* {@inheritDoc}
*/
@Override
public Task invoke(Consumer<Task> consumer, TaskPriority priority, long delay, long period, boolean isAsync) {
final Task task
= new Task(consumer, priority, isAsync, System.currentTimeMillis() - mStartTime + delay, period);
mDirtyQueue.add(task);
return task;
}
/**
* Starts the scheduler
*/
public void start() {
if (mActive.get()) {
throw new IllegalStateException("Scheduler has been already started.");
}
mActive.set(true);
final Queue<Task> defQueue = new ArrayDeque<>();
mLoopFrameTime = System.currentTimeMillis();
do {
// Add all task that has been added into the executor with
while (!mDirtyQueue.isEmpty()) {
final Task task = mDirtyQueue.poll();
if (task != null) {
mQueue.add(task);
}
}
// Deferred all tasks that needs to be executed in asynchronous channel
// or the synchronous channel.
while (!mQueue.isEmpty()) {
final Task task = mQueue.peek();
if (task.getTime() > System.currentTimeMillis() - mStartTime) {
break;
}
if (task.isAlive()) {
if (task.isAsynchronous()) {
mExecutor.execute(() -> executeTaskIfNotDestroyOrRepeat(task));
} else {
defQueue.add(task);
}
}
mQueue.poll();
}
// Run all tasks deferred to the synchronous channel.
while (!defQueue.isEmpty()) {
executeTaskIfNotDestroyOrRepeat(defQueue.poll());
}
final long current = System.currentTimeMillis();
if (current - mLoopFrameTime >= SECOND_AS_MILLISECOND) {
mLoopFrameTime = current;
mLoopTickTime = 0;
mOverloaded.set(mLoopTickTime < mDesiredTicks);
} else {
mLoopTickTime++;
}
} while (mActive.get());
// Remove all references to the old task to ensure GC collect them when
// the executor has been stopped.
mQueue.clear();
mDirtyQueue.clear();
}
/**
* Stops the scheduler.
*/
public void stop() {
if (!mActive.getAndSet(false)) {
throw new IllegalStateException("Executor has not been started.");
}
}
/**
* Cancel all tasks.
*/
public void cancelAllTasks() {
mQueue.forEach(Task::cancel);
mDirtyQueue.clear();
}
/**
* Check if the executor is active.
*/
public boolean isActive() {
return mActive.get();
}
/**
* Check if the executor is overloaded.
*/
public boolean isOverloaded() {
return mActive.get();
}
/**
* Execute a task.
*/
private void executeTaskIfNotDestroyOrRepeat(Task task) {
try {
task.execute();
} catch (Exception exception) {
exception.printStackTrace();
}
if (task.isRepeating() && task.isAlive()) {
invoke(task.getConsumer(),
task.getPriority(),
task.getPeriod(),
task.getPeriod(),
task.isAsynchronous());
}
}
}