Limbo Schedulers

This commit is contained in:
LOOHP 2021-02-22 22:26:14 +08:00
parent 2a0839963a
commit 25afa65ad9
13 changed files with 523 additions and 75 deletions

View File

@ -11,7 +11,6 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;

View File

@ -71,8 +71,10 @@ public class GUI extends JFrame {
addWindowListener(new WindowAdapter() { addWindowListener(new WindowAdapter() {
@Override @Override
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
if (Limbo.getInstance().isRunning()) {
Limbo.getInstance().stopServer(); Limbo.getInstance().stopServer();
} }
}
}); });
setBounds(100, 100, 1198, 686); setBounds(100, 100, 1198, 686);
contentPane = new JPanel(); contentPane = new JPanel();

View File

@ -49,6 +49,8 @@ import com.loohp.limbo.Permissions.PermissionsManager;
import com.loohp.limbo.Player.Player; import com.loohp.limbo.Player.Player;
import com.loohp.limbo.Plugins.LimboPlugin; import com.loohp.limbo.Plugins.LimboPlugin;
import com.loohp.limbo.Plugins.PluginManager; import com.loohp.limbo.Plugins.PluginManager;
import com.loohp.limbo.Scheduler.LimboScheduler;
import com.loohp.limbo.Scheduler.Tick;
import com.loohp.limbo.Server.ServerConnection; import com.loohp.limbo.Server.ServerConnection;
import com.loohp.limbo.Server.Packets.Packet; import com.loohp.limbo.Server.Packets.Packet;
import com.loohp.limbo.Server.Packets.PacketIn; import com.loohp.limbo.Server.Packets.PacketIn;
@ -130,8 +132,8 @@ public class Limbo {
private DimensionRegistry dimensionRegistry; private DimensionRegistry dimensionRegistry;
@SuppressWarnings("unused")
private Tick tick; private Tick tick;
private LimboScheduler scheduler;
private Metrics metrics; private Metrics metrics;
@ -281,6 +283,9 @@ public class Limbo {
} }
} }
scheduler = new LimboScheduler();
tick = new Tick(this);
permissionManager = new PermissionsManager(); permissionManager = new PermissionsManager();
permissionManager.loadDefaultPermissionFile(permissionFile); permissionManager.loadDefaultPermissionFile(permissionFile);
@ -314,8 +319,6 @@ public class Limbo {
server = new ServerConnection(properties.getServerIp(), properties.getServerPort()); server = new ServerConnection(properties.getServerIp(), properties.getServerPort());
tick = new Tick(this);
metrics = new Metrics(); metrics = new Metrics();
console.run(); console.run();
@ -326,6 +329,14 @@ public class Limbo {
return unsafe; return unsafe;
} }
public Tick getHeartBeat() {
return tick;
}
public LimboScheduler getScheduler() {
return scheduler;
}
public DimensionRegistry getDimensionRegistry() { public DimensionRegistry getDimensionRegistry() {
return dimensionRegistry; return dimensionRegistry;
} }
@ -494,6 +505,8 @@ public class Limbo {
plugin.onDisable(); plugin.onDisable();
} }
tick.waitAndKillThreads(5000);
for (Player player : getPlayers()) { for (Player player : getPlayers()) {
player.disconnect("Server closed"); player.disconnect("Server closed");
} }

View File

@ -10,11 +10,17 @@ public class LimboPlugin {
private String name; private String name;
private File dataFolder; private File dataFolder;
private PluginInfo info; private PluginInfo info;
private File pluginJar;
protected final void setInfo(FileConfiguration file) { protected final void setInfo(FileConfiguration file, File pluginJar) {
info = new PluginInfo(file); this.info = new PluginInfo(file);
name = info.getName(); this.name = info.getName();
dataFolder = new File(Limbo.getInstance().getPluginFolder(), name); this.dataFolder = new File(Limbo.getInstance().getPluginFolder(), name);
this.pluginJar = pluginJar;
}
protected final File getPluginJar() {
return pluginJar;
} }
public void onLoad() { public void onLoad() {

View File

@ -52,15 +52,13 @@ public class PluginManager {
System.err.println("Ambiguous plugin name in " + file.getName() + " with the plugin \"" + plugins.get(pluginName).getClass().getName() + "\""); System.err.println("Ambiguous plugin name in " + file.getName() + " with the plugin \"" + plugins.get(pluginName).getClass().getName() + "\"");
break; break;
} }
URLClassLoader child = new URLClassLoader(new URL[] {file.toURI().toURL()}, Limbo.getInstance().getClass().getClassLoader());
URLClassLoader url = new URLClassLoader(new URL[] {file.toURI().toURL()}); Class<?> clazz = Class.forName(main, true, child);
Class<?> clazz = url.loadClass(main);
LimboPlugin plugin = (LimboPlugin) clazz.getDeclaredConstructor().newInstance(); LimboPlugin plugin = (LimboPlugin) clazz.getDeclaredConstructor().newInstance();
plugin.setInfo(pluginYaml); plugin.setInfo(pluginYaml, file);
plugins.put(plugin.getName(), plugin); plugins.put(plugin.getName(), plugin);
plugin.onLoad(); plugin.onLoad();
Limbo.getInstance().getConsole().sendMessage("Loading plugin " + file.getName() + " " + plugin.getInfo().getVersion() + " by " + plugin.getInfo().getAuthor()); Limbo.getInstance().getConsole().sendMessage("Loading plugin " + file.getName() + " " + plugin.getInfo().getVersion() + " by " + plugin.getInfo().getAuthor());
url.close();
break; break;
} }
} }

View File

@ -0,0 +1,99 @@
package com.loohp.limbo.Scheduler;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.Plugins.LimboPlugin;
public abstract class LimboRunnable implements LimboTask {
private volatile boolean registered = false;
protected volatile int taskId = -1;
public void cancel() {
synchronized (this) {
if (registered && taskId >= 0) {
Limbo.getInstance().getScheduler().cancelTask(taskId);
}
}
}
public int getTaskId() {
if (registered && taskId >= 0) {
return taskId;
} else {
throw new IllegalStateException("LimboRunnable not yet scheduled");
}
}
public LimboRunnable runTask(LimboPlugin plugin) {
synchronized (this) {
if (!registered) {
taskId = Limbo.getInstance().getScheduler().runTask(plugin, this);
registered = true;
return this;
} else {
throw new IllegalStateException("LimboRunnable already scheduled");
}
}
}
public LimboRunnable runTaskLater(LimboPlugin plugin, long delay) {
synchronized (this) {
if (!registered) {
taskId = Limbo.getInstance().getScheduler().runTaskLater(plugin, this, delay);
registered = true;
return this;
} else {
throw new IllegalStateException("LimboRunnable already scheduled");
}
}
}
public LimboRunnable runTaskAsync(LimboPlugin plugin) {
synchronized (this) {
if (!registered) {
taskId = Limbo.getInstance().getScheduler().runTaskAsync(plugin, this);
registered = true;
return this;
} else {
throw new IllegalStateException("LimboRunnable already scheduled");
}
}
}
public LimboRunnable runTaskLaterAsync(LimboPlugin plugin, long delay) {
synchronized (this) {
if (!registered) {
taskId = Limbo.getInstance().getScheduler().runTaskLaterAsync(plugin, this, delay);
registered = true;
return this;
} else {
throw new IllegalStateException("LimboRunnable already scheduled");
}
}
}
public LimboRunnable runTaskTimer(LimboPlugin plugin, long delay, long period) {
synchronized (this) {
if (!registered) {
taskId = Limbo.getInstance().getScheduler().runTaskTimer(plugin, this, delay, period);
registered = true;
return this;
} else {
throw new IllegalStateException("LimboRunnable already scheduled");
}
}
}
public LimboRunnable runTaskTimerAsync(LimboPlugin plugin, long delay, long period) {
synchronized (this) {
if (!registered) {
taskId = Limbo.getInstance().getScheduler().runTaskTimerAsync(plugin, this, delay, period);
registered = true;
return this;
} else {
throw new IllegalStateException("LimboRunnable already scheduled");
}
}
}
}

View File

@ -0,0 +1,250 @@
package com.loohp.limbo.Scheduler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.Plugins.LimboPlugin;
public class LimboScheduler {
private AtomicInteger idProvider = new AtomicInteger(0);
private Map<Long, List<LimboSchedulerTask>> registeredTasks = new HashMap<>();
private Map<Integer, LimboSchedulerTask> tasksById = new HashMap<>();
private Set<Integer> cancelledTasks = new HashSet<>();
public LimboScheduler() {
}
protected int nextId() {
return idProvider.getAndUpdate(id -> id >= Integer.MAX_VALUE ? 0 : id + 1);
}
public void cancelTask(int taskId) {
if (tasksById.containsKey(taskId)) {
cancelledTasks.add(taskId);
}
}
public void cancelTask(LimboPlugin plugin) {
for (LimboSchedulerTask task : tasksById.values()) {
if (task.getPlugin().getName().equals(plugin.getName())) {
cancelledTasks.add(task.getTaskId());
}
}
}
protected int runTask(int taskId, LimboPlugin plugin, LimboTask task) {
return runTaskLater(taskId, plugin, task, 0);
}
public int runTask(LimboPlugin plugin, LimboTask task) {
return runTaskLater(plugin, task, 0);
}
protected int runTaskLater(int taskId, LimboPlugin plugin, LimboTask task, long delay) {
LimboSchedulerTask st = new LimboSchedulerTask(plugin, task, taskId, LimboSchedulerTaskType.SYNC, 0);
if (delay <= 0) {
delay = 1;
}
long tick = Limbo.getInstance().getHeartBeat().getCurrentTick() + delay;
tasksById.put(taskId, st);
List<LimboSchedulerTask> list = registeredTasks.get(tick);
if (list == null) {
list = new ArrayList<>();
registeredTasks.put(tick, list);
}
list.add(st);
return taskId;
}
public int runTaskLater(LimboPlugin plugin, LimboTask task, long delay) {
return runTaskLater(nextId(), plugin, task, delay);
}
protected int runTaskAsync(int taskId, LimboPlugin plugin, LimboTask task) {
return runTaskLaterAsync(taskId, plugin, task, 0);
}
public int runTaskAsync(LimboPlugin plugin, LimboTask task) {
return runTaskLaterAsync(plugin, task, 0);
}
protected int runTaskLaterAsync(int taskId, LimboPlugin plugin, LimboTask task, long delay) {
LimboSchedulerTask st = new LimboSchedulerTask(plugin, task, taskId, LimboSchedulerTaskType.ASYNC, 0);
if (delay <= 0) {
delay = 1;
}
long tick = Limbo.getInstance().getHeartBeat().getCurrentTick() + delay;
tasksById.put(taskId, st);
List<LimboSchedulerTask> list = registeredTasks.get(tick);
if (list == null) {
list = new ArrayList<>();
registeredTasks.put(tick, list);
}
list.add(st);
return taskId;
}
public int runTaskLaterAsync(LimboPlugin plugin, LimboTask task, long delay) {
return runTaskLaterAsync(nextId(), plugin, task, delay);
}
protected int runTaskTimer(int taskId, LimboPlugin plugin, LimboTask task, long delay, long period) {
LimboSchedulerTask st = new LimboSchedulerTask(plugin, task, taskId, LimboSchedulerTaskType.TIMER_SYNC, period);
if (delay <= 0) {
delay = 1;
}
if (period <= 0) {
period = 1;
}
long tick = Limbo.getInstance().getHeartBeat().getCurrentTick() + delay;
tasksById.put(taskId, st);
List<LimboSchedulerTask> list = registeredTasks.get(tick);
if (list == null) {
list = new ArrayList<>();
registeredTasks.put(tick, list);
}
list.add(st);
return taskId;
}
public int runTaskTimer(LimboPlugin plugin, LimboTask task, long delay, long period) {
return runTaskTimer(nextId(), plugin, task, delay, period);
}
protected int runTaskTimerAsync(int taskId, LimboPlugin plugin, LimboTask task, long delay, long period) {
LimboSchedulerTask st = new LimboSchedulerTask(plugin, task, taskId, LimboSchedulerTaskType.TIMER_ASYNC, period);
if (delay <= 0) {
delay = 1;
}
if (period <= 0) {
period = 1;
}
long tick = Limbo.getInstance().getHeartBeat().getCurrentTick() + delay;
tasksById.put(taskId, st);
List<LimboSchedulerTask> list = registeredTasks.get(tick);
if (list == null) {
list = new ArrayList<>();
registeredTasks.put(tick, list);
}
list.add(st);
return taskId;
}
public int runTaskTimerAsync(LimboPlugin plugin, LimboTask task, long delay, long period) {
return runTaskTimerAsync(nextId(), plugin, task, delay, period);
}
protected CurrentSchedulerTask collectTasks(long currentTick) {
List<LimboSchedulerTask> tasks = registeredTasks.remove(currentTick);
if (tasks == null) {
return null;
}
List<LimboSchedulerTask> asyncTasks = new LinkedList<>();
List<LimboSchedulerTask> syncedTasks = new LinkedList<>();
for (LimboSchedulerTask task : tasks) {
int taskId = task.getTaskId();
if (cancelledTasks.contains(taskId)) {
cancelledTasks.remove(taskId);
continue;
}
switch (task.getType()) {
case ASYNC:
asyncTasks.add(task);
break;
case SYNC:
syncedTasks.add(task);
break;
case TIMER_ASYNC:
asyncTasks.add(task);
runTaskTimerAsync(task.getTaskId(), task.getPlugin(), task.getTask(), task.getPeriod(), task.getPeriod());
break;
case TIMER_SYNC:
syncedTasks.add(task);
runTaskTimer(task.getTaskId(), task.getPlugin(), task.getTask(), task.getPeriod(), task.getPeriod());
break;
}
}
return new CurrentSchedulerTask(syncedTasks, asyncTasks);
}
public static class CurrentSchedulerTask {
private List<LimboSchedulerTask> asyncTasks;
private List<LimboSchedulerTask> syncedTasks;
public CurrentSchedulerTask(List<LimboSchedulerTask> syncedTasks, List<LimboSchedulerTask> asyncTasks) {
this.asyncTasks = asyncTasks;
this.syncedTasks = syncedTasks;
}
public List<LimboSchedulerTask> getAsyncTasks() {
return asyncTasks;
}
public List<LimboSchedulerTask> getSyncedTasks() {
return syncedTasks;
}
}
public static class LimboSchedulerTask {
private int taskId;
private LimboPlugin plugin;
private LimboTask task;
private LimboSchedulerTaskType type;
private long period;
private LimboSchedulerTask(LimboPlugin plugin, LimboTask task, int taskId, LimboSchedulerTaskType type, long period) {
this.plugin = plugin;
this.task = task;
this.taskId = taskId;
this.type = type;
this.period = period;
}
public LimboPlugin getPlugin() {
return plugin;
}
public LimboTask getTask() {
return task;
}
public int getTaskId() {
return taskId;
}
public LimboSchedulerTaskType getType() {
return type;
}
public long getPeriod() {
return period;
}
}
public static enum LimboSchedulerTaskType {
SYNC,
ASYNC,
TIMER_SYNC,
TIMER_ASYNC;
}
}

View File

@ -0,0 +1,5 @@
package com.loohp.limbo.Scheduler;
public interface LimboTask extends Runnable {
}

View File

@ -0,0 +1,129 @@
package com.loohp.limbo.Scheduler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.Scheduler.LimboScheduler.CurrentSchedulerTask;
import com.loohp.limbo.Scheduler.LimboScheduler.LimboSchedulerTask;
public class Tick {
private int tickingInterval;
private AtomicLong tick = new AtomicLong(0);
private List<Thread> threads = new ArrayList<>();
private Queue<LimboSchedulerTask> asyncTasksQueue = new ConcurrentLinkedQueue<>();
public Tick(Limbo instance) {
new Thread(new Runnable() {
@Override
public void run() {
tickingInterval = (int) Math.round(1000.0 / Limbo.getInstance().getServerProperties().getDefinedTicksPerSecond());
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (instance.isRunning()) {
LimboSchedulerTask task = asyncTasksQueue.poll();
if (task == null) {
try {
TimeUnit.NANOSECONDS.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
LimboTask limboTask = task.getTask();
try {
limboTask.run();
} catch (Throwable e) {
System.err.println("Task " + task.getTaskId() + " threw an exception: " + e.getLocalizedMessage());
e.printStackTrace();
}
}
}
}
});
thread.start();
threads.add(thread);
}
while (instance.isRunning()) {
long start = System.currentTimeMillis();
tick.incrementAndGet();
instance.getPlayers().forEach(each -> {
if (each.clientConnection.isReady()) {
try {
each.playerInteractManager.update();
} catch (IOException e) {
e.printStackTrace();
}
/*
try {
each.getDataWatcher().update();
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
*/
}
});
instance.getWorlds().forEach(each -> {
try {
each.update();
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
});
CurrentSchedulerTask tasks = instance.getScheduler().collectTasks(getCurrentTick());
if (tasks != null) {
asyncTasksQueue.addAll(tasks.getAsyncTasks());
tasks.getSyncedTasks().forEach(task -> {
LimboTask limboTask = task.getTask();
try {
limboTask.run();
} catch (Throwable e) {
System.err.println("Task " + task.getTaskId() + " threw an exception: " + e.getLocalizedMessage());
e.printStackTrace();
}
});
}
long end = System.currentTimeMillis();
try {
TimeUnit.MILLISECONDS.sleep(tickingInterval - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public long getCurrentTick() {
return tick.get();
}
@SuppressWarnings("deprecation")
public void waitAndKillThreads(long waitTime) {
long end = System.currentTimeMillis() + waitTime;
for (Thread thread : threads) {
try {
thread.join(Math.max(end - System.currentTimeMillis(), 1));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (thread.isAlive()) {
thread.stop();
}
}
}
}

View File

@ -357,8 +357,7 @@ public class ClientConnection extends Thread {
int packetId = DataTypeIO.readVarInt(input); int packetId = DataTypeIO.readVarInt(input);
Class<? extends Packet> packetType = Packet.getPlayIn().get(packetId); Class<? extends Packet> packetType = Packet.getPlayIn().get(packetId);
//Limbo.getInstance().getConsole().sendMessage(packetId + " -> " + packetType); //Limbo.getInstance().getConsole().sendMessage(packetId + " -> " + packetType);
CheckedConsumer<PlayerMoveEvent, IOException> processMoveEvent = event -> CheckedConsumer<PlayerMoveEvent, IOException> processMoveEvent = event -> {
{
Location originalTo = event.getTo().clone(); Location originalTo = event.getTo().clone();
if (event.isCancelled()) { if (event.isCancelled()) {
Location returnTo = event.getFrom(); Location returnTo = event.getFrom();
@ -368,8 +367,7 @@ public class ClientConnection extends Thread {
Location to = event.getTo(); Location to = event.getTo();
Limbo.getInstance().getUnsafe().setPlayerLocationSilently(player, to); Limbo.getInstance().getUnsafe().setPlayerLocationSilently(player, to);
// If an event handler used setTo, let's make sure we tell the player about it. // If an event handler used setTo, let's make sure we tell the player about it.
if(!originalTo.equals(to)) if (!originalTo.equals(to)) {
{
PacketPlayOutPositionAndLook pos = new PacketPlayOutPositionAndLook(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), 1); PacketPlayOutPositionAndLook pos = new PacketPlayOutPositionAndLook(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), 1);
sendPacket(pos); sendPacket(pos);
} }

View File

@ -5,7 +5,7 @@ import java.io.IOException;
public class PacketPlayInHeldItemChange extends PacketIn { public class PacketPlayInHeldItemChange extends PacketIn {
private final short slot; private short slot;
public PacketPlayInHeldItemChange(short slot) { public PacketPlayInHeldItemChange(short slot) {
this.slot = slot; this.slot = slot;

View File

@ -5,7 +5,7 @@ import java.io.IOException;
public class PacketPlayInKeepAlive extends PacketIn { public class PacketPlayInKeepAlive extends PacketIn {
long payload; private long payload;
public PacketPlayInKeepAlive(long payload) { public PacketPlayInKeepAlive(long payload) {
this.payload = payload; this.payload = payload;

View File

@ -1,51 +0,0 @@
package com.loohp.limbo;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class Tick {
private TimerTask timerTask;
public Tick(Limbo instance) {
this.timerTask = new TimerTask () {
@Override
public void run () {
if (instance.isRunning()) {
instance.getPlayers().forEach(each -> {
if (each.clientConnection.isReady()) {
try {
each.playerInteractManager.update();
} catch (IOException e) {
e.printStackTrace();
}
/*
try {
each.getDataWatcher().update();
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
*/
}
});
instance.getWorlds().forEach(each -> {
try {
each.update();
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
});
} else {
this.cancel();
}
}
};
new Timer().schedule(timerTask, 0, Math.round(1000 / instance.getServerProperties().getDefinedTicksPerSecond()));
}
public void cancel() {
timerTask.cancel();
}
}