package de.take_weiland.mods.commons.util;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.AbstractListeningExecutorService;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ITickHandler;
import cpw.mods.fml.common.LoaderState;
import cpw.mods.fml.common.TickType;
import cpw.mods.fml.common.registry.TickRegistry;
import cpw.mods.fml.relauncher.Side;
import de.take_weiland.mods.commons.internal.SevenCommons;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
/**
* <p>A thread-safe implementation of {@link com.google.common.util.concurrent.ListeningExecutorService} that uses the
* main Minecraft thread to execute tasks.</p>
* <p>Limited scheduling is available via the {@link #schedule(Runnable, long)} method.</p>
* <p>This ExecutorService cannot be shut down or terminated.</p>
*
* @author diesieben07
*/
@ParametersAreNonnullByDefault
public final class Scheduler extends AbstractListeningExecutorService {
private static final Scheduler server;
private static final Scheduler client;
/**
* <p>Return a Scheduler that executes tasks on the main server thread.</p>
* @return a Scheduler
*/
public static Scheduler server() {
return server;
}
/**
* <p>The Scheduler that executes tasks on the main client thread. On a dedicated server this method will return null.</p>
* @return a Scheduler or null
*/
public static Scheduler client() {
return client;
}
/**
* <p>Return {@link #client()} if {@code side} is {@code Side.CLIENT}, {@link #server()} otherwise.</p>
* @param side the side
* @return a Scheduler for the side
*/
public static Scheduler forSide(Side side) {
return side.isClient() ? client : server;
}
@Override
public void execute(Runnable task) {
schedule(task, 0);
}
/**
* <p>Execute the given task after {@code tickDelay} ticks have passed.</p>
* @param task the task
* @param tickDelay the delay, in ticks
*/
public void schedule(Runnable task, long tickDelay) {
checkArgument(tickDelay >= 0);
synchronized (ticker.queue) {
ticker.queue.add(new Task(task, tickDelay + 1));
}
}
static {
if (FMLCommonHandler.instance().getSide().isClient()) {
client = new Scheduler(Side.CLIENT);
} else {
client = null;
}
server = new Scheduler(Side.SERVER);
SevenCommons.registerStateCallback(LoaderState.ModState.PREINITIALIZED, new Runnable() {
@Override
public void run() {
if (client != null) {
client.registerTickHandler(Side.CLIENT);
}
server.registerTickHandler(Side.SERVER);
}
});
}
private final Ticker ticker;
private Scheduler(Side side) {
this.ticker = new Ticker(side);
}
private void registerTickHandler(Side side) {
TickRegistry.registerTickHandler(ticker, side);
}
static final class Task {
final Runnable r;
long ticks;
Task(Runnable r, long ticks) {
this.r = r;
this.ticks = ticks;
}
}
static final class Ticker implements ITickHandler {
final List<Task> queue = new ArrayList<>();
final List<Task> scheduledNow = new ArrayList<>();
private final EnumSet<TickType> tickTypes;
Ticker(Side side) {
tickTypes = EnumSet.of(side.isClient() ? TickType.CLIENT : TickType.SERVER);
}
@Override
public void tickStart(EnumSet<TickType> type, Object... tickData) {
List<Task> scheduledNow = this.scheduledNow;
List<Task> queue = this.queue;
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (queue) {
int idx = queue.size();
while (--idx >= 0) {
Task task = queue.get(idx);
if (--task.ticks == 0) {
queue.remove(idx);
scheduledNow.add(task);
}
}
}
int idx = scheduledNow.size();
while (--idx >= 0) {
scheduledNow.remove(idx).r.run();
}
}
@Override
public void tickEnd(EnumSet<TickType> type, Object... tickData) { }
@Override
public EnumSet<TickType> ticks() {
return tickTypes;
}
@Override
public String getLabel() {
return "SC|Scheduler";
}
}
/**
* @deprecated always false, this ExecutorService cannot be shut down
* @return
*/
@Override
@Deprecated
public boolean isShutdown() {
return false;
}
/**
* @deprecated always false, this ExecutorService cannot be shut down
* @return
*/
@Override
@Deprecated
public boolean isTerminated() {
return false;
}
/**
* @deprecated this ExecutorService cannot be shut down
*/
@Override
@Deprecated
public void shutdown() { }
/**
* @deprecated this ExecutorService cannot be shut down
* @return
*/
@Nonnull
@Override
@Deprecated
public List<Runnable> shutdownNow() {
List<Runnable> result = new ArrayList<>();
synchronized (ticker.queue) {
for (Task task : ticker.queue) {
result.add(task.r);
}
}
return result;
}
/**
* @deprecated this ExecutorService cannot be shut down, always returns false after sleeping for the specified
* amount of time
* @param timeout the timeout
* @param unit TimeUnit
* @return always false
* @throws InterruptedException
*/
@Override
@Deprecated
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
long millis = unit.toMillis(timeout);
long milliNanos = TimeUnit.MILLISECONDS.toNanos(millis);
int additionalNanos = Ints.saturatedCast(unit.toNanos(timeout) - milliNanos);
Thread.sleep(millis, additionalNanos);
return false;
}
}