diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 5fcff48f88..887a68dd6a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -17,6 +17,7 @@ import java.net.UnknownHostException; import joptsimple.OptionSet; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.scheduler.CraftScheduler; // CraftBukkit end public class MinecraftServer implements ICommandListener, Runnable { @@ -275,6 +276,10 @@ public class MinecraftServer implements ICommandListener, Runnable { this.e.f(); + // CraftBukkit start + ((CraftScheduler) server.getScheduler()).mainThreadHeartbeat(this.h); + // CraftBukkit end + while (this.e.d()) { ; } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 3ebb579c9c..c2650addc0 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -16,12 +16,15 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.SimplePluginManager; import org.bukkit.plugin.java.JavaPluginLoader; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.craftbukkit.scheduler.CraftScheduler; public final class CraftServer implements Server { private final String serverName = "Craftbukkit"; private final String serverVersion; private final String protocolVersion = "1.2_01"; private final PluginManager pluginManager = new SimplePluginManager(this); + private final BukkitScheduler scheduler = new CraftScheduler(this); private final CommandMap commandMap = new SimpleCommandMap(this); protected final MinecraftServer console; protected final ServerConfigurationManager server; @@ -145,6 +148,10 @@ public final class CraftServer implements Server { return pluginManager; } + public BukkitScheduler getScheduler() { + return scheduler; + } + public World[] getWorlds() { return new World[]{console.e.getWorld()}; } diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java new file mode 100644 index 0000000000..d50a036e63 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -0,0 +1,209 @@ +package org.bukkit.craftbukkit.scheduler; + +import java.util.LinkedList; +import java.util.TreeMap; +import java.util.Iterator; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.plugin.Plugin; + +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.scheduler.CraftTask; + +public class CraftScheduler implements BukkitScheduler, Runnable { + + private final CraftServer server; + + private final CraftThreadManager craftThreadManager = new CraftThreadManager(); + + private final LinkedList mainThreadQueue = new LinkedList(); + + private final TreeMap schedulerQueue = new TreeMap(); + + private final Object currentTickSync = new Object(); + private Long currentTick = 0L; + + // This lock locks the mainThreadQueue and the currentTick value + private final Lock mainThreadLock = new ReentrantLock(); + + public void run() { + + while (true) { + boolean stop = false; + long firstTick = -1; + long currentTick = -1; + CraftTask first = null; + do { + synchronized (schedulerQueue) { + first = null; + if (!schedulerQueue.isEmpty()) { + first = schedulerQueue.firstKey(); + if (first!=null) { + currentTick = getCurrentTick(); + + firstTick = first.getExecutionTick(); + + if (currentTick >= firstTick ) { + schedulerQueue.remove(first); + processTask(first); + if (first.getPeriod()>=0) { + first.updateExecution(); + schedulerQueue.put(first, first.isSync()); + } + } else { + stop = true; + } + } else { + stop = true; + } + } else { + stop = true; + } + } + } while (!stop); + + long sleepTime = 0; + if (first == null) { + sleepTime = 60000L; + } else { + currentTick = getCurrentTick(); + sleepTime = (firstTick-currentTick)*50 + 25; + } + + if (sleepTime < 50L) { + sleepTime = 50L; + } else if (sleepTime > 60000L) { + sleepTime = 60000L; + } + + synchronized (schedulerQueue) { + try { + schedulerQueue.wait(sleepTime); + } catch (InterruptedException ie) { + } + } + } + } + + void processTask(CraftTask task) { + if (task.isSync()) { + addToMainThreadQueue(task.getTask()); + } else { + craftThreadManager.executeTask(task.getTask(), task.getOwner(), task.getIdNumber()); + } + } + + public CraftScheduler(CraftServer server) { + this.server = server; + + Thread t = new Thread(this); + t.start(); + + } + + + // If the main thread cannot obtain the lock, it doesn't wait + public void mainThreadHeartbeat(long currentTick) { + if (mainThreadLock.tryLock()) { + try { + this.currentTick = currentTick; + while (!mainThreadQueue.isEmpty()) { + mainThreadQueue.removeFirst().run(); + } + } finally { + mainThreadLock.unlock(); + } + } + } + + long getCurrentTick() { + mainThreadLock.lock(); + long tempTick = 0; + try { + tempTick = currentTick; + } finally { + mainThreadLock.unlock(); + } + return tempTick; + } + + void addToMainThreadQueue(Runnable task) { + mainThreadLock.lock(); + try { + mainThreadQueue.addLast(task); + } finally { + mainThreadLock.unlock(); + } + } + + public int scheduleSyncDelayedTask(Plugin plugin, Runnable task, long delay) { + return scheduleSyncRepeatingTask(plugin, task, delay, -1); + } + + public int scheduleSyncDelayedTask(Plugin plugin, Runnable task) { + return scheduleSyncDelayedTask(plugin, task, 0L); + } + + public int scheduleSyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) { + CraftTask newTask = new CraftTask(plugin, task, true, getCurrentTick()+delay, period); + synchronized (schedulerQueue) { + schedulerQueue.put(newTask, true); + schedulerQueue.notify(); + } + return newTask.getIdNumber(); + } + + public int scheduleAsyncDelayedTask(Plugin plugin, Runnable task, long delay) { + return scheduleAsyncRepeatingTask(plugin, task, delay, -1); + } + + public int scheduleAsyncDelayedTask(Plugin plugin, Runnable task) { + return scheduleAsyncDelayedTask(plugin, task, 0L); + } + + public int scheduleAsyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) { + CraftTask newTask = new CraftTask(plugin, task, false, getCurrentTick()+delay, period); + synchronized (schedulerQueue) { + schedulerQueue.put(newTask, false); + schedulerQueue.notify(); + } + return newTask.getIdNumber(); + } + + public void cancelTask(int taskId) { + synchronized (schedulerQueue) { + Iterator itr = schedulerQueue.keySet().iterator(); + while (itr.hasNext()) { + CraftTask current = itr.next(); + if (current.getIdNumber() == taskId) { + itr.remove(); + } + } + } + craftThreadManager.interruptTask(taskId); + } + + public void cancelTasks(Plugin plugin) { + synchronized (schedulerQueue) { + Iterator itr = schedulerQueue.keySet().iterator(); + while (itr.hasNext()) { + CraftTask current = itr.next(); + if (current.getOwner().equals(plugin)) { + itr.remove(); + } + } + } + craftThreadManager.interruptTask(plugin); + } + + public void cancelAllTasks() { + synchronized (schedulerQueue) { + schedulerQueue.clear(); + } + craftThreadManager.interruptAllTasks(); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java new file mode 100644 index 0000000000..2b47cc47fd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -0,0 +1,110 @@ +package org.bukkit.craftbukkit.scheduler; + +import java.lang.Comparable; + +import org.bukkit.plugin.Plugin; + +public class CraftTask implements Comparable { + + private final Runnable task; + private final boolean syncTask; + private long executionTick; + private final long period; + private final Plugin owner; + private final int idNumber; + + private static Integer idCounter = 1; + private static Object idCounterSync = new Object(); + + CraftTask(Plugin owner, Runnable task, boolean syncTask) { + this(owner, task, syncTask, -1, -1); + } + + CraftTask(Plugin owner, Runnable task, boolean syncTask, long executionTick) { + this(owner, task, syncTask, executionTick, -1); + } + + CraftTask(Plugin owner, Runnable task, boolean syncTask, long executionTick, long period) { + this.task = task; + this.syncTask = syncTask; + this.executionTick = executionTick; + this.period = period; + this.owner = owner; + this.idNumber = CraftTask.getNextId(); + } + + static int getNextId() { + synchronized (idCounterSync) { + idCounter++; + return idCounter; + } + } + + Runnable getTask() { + return task; + } + + boolean isSync() { + return syncTask; + } + + long getExecutionTick() { + return executionTick; + } + + long getPeriod() { + return period; + } + + Plugin getOwner() { + return owner; + } + + void updateExecution() { + executionTick += period; + } + + int getIdNumber() { + return idNumber; + } + + @Override + public int compareTo(Object other) { + if (!(other instanceof CraftTask)) { + return 0; + } else { + CraftTask o = (CraftTask) other; + long timeDiff = executionTick - o.getExecutionTick(); + if (timeDiff>0) { + return 1; + } else if (timeDiff<0) { + return -1; + } else { + CraftTask otherCraftTask = (CraftTask) other; + return getIdNumber() - otherCraftTask.getIdNumber(); + } + } + } + + @Override + public boolean equals( Object other ) { + + if (other == null) { + return false; + } + + if (!(other instanceof CraftTask)) { + return false; + } + + CraftTask otherCraftTask = (CraftTask) other; + return otherCraftTask.getIdNumber() == getIdNumber(); + } + + @Override + public int hashCode() { + return getIdNumber(); + } + + +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftThreadManager.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftThreadManager.java new file mode 100644 index 0000000000..8ded4984c5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftThreadManager.java @@ -0,0 +1,55 @@ +package org.bukkit.craftbukkit.scheduler; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Iterator; + +import org.bukkit.plugin.Plugin; + +public class CraftThreadManager { + + final HashSet workers = new HashSet(); + + void executeTask(Runnable task, Plugin owner, int taskId) { + + CraftWorker craftWorker = new CraftWorker(this, task, owner, taskId); + synchronized (workers) { + workers.add(craftWorker); + } + + } + + void interruptTask(int taskId) { + synchronized (workers) { + Iterator itr = workers.iterator(); + while (itr.hasNext()) { + CraftWorker craftWorker = itr.next(); + if (craftWorker.getTaskId() == taskId) { + craftWorker.interrupt(); + } + } + } + } + + void interruptTask(Plugin owner) { + synchronized (workers) { + Iterator itr = workers.iterator(); + while (itr.hasNext()) { + CraftWorker craftWorker = itr.next(); + if (craftWorker.getOwner().equals(owner)) { + craftWorker.interrupt(); + } + } + } + } + + void interruptAllTasks() { + synchronized (workers) { + Iterator itr = workers.iterator(); + while (itr.hasNext()) { + CraftWorker craftWorker = itr.next(); + craftWorker.interrupt(); + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftWorker.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftWorker.java new file mode 100644 index 0000000000..2bb9400dfe --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftWorker.java @@ -0,0 +1,82 @@ +package org.bukkit.craftbukkit.scheduler; + +import org.bukkit.plugin.Plugin; + +public class CraftWorker implements Runnable { + + private static int hashIdCounter = 1; + private static Object hashIdCounterSync = new Object(); + + private final int hashId; + + private final Plugin owner; + private final int taskId; + + private final Thread t; + private final CraftThreadManager parent; + + private final Runnable task; + + CraftWorker(CraftThreadManager parent, Runnable task, Plugin owner, int taskId) { + this.parent = parent; + this.taskId = taskId; + this.task = task; + this.owner = owner; + this.hashId = CraftWorker.getNextHashId(); + t = new Thread(this); + t.start(); + } + + public void run() { + + try { + task.run(); + } catch (Exception e) { + e.printStackTrace(); + } + + synchronized (parent.workers) { + parent.workers.remove(this); + } + + } + + public int getTaskId() { + return taskId; + } + + public Plugin getOwner() { + return owner; + } + + public void interrupt() { + t.interrupt(); + } + + private static int getNextHashId() { + synchronized (hashIdCounterSync) { + return hashIdCounter++; + } + } + + @Override + public int hashCode() { + return hashId; + } + + @Override + public boolean equals( Object other ) { + + if (other == null) { + return false; + } + + if (!(other instanceof CraftWorker)) { + return false; + } + + CraftWorker otherCraftWorker = (CraftWorker) other; + return otherCraftWorker.hashCode() == hashId; + } + +}