saveMap() {
map.put("name", name);
map.put("main", main);
map.put("version", version);
- map.put("database", database);
map.put("order", order.toString());
map.put("default-permission", defaultPerm.toString());
diff --git a/NachoSpigot-API/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/NachoSpigot-API/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
index 231738b6e..ac615ac25 100644
--- a/NachoSpigot-API/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
+++ b/NachoSpigot-API/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
@@ -53,7 +53,6 @@ public abstract class JavaPlugin extends PluginBase {
private File dataFolder = null;
private ClassLoader classLoader = null;
private boolean naggable = true;
- private EbeanServer ebean = null;
private FileConfiguration newConfig = null;
private File configFile = null;
private PluginLogger logger = null;
@@ -353,26 +352,6 @@ final void init(PluginLoader loader, Server server, PluginDescriptionFile descri
this.configFile = new File(dataFolder, "config.yml");
this.logger = new PluginLogger(this);
- if (description.isDatabaseEnabled()) {
- ServerConfig db = new ServerConfig();
-
- db.setDefaultServer(false);
- db.setRegister(false);
- db.setClasses(getDatabaseClasses());
- db.setName(description.getName());
- server.configureDbConfig(db);
-
- DataSourceConfig ds = db.getDataSourceConfig();
-
- ds.setUrl(replaceDatabaseString(ds.getUrl()));
- dataFolder.mkdirs();
-
- ClassLoader previous = Thread.currentThread().getContextClassLoader();
-
- Thread.currentThread().setContextClassLoader(classLoader);
- ebean = EbeanServerFactory.create(db);
- Thread.currentThread().setContextClassLoader(previous);
- }
}
/**
@@ -465,25 +444,6 @@ public final void setNaggable(boolean canNag) {
this.naggable = canNag;
}
- @Override
- public EbeanServer getDatabase() {
- return ebean;
- }
-
- protected void installDDL() {
- SpiEbeanServer serv = (SpiEbeanServer) getDatabase();
- DdlGenerator gen = serv.getDdlGenerator();
-
- gen.runScript(false, gen.generateCreateDdl());
- }
-
- protected void removeDDL() {
- SpiEbeanServer serv = (SpiEbeanServer) getDatabase();
- DdlGenerator gen = serv.getDdlGenerator();
-
- gen.runScript(true, gen.generateDropDdl());
- }
-
@Override
public final Logger getLogger() {
return logger;
diff --git a/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitScheduler.java b/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitScheduler.java
index 6e28205f2..e8d97554a 100644
--- a/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitScheduler.java
+++ b/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitScheduler.java
@@ -1,9 +1,12 @@
package org.bukkit.scheduler;
import org.bukkit.plugin.Plugin;
+
+import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
import java.util.concurrent.Future;
-import java.util.List;
+import java.util.function.Consumer;
public interface BukkitScheduler {
@@ -63,7 +66,7 @@ public interface BukkitScheduler {
public int scheduleSyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period);
/**
- * @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)} *
+ * @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)}
* @param plugin Plugin that owns the task
* @param task Task to be executed
* @param delay Delay in server ticks before executing first repeat
@@ -155,11 +158,6 @@ public interface BukkitScheduler {
*/
public void cancelTasks(Plugin plugin);
- /**
- * Removes all tasks from the scheduler.
- */
- public void cancelAllTasks();
-
/**
* Check if the task currently running.
*
@@ -218,6 +216,16 @@ public interface BukkitScheduler {
*/
public BukkitTask runTask(Plugin plugin, Runnable task) throws IllegalArgumentException;
+ /**
+ * Returns a task that will run on the next server tick.
+ *
+ * @param plugin the reference to the plugin scheduling task
+ * @param task the task to be run
+ * @throws IllegalArgumentException if plugin is null
+ * @throws IllegalArgumentException if task is null
+ */
+ public void runTask(Plugin plugin, Consumer task) throws IllegalArgumentException;
+
/**
* @deprecated Use {@link BukkitRunnable#runTask(Plugin)}
*
@@ -244,6 +252,19 @@ public interface BukkitScheduler {
*/
public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable task) throws IllegalArgumentException;
+ /**
+ * Asynchronous tasks should never access any API in Bukkit. Great care
+ * should be taken to assure the thread-safety of asynchronous tasks.
+ *
+ * Returns a task that will run asynchronously.
+ *
+ * @param plugin the reference to the plugin scheduling task
+ * @param task the task to be run
+ * @throws IllegalArgumentException if plugin is null
+ * @throws IllegalArgumentException if task is null
+ */
+ public void runTaskAsynchronously(Plugin plugin, Consumer task) throws IllegalArgumentException;
+
/**
* @deprecated Use {@link BukkitRunnable#runTaskAsynchronously(Plugin)}
* @param plugin the reference to the plugin scheduling task
@@ -268,6 +289,18 @@ public interface BukkitScheduler {
*/
public BukkitTask runTaskLater(Plugin plugin, Runnable task, long delay) throws IllegalArgumentException;
+ /**
+ * Returns a task that will run after the specified number of server
+ * ticks.
+ *
+ * @param plugin the reference to the plugin scheduling task
+ * @param task the task to be run
+ * @param delay the ticks to wait before running the task
+ * @throws IllegalArgumentException if plugin is null
+ * @throws IllegalArgumentException if task is null
+ */
+ public void runTaskLater(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException;
+
/**
* @deprecated Use {@link BukkitRunnable#runTaskLater(Plugin, long)}
* @param plugin the reference to the plugin scheduling task
@@ -296,6 +329,21 @@ public interface BukkitScheduler {
*/
public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable task, long delay) throws IllegalArgumentException;
+ /**
+ * Asynchronous tasks should never access any API in Bukkit. Great care
+ * should be taken to assure the thread-safety of asynchronous tasks.
+ *
+ * Returns a task that will run asynchronously after the specified number
+ * of server ticks.
+ *
+ * @param plugin the reference to the plugin scheduling task
+ * @param task the task to be run
+ * @param delay the ticks to wait before running the task
+ * @throws IllegalArgumentException if plugin is null
+ * @throws IllegalArgumentException if task is null
+ */
+ public void runTaskLaterAsynchronously(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException;
+
/**
* @deprecated Use {@link BukkitRunnable#runTaskLaterAsynchronously(Plugin, long)}
* @param plugin the reference to the plugin scheduling task
@@ -322,6 +370,19 @@ public interface BukkitScheduler {
*/
public BukkitTask runTaskTimer(Plugin plugin, Runnable task, long delay, long period) throws IllegalArgumentException;
+ /**
+ * Returns a task that will repeatedly run until cancelled, starting after
+ * the specified number of server ticks.
+ *
+ * @param plugin the reference to the plugin scheduling task
+ * @param task the task to be run
+ * @param delay the ticks to wait before running the task
+ * @param period the ticks to wait between runs
+ * @throws IllegalArgumentException if plugin is null
+ * @throws IllegalArgumentException if task is null
+ */
+ public void runTaskTimer(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException;
+
/**
* @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)}
* @param plugin the reference to the plugin scheduling task
@@ -353,6 +414,23 @@ public interface BukkitScheduler {
*/
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable task, long delay, long period) throws IllegalArgumentException;
+ /**
+ * Asynchronous tasks should never access any API in Bukkit. Great care
+ * should be taken to assure the thread-safety of asynchronous tasks.
+ *
+ * Returns a task that will repeatedly run asynchronously until cancelled,
+ * starting after the specified number of server ticks.
+ *
+ * @param plugin the reference to the plugin scheduling task
+ * @param task the task to be run
+ * @param delay the ticks to wait before running the task for the first
+ * time
+ * @param period the ticks to wait between runs
+ * @throws IllegalArgumentException if plugin is null
+ * @throws IllegalArgumentException if task is null
+ */
+ public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException;
+
/**
* @deprecated Use {@link BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)}
* @param plugin the reference to the plugin scheduling task
@@ -366,4 +444,14 @@ public interface BukkitScheduler {
*/
@Deprecated
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException;
+
+ // Paper start - add getMainThreadExecutor
+ /**
+ * Returns an executor that will run tasks on the next server tick.
+ *
+ * @param plugin the reference to the plugin scheduling tasks
+ * @return an executor associated with the given plugin
+ */
+ public Executor getMainThreadExecutor(Plugin plugin);
+ // Paper end
}
diff --git a/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitTask.java b/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitTask.java
index e447e64e5..9c693f838 100644
--- a/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitTask.java
+++ b/NachoSpigot-API/src/main/java/org/bukkit/scheduler/BukkitTask.java
@@ -28,6 +28,13 @@ public interface BukkitTask {
*/
public boolean isSync();
+ /**
+ * Returns true if this task has been cancelled.
+ *
+ * @return true if the task has been cancelled
+ */
+ public boolean isCancelled();
+
/**
* Will attempt to cancel this task.
*/
diff --git a/NachoSpigot-API/src/main/java/org/github/paperspigot/Title.java b/NachoSpigot-API/src/main/java/org/github/paperspigot/Title.java
new file mode 100644
index 000000000..29f3cfb8c
--- /dev/null
+++ b/NachoSpigot-API/src/main/java/org/github/paperspigot/Title.java
@@ -0,0 +1,355 @@
+package org.github.paperspigot;
+
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.TextComponent;
+import org.bukkit.entity.Player;
+
+import static com.google.common.base.Preconditions.*;
+
+/**
+ * Represents a title to may be sent to a {@link Player}.
+ *
+ *
A title can be sent without subtitle text.
+ */
+public final class Title {
+
+ /**
+ * The default number of ticks for the title to fade in.
+ */
+ public static final int DEFAULT_FADE_IN = 20;
+ /**
+ * The default number of ticks for the title to stay.
+ */
+ public static final int DEFAULT_STAY = 200;
+ /**
+ * The default number of ticks for the title to fade out.
+ */
+ public static final int DEFAULT_FADE_OUT = 20;
+
+ private final BaseComponent[] title;
+ private final BaseComponent[] subtitle;
+ private final int fadeIn;
+ private final int stay;
+ private final int fadeOut;
+
+ /**
+ * Create a title with the default time values and no subtitle.
+ *
+ *
Times use default values.
+ *
+ * @param title the main text of the title
+ * @throws NullPointerException if the title is null
+ */
+ public Title(BaseComponent title) {
+ this(title, null);
+ }
+
+ /**
+ * Create a title with the default time values and no subtitle.
+ *
+ *
Times use default values.
+ *
+ * @param title the main text of the title
+ * @throws NullPointerException if the title is null
+ */
+ public Title(BaseComponent[] title) {
+ this(title, null);
+ }
+
+ /**
+ * Create a title with the default time values and no subtitle.
+ *
+ *
Times use default values.
+ *
+ * @param title the main text of the title
+ * @throws NullPointerException if the title is null
+ */
+ public Title(String title) {
+ this(title, null);
+ }
+
+ /**
+ * Create a title with the default time values.
+ *
+ *
Times use default values.
+ *
+ * @param title the main text of the title
+ * @param subtitle the secondary text of the title
+ */
+ public Title(BaseComponent title, BaseComponent subtitle) {
+ this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT);
+ }
+
+ /**
+ * Create a title with the default time values.
+ *
+ *
Times use default values.
+ *
+ * @param title the main text of the title
+ * @param subtitle the secondary text of the title
+ */
+ public Title(BaseComponent[] title, BaseComponent[] subtitle) {
+ this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT);
+ }
+
+ /**
+ * Create a title with the default time values.
+ *
+ *
Times use default values.
+ *
+ * @param title the main text of the title
+ * @param subtitle the secondary text of the title
+ */
+ public Title(String title, String subtitle) {
+ this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT);
+ }
+
+ /**
+ * Creates a new title.
+ *
+ * @param title the main text of the title
+ * @param subtitle the secondary text of the title
+ * @param fadeIn the number of ticks for the title to fade in
+ * @param stay the number of ticks for the title to stay on screen
+ * @param fadeOut the number of ticks for the title to fade out
+ * @throws IllegalArgumentException if any of the times are negative
+ */
+ public Title(BaseComponent title, BaseComponent subtitle, int fadeIn, int stay, int fadeOut) {
+ this(
+ new BaseComponent[]{checkNotNull(title, "title")},
+ subtitle == null ? null : new BaseComponent[]{subtitle},
+ fadeIn,
+ stay,
+ fadeOut
+ );
+ }
+
+ /**
+ * Creates a new title.
+ *
+ * @param title the main text of the title
+ * @param subtitle the secondary text of the title
+ * @param fadeIn the number of ticks for the title to fade in
+ * @param stay the number of ticks for the title to stay on screen
+ * @param fadeOut the number of ticks for the title to fade out
+ * @throws IllegalArgumentException if any of the times are negative
+ */
+ public Title(BaseComponent[] title, BaseComponent[] subtitle, int fadeIn, int stay, int fadeOut) {
+ checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn);
+ checkArgument(stay >= 0, "Negative stay: %s", stay);
+ checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut);
+ this.title = checkNotNull(title, "title");
+ this.subtitle = subtitle;
+ this.fadeIn = fadeIn;
+ this.stay = stay;
+ this.fadeOut = fadeOut;
+ }
+
+ /**
+ * Creates a new title.
+ *
+ *
It is recommended to the {@link BaseComponent} constrctors.
+ *
+ * @param title the main text of the title
+ * @param subtitle the secondary text of the title
+ * @param fadeIn the number of ticks for the title to fade in
+ * @param stay the number of ticks for the title to stay on screen
+ * @param fadeOut the number of ticks for the title to fade out
+ */
+ public Title(String title, String subtitle, int fadeIn, int stay, int fadeOut) {
+ this(
+ TextComponent.fromLegacyText(checkNotNull(title, "title")),
+ subtitle == null ? null : TextComponent.fromLegacyText(subtitle),
+ fadeIn,
+ stay,
+ fadeOut
+ );
+ }
+
+ /**
+ * Gets the text of this title
+ *
+ * @return the text
+ */
+ public BaseComponent[] getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Gets the text of this title's subtitle
+ *
+ * @return the text
+ */
+ public BaseComponent[] getSubtitle() {
+ return this.subtitle;
+ }
+
+ /**
+ * Gets the number of ticks to fade in.
+ *
+ *
The returned value is never negative.
+ *
+ * @return the number of ticks to fade in
+ */
+ public int getFadeIn() {
+ return this.fadeIn;
+ }
+
+ /**
+ * Gets the number of ticks to stay.
+ *
+ *
The returned value is never negative.
+ *
+ * @return the number of ticks to stay
+ */
+ public int getStay() {
+ return this.stay;
+ }
+
+ /**
+ * Gets the number of ticks to fade out.
+ *
+ *
The returned value is never negative.
+ *
+ * @return the number of ticks to fade out
+ */
+ public int getFadeOut() {
+ return this.fadeOut;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * A builder for creating titles
+ */
+ public static final class Builder {
+
+ private BaseComponent[] title;
+ private BaseComponent[] subtitle;
+ private int fadeIn = DEFAULT_FADE_IN;
+ private int stay = DEFAULT_STAY;
+ private int fadeOut = DEFAULT_FADE_OUT;
+
+ /**
+ * Sets the title to the given text.
+ *
+ * @param title the title text
+ * @return this builder instance
+ * @throws NullPointerException if the title is null
+ */
+ public Builder title(BaseComponent title) {
+ return this.title(new BaseComponent[]{checkNotNull(title, "title")});
+ }
+
+ /**
+ * Sets the title to the given text.
+ *
+ * @param title the title text
+ * @return this builder instance
+ * @throws NullPointerException if the title is null
+ */
+ public Builder title(BaseComponent[] title) {
+ this.title = checkNotNull(title, "title");
+ return this;
+ }
+
+ /**
+ * Sets the title to the given text.
+ *
+ *
It is recommended to the {@link BaseComponent} methods.
+ *
+ * @param title the title text
+ * @return this builder instance
+ * @throws NullPointerException if the title is null
+ */
+ public Builder title(String title) {
+ return this.title(TextComponent.fromLegacyText(checkNotNull(title, "title")));
+ }
+
+ /**
+ * Sets the subtitle to the given text.
+ *
+ * @param subtitle the title text
+ * @return this builder instance
+ */
+ public Builder subtitle(BaseComponent subtitle) {
+ return this.subtitle(subtitle == null ? null : new BaseComponent[]{subtitle});
+ }
+
+ /**
+ * Sets the subtitle to the given text.
+ *
+ * @param subtitle the title text
+ * @return this builder instance
+ */
+ public Builder subtitle(BaseComponent[] subtitle) {
+ this.subtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Sets the subtitle to the given text.
+ *
+ *
It is recommended to the {@link BaseComponent} methods.
+ *
+ * @param subtitle the title text
+ * @return this builder instance
+ */
+ public Builder subtitle(String subtitle) {
+ return this.subtitle(subtitle == null ? null : TextComponent.fromLegacyText(subtitle));
+ }
+
+ /**
+ * Sets the number of ticks for the title to fade in
+ *
+ * @param fadeIn the number of ticks to fade in
+ * @return this builder instance
+ * @throws IllegalArgumentException if it is negative
+ */
+ public Builder fadeIn(int fadeIn) {
+ checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn);
+ this.fadeIn = fadeIn;
+ return this;
+ }
+
+
+ /**
+ * Sets the number of ticks for the title to stay.
+ *
+ * @param stay the number of ticks to stay
+ * @return this builder instance
+ * @throws IllegalArgumentException if it is negative
+ */
+ public Builder stay(int stay) {
+ checkArgument(stay >= 0, "Negative stay: %s", stay);
+ this.stay = stay;
+ return this;
+ }
+
+ /**
+ * Sets the number of ticks for the title to fade out.
+ *
+ * @param fadeOut the number of ticks to fade out
+ * @return this builder instance
+ * @throws IllegalArgumentException if it is negative
+ */
+ public Builder fadeOut(int fadeOut) {
+ checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut);
+ this.fadeOut = fadeOut;
+ return this;
+ }
+
+ /**
+ * Create a title based on the values in the builder.
+ *
+ * @return a title from the values in this builder
+ * @throws IllegalStateException if title isn't specified
+ */
+ public Title build() {
+ checkState(title != null, "Title not specified");
+ return new Title(this.title, this.subtitle, this.fadeIn, this.stay, this.fadeOut);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-API/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java b/NachoSpigot-API/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java
new file mode 100644
index 000000000..74b714e58
--- /dev/null
+++ b/NachoSpigot-API/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java
@@ -0,0 +1,38 @@
+package org.github.paperspigot.event;
+
+import com.google.common.base.Preconditions;
+import org.bukkit.Bukkit;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.github.paperspigot.exception.ServerException;
+
+/**
+ * Called whenever an exception is thrown in a recoverable section of the server.
+ */
+public class ServerExceptionEvent extends Event {
+ private static final HandlerList handlers = new HandlerList();
+ private final ServerException exception;
+
+ public ServerExceptionEvent(ServerException exception) {
+ super(!Bukkit.isPrimaryThread());
+ this.exception = Preconditions.checkNotNull(exception, "exception");
+ }
+
+ /**
+ * Gets the wrapped exception that was thrown.
+ *
+ * @return Exception thrown
+ */
+ public ServerException getException() {
+ return exception;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerException.java b/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerException.java
new file mode 100644
index 000000000..265d8688a
--- /dev/null
+++ b/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerException.java
@@ -0,0 +1,22 @@
+package org.github.paperspigot.exception;
+/**
+ * Wrapper exception for all exceptions that are thrown by the server.
+ */
+public class ServerException extends Exception {
+
+ public ServerException(String message) {
+ super(message);
+ }
+
+ public ServerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ServerException(Throwable cause) {
+ super(cause);
+ }
+
+ protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginException.java b/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginException.java
new file mode 100644
index 000000000..8ed5c69a1
--- /dev/null
+++ b/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginException.java
@@ -0,0 +1,33 @@
+package org.github.paperspigot.exception;
+
+import org.bukkit.plugin.Plugin;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class ServerPluginException extends ServerException {
+ public ServerPluginException(String message, Throwable cause, Plugin responsiblePlugin) {
+ super(message, cause);
+ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin");
+ }
+
+ public ServerPluginException(Throwable cause, Plugin responsiblePlugin) {
+ super(cause);
+ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin");
+ }
+
+ protected ServerPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin");
+ }
+
+ private final Plugin responsiblePlugin;
+
+ /**
+ * Gets the plugin which is directly responsible for the exception being thrown
+ *
+ * @return plugin which is responsible for the exception throw
+ */
+ public Plugin getResponsiblePlugin() {
+ return responsiblePlugin;
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java b/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java
new file mode 100644
index 000000000..d18b9365d
--- /dev/null
+++ b/NachoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java
@@ -0,0 +1,37 @@
+package org.github.paperspigot.exception;
+
+import org.bukkit.scheduler.BukkitTask;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Thrown when a plugin's scheduler fails with an exception
+ */
+public class ServerSchedulerException extends ServerPluginException {
+
+ private final BukkitTask task;
+
+ public ServerSchedulerException(String message, Throwable cause, BukkitTask task) {
+ super(message, cause, task.getOwner());
+ this.task = checkNotNull(task, "task");
+ }
+
+ public ServerSchedulerException(Throwable cause, BukkitTask task) {
+ super(cause, task.getOwner());
+ this.task = checkNotNull(task, "task");
+ }
+
+ protected ServerSchedulerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, BukkitTask task) {
+ super(message, cause, enableSuppression, writableStackTrace, task.getOwner());
+ this.task = checkNotNull(task, "task");
+ }
+
+ /**
+ * Gets the task which threw the exception
+ *
+ * @return exception throwing task
+ */
+ public BukkitTask getTask() {
+ return task;
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-Server/src/main/java/co/aikar/timings/SpigotTimings.java b/NachoSpigot-Server/src/main/java/co/aikar/timings/SpigotTimings.java
index b3cafafcb..d314c000c 100644
--- a/NachoSpigot-Server/src/main/java/co/aikar/timings/SpigotTimings.java
+++ b/NachoSpigot-Server/src/main/java/co/aikar/timings/SpigotTimings.java
@@ -1,18 +1,25 @@
package co.aikar.timings;
+import com.google.common.collect.MapMaker;
import net.minecraft.server.*;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.craftbukkit.scheduler.CraftTask;
+import java.util.Map;
+
public final class SpigotTimings {
+ public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep");
public static final Timing playerListTimer = Timings.ofSafe("Player List");
public static final Timing connectionTimer = Timings.ofSafe("Connection Handler");
public static final Timing tickablesTimer = Timings.ofSafe("Tickables");
- public static final Timing minecraftSchedulerTimer = Timings.ofSafe("Minecraft Scheduler");
+
public static final Timing bukkitSchedulerTimer = Timings.ofSafe("Bukkit Scheduler");
+ public static final Timing bukkitSchedulerPendingTimer = Timings.ofSafe("Bukkit Scheduler - Pending");
+ public static final Timing bukkitSchedulerFinishTimer = Timings.ofSafe("Bukkit Scheduler - Finishing");
+
public static final Timing chunkIOTickTimer = Timings.ofSafe("ChunkIOTick");
public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update");
public static final Timing serverCommandTimer = Timings.ofSafe("Server Command");
@@ -20,6 +27,7 @@ public final class SpigotTimings {
public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity");
public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity");
+ public static final Timing packetProcessTimer = Timings.ofSafe("## Packet Processing");
public static final Timing processQueueTimer = Timings.ofSafe("processQueue");
@@ -31,8 +39,14 @@ public final class SpigotTimings {
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
+ private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap();
+
private SpigotTimings() {}
+ public static Timing getInternalTaskName(String taskName) {
+ return Timings.ofSafe(taskName);
+ }
+
/**
* Gets a timer associated with a plugins tasks.
* @param bukkitTask
@@ -41,38 +55,47 @@ private SpigotTimings() {}
*/
public static Timing getPluginTaskTimings(BukkitTask bukkitTask, long period) {
if (!bukkitTask.isSync()) {
- return null;
+ return NullTimingHandler.NULL;
}
Plugin plugin;
- Runnable task = ((CraftTask) bukkitTask).task;
+ CraftTask craftTask = (CraftTask) bukkitTask;
- final Class extends Runnable> taskClass = task.getClass();
+ final Class> taskClass = craftTask.getTaskClass();
if (bukkitTask.getOwner() != null) {
plugin = bukkitTask.getOwner();
} else {
plugin = TimingsManager.getPluginByClassloader(taskClass);
}
- final String taskname;
- if (taskClass.isAnonymousClass()) {
- taskname = taskClass.getName();
- } else {
- taskname = taskClass.getCanonicalName();
- }
-
- String name = "Task: " +taskname;
+ final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz -> {
+ try {
+ String clsName = !clazz.isMemberClass()
+ ? clazz.getName()
+ : clazz.getCanonicalName();
+ if (clsName != null && clsName.contains("$Lambda$")) {
+ clsName = clsName.replaceAll("(Lambda\\$.*?)/.*", "$1");
+ }
+ return clsName != null ? clsName : "UnknownTask";
+ } catch (Throwable ex) {
+ new Exception("Error occurred detecting class name", ex).printStackTrace();
+ return "MangledClassFile";
+ }
+ });
+
+ StringBuilder name = new StringBuilder(64);
+ name.append("Task: ").append(taskname);
if (period > 0) {
- name += " (interval:" + period +")";
+ name.append(" (interval:").append(period).append(")");
} else {
- name += " (Single)";
+ name.append(" (Single)");
}
if (plugin == null) {
- return Timings.ofSafe(null, name, TimingsManager.PLUGIN_GROUP_HANDLER);
+ return Timings.ofSafe(null, name.toString());
}
- return Timings.ofSafe(plugin, name);
+ return Timings.ofSafe(plugin, name.toString());
}
/**
@@ -85,6 +108,10 @@ public static Timing getEntityTimings(Entity entity) {
return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType, tickEntityTimer);
}
+ public static Timing getEntityTimings(String entityType, String type) {
+ return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer);
+ }
+
/**
* Get a named timer for the specified tile entity type to track type specific timings.
* @param entity
@@ -108,4 +135,8 @@ public static void stopServer() {
public static Timing getBlockTiming(Block block) {
return Timings.ofSafe("## Scheduled Block: " + block.getName());
}
+
+ public static Timing getPacketTiming(Packet packet) {
+ return Timings.ofSafe("## Packet - " + packet.getClass().getName(), packetProcessTimer);
+ }
}
diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java
index ee1bfc695..5028c37e3 100644
--- a/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java
+++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java
@@ -18,6 +18,8 @@
// CraftBukkit start
import java.io.PrintStream;
+import java.util.function.BooleanSupplier;
+
import org.apache.logging.log4j.Level;
import org.bukkit.craftbukkit.LoggerOutputStream;
@@ -40,8 +42,8 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
private boolean s;
// CraftBukkit start - Signature changed
- public DedicatedServer(joptsimple.OptionSet options) {
- super(options, Proxy.NO_PROXY, DedicatedServer.a);
+ public DedicatedServer(joptsimple.OptionSet options, Thread thread1) {
+ super(options, Proxy.NO_PROXY, DedicatedServer.a, thread1);
// CraftBukkit end
if (!NachoConfig.disableInfiniSleeperThreadUsage) {
Thread thread = new Thread("Server Infinisleeper") {
@@ -296,7 +298,6 @@ public void run() {
long i1 = System.nanoTime() - j;
String s3 = String.format("%.3fs", (double) i1 / 1.0E9D);
- DedicatedServer.LOGGER.info("Done (" + s3 + ")! For help, type \"help\" or \"?\"");
if (this.propertyManager.getBoolean("enable-query", false)) {
DedicatedServer.LOGGER.info("Starting GS4 status listener");
this.m = new RemoteStatusListener(this);
@@ -394,8 +395,8 @@ protected void z() {
System.exit(0);
}
- public void B() { // CraftBukkit - fix decompile error
- super.B();
+ public void B(BooleanSupplier shouldKeepTicking) { // CraftBukkit - fix decompile error
+ super.B(shouldKeepTicking);
this.aO();
}
diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java
index 59184db84..bee5a22bb 100644
--- a/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1,10 +1,8 @@
package net.minecraft.server;
-import com.google.common.base.Charsets;
+import co.aikar.timings.SpigotTimings;
import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListenableFutureTask;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.minecraft.MinecraftSessionService;
@@ -12,44 +10,45 @@
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
-import io.netty.handler.codec.base64.Base64;
-import java.awt.GraphicsEnvironment;
+import io.netty.util.ResourceLeakDetector;
+import jline.console.ConsoleReader;
+import joptsimple.OptionSet;
+import me.elier.nachospigot.config.NachoConfig;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.lang3.Validate;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bukkit.craftbukkit.Main;
+import org.bukkit.event.world.WorldLoadEvent;
+import org.spigotmc.WatchdogThread;
+import xyz.sculas.nacho.async.AsyncExplosions;
+import xyz.dysaido.nacho.ReentrantIAsyncHandler;
+import xyz.dysaido.nacho.TasksPerTick;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.net.Proxy;
+import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import java.util.Queue;
-import java.util.Random;
-import java.util.UUID;
+import java.util.*;
import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.FutureTask;
-import javax.imageio.ImageIO;
-
-import io.netty.util.ResourceLeakDetector;
-import me.elier.nachospigot.config.NachoConfig;
-import org.apache.commons.lang3.Validate;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-// CraftBukkit start
-
-import jline.console.ConsoleReader;
-import joptsimple.OptionSet;
-
-import org.bukkit.craftbukkit.Main;
-import co.aikar.timings.SpigotTimings; // Spigot
-import xyz.sculas.nacho.async.AsyncExplosions;
-// CraftBukkit end
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
-public abstract class MinecraftServer implements Runnable, ICommandListener, IAsyncTaskHandler, IMojangStatistics {
+public abstract class MinecraftServer extends ReentrantIAsyncHandler implements ICommandListener, IAsyncTaskHandler, IMojangStatistics {
public static final Logger LOGGER = LogManager.getLogger(MinecraftServer.class);
public static final File a = new File("usercache.json");
@@ -70,6 +69,8 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
private boolean isRunning = true;
private boolean isStopped;
private int ticks;
+ private volatile boolean isReady;
+ private long lastOverloadWarning;
protected final Proxy e;
public String f;
public int g;
@@ -101,9 +102,10 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
private long X = 0L;
private final GameProfileRepository Y;
private final UserCache Z;
- protected final Queue> j = new java.util.concurrent.ConcurrentLinkedQueue>(); // Spigot, PAIL: Rename
- private Thread serverThread;
- private long ab = az();
+ public final Thread serverThread;
+ private long nextTickTime;
+ private long delayedTasksMaxNextTickTime;
+ private boolean mayHaveDelayedTasks;
// CraftBukkit start
public List worlds = new ArrayList();
@@ -113,12 +115,31 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
public ConsoleReader reader;
public static int currentTick = 0; // PaperSpigot - Further improve tick loop
- public final Thread primaryThread;
- public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue();
+ // public final Thread primaryThread;
+ public long serverStartTime;
+ public Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue();
public int autosavePeriod;
+ private boolean forceTicks;
// CraftBukkit end
+ public volatile Thread shutdownThread; // Paper
- public MinecraftServer(OptionSet options, Proxy proxy, File file1) {
+ // Nacho start
+ public static S spin(Function serverFactory) {
+ AtomicReference reference = new AtomicReference<>();
+ Thread thread = new Thread(() -> reference.get().runServer(), "Server thread");
+
+ thread.setUncaughtExceptionHandler((thread1, throwable) -> MinecraftServer.LOGGER.error(throwable));
+ S server = serverFactory.apply(thread); // CraftBukkit - decompile error
+
+ reference.set(server);
+ thread.setPriority(Thread.NORM_PRIORITY + 2); // Paper - boost priority
+ thread.start();
+ return server;
+ }
+ // Nacho end
+
+ public MinecraftServer(OptionSet options, Proxy proxy, File file1, Thread thread) {
+ super("Server");
io.netty.util.ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); // [Nacho-0040] Change deprecated Netty parameter // Spigot - disable
this.e = proxy;
MinecraftServer.l = this;
@@ -130,6 +151,8 @@ public MinecraftServer(OptionSet options, Proxy proxy, File file1) {
this.V = new YggdrasilAuthenticationService(proxy, UUID.randomUUID().toString());
this.W = this.V.createMinecraftSessionService();
this.Y = this.V.createProfileRepository();
+ this.nextTickTime = getMillis();
+ this.serverThread = thread;
// CraftBukkit start
this.options = options;
// Try to see if we're actually running in a terminal, disable jline if not
@@ -155,7 +178,7 @@ public MinecraftServer(OptionSet options, Proxy proxy, File file1) {
}
Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
- this.serverThread = primaryThread = new Thread(this, "Server thread"); // Moved from main
+ // this.serverThread = primaryThread = new Thread(this, "Server thread"); // Moved from main
}
public abstract PropertyManager getPropertyManager();
@@ -328,9 +351,42 @@ protected void a(String s, String s1, long i, WorldType worldtype, String s2) {
this.k();
}
+ private void optimizeWorldLoad(WorldServer world, int count) {
+ LOGGER.info("Preparing start region for level " + count + " (Seed: " + world.getSeed() + ")");
+
+ if (!world.getWorld().getKeepSpawnInMemory()) {
+ return;
+ }
+
+ this.forceTicks = true;
+
+ BlockPosition blockposition = world.getSpawn();
+ long j = az();
+ int i = 0;
+
+ this.nextTickTime = getMillis();
+ this.executeModerately();
+ for (int k = -192; k <= 192 && this.isRunning(); k += 16) {
+ for (int l = -192; l <= 192 && this.isRunning(); l += 16) {
+ long i1 = az();
+
+ if (i1 - j > 1000L) {
+ this.a_("Preparing spawn area", i * 100 / 625);
+ j = i1;
+ }
+
+ ++i;
+ world.chunkProviderServer.getChunkAt(blockposition.getX() + k >> 4, blockposition.getZ() + l >> 4);
+ }
+ }
+ this.executeModerately();
+ this.forceTicks = false;
+ }
+
protected void k() {
this.b("menu.generatingTerrain");
-
+ int count = 0;
+ /*
// CraftBukkit start - fire WorldLoadEvent and handle whether or not to keep the spawn in memory
for (int m = 0; m < worlds.size(); m++) {
WorldServer worldserver = this.worlds.get(m);
@@ -340,10 +396,14 @@ protected void k() {
continue;
}
+ this.forceTicks = true;
+
BlockPosition blockposition = worldserver.getSpawn();
long j = az();
int i = 0;
+ this.nextTickTime = getMillis();
+
for (int k = -192; k <= 192 && this.isRunning(); k += 16) {
for (int l = -192; l <= 192 && this.isRunning(); l += 16) {
long i1 = az();
@@ -357,10 +417,11 @@ protected void k() {
worldserver.chunkProviderServer.getChunkAt(blockposition.getX() + k >> 4, blockposition.getZ() + l >> 4);
}
}
- }
-
+ }*/
for (WorldServer world : this.worlds) {
- this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(world.getWorld()));
+ this.optimizeWorldLoad(world, count);
+ this.server.getPluginManager().callEvent(new WorldLoadEvent(world.getWorld()));
+ ++count;
}
// CraftBukkit end
this.s();
@@ -404,12 +465,9 @@ protected void s() {
protected void saveChunks(boolean flag) throws ExceptionWorldConflict { // CraftBukkit - added throws
if (!this.N) {
- WorldServer[] aworldserver = this.worldServer;
- int i = aworldserver.length;
// CraftBukkit start
- for (int j = 0; j < worlds.size(); ++j) {
- WorldServer worldserver = worlds.get(j);
+ for (WorldServer worldserver : worlds) {
// CraftBukkit end
if (worldserver != null) {
@@ -432,6 +490,13 @@ protected void saveChunks(boolean flag) throws ExceptionWorldConflict { // Craft
// CraftBukkit start
private boolean hasStopped = false;
private final Object stopLock = new Object();
+
+
+ public boolean hasStopped() {
+ synchronized (this.stopLock) {
+ return this.hasStopped;
+ }
+ }
// CraftBukkit end
public void stop() throws ExceptionWorldConflict, InterruptedException { // CraftBukkit - added throws
@@ -510,6 +575,8 @@ public void safeShutdown() {
private static final long SEC_IN_NANO = 1000000000;
private static final long TICK_TIME = SEC_IN_NANO / TPS;
private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
+ private long lastTick = 0;
+ private long catchupTime = 0;
private static final int SAMPLE_INTERVAL = 20;
public final RollingAverage tps1 = new RollingAverage(60);
public final RollingAverage tps5 = new RollingAverage(60 * 5);
@@ -519,106 +586,110 @@ public void safeShutdown() {
public static class RollingAverage {
private final int size;
private long time;
- private double total;
+ private BigDecimal total;
private int index = 0;
- private final double[] samples;
+ private final BigDecimal[] samples;
private final long[] times;
RollingAverage(int size) {
this.size = size;
this.time = size * SEC_IN_NANO;
- this.total = TPS * SEC_IN_NANO * size;
- this.samples = new double[size];
+ this.total = dec(TPS).multiply(dec(SEC_IN_NANO)).multiply(dec(size));
+ this.samples = new java.math.BigDecimal[size];
this.times = new long[size];
for (int i = 0; i < size; i++) {
- this.samples[i] = TPS;
+ this.samples[i] = dec(TPS);
this.times[i] = SEC_IN_NANO;
}
}
- public void add(double x, long t) {
+ private static BigDecimal dec(long t) {
+ return new BigDecimal(t);
+ }
+
+ public void add(BigDecimal x, long t) {
time -= times[index];
- total -= samples[index] * times[index];
+ total = total.subtract(samples[index].multiply(dec(times[index])));
samples[index] = x;
times[index] = t;
time += t;
- total += x * t;
+ total = total.add(x.multiply(dec(t)));
if (++index == size) {
index = 0;
}
}
public double getAverage() {
- return total / time;
+ return total.divide(dec(time), 30, RoundingMode.HALF_UP).doubleValue();
}
}
+ private static final BigDecimal TPS_BASE = new BigDecimal("1E9").multiply(new BigDecimal(SAMPLE_INTERVAL));
// PaperSpigot End
- public void run() {
+ public void runServer() {
try {
+ serverStartTime = getNanos(); // Paper
if (this.init()) {
- this.ab = az();
- long i = 0L;
this.r.setMOTD(new ChatComponentText(this.motd));
this.r.setServerInfo(new ServerPing.ServerData("1.8.8", 47));
this.a(this.r);
// Spigot start
- // PaperSpigot start - Further improve tick loop
+ // Paper start - move done tracking
+ LOGGER.info("Running delayed init tasks");
+ this.server.getScheduler().mainThreadHeartbeat(this.ticks); // run all 1 tick delay tasks during init,
+ // this is going to be the first thing the tick process does anyways, so move done and run it after
+ // everything is init before watchdog tick.
+ // anything at 3+ won't be caught here but also will trip watchdog....
+ // tasks are default scheduled at -1 + delay, and first tick will tick at 1
+ String doneTime = String.format(Locale.ROOT, "%.3fs", (double) (getNanos() - serverStartTime) / 1.0E9D);
+ LOGGER.info("Done ({})! For help, type \"help\" or \"?\"", doneTime);
+ // Paper end
+
+ WatchdogThread.tick(); // Paper
Arrays.fill( recentTps, 20 );
- //long lastTick = System.nanoTime(), catchupTime = 0, curTime, wait, tickSection = lastTick;
- long start = System.nanoTime(), lastTick = start - TICK_TIME, catchupTime = 0, curTime, wait, tickSection = start;
- // PaperSpigot end
+ long start = System.nanoTime(), curTime, tickSection = start; // Paper - Further improve server tick loop
+ lastTick = start - TICK_TIME; // Paper
while (this.isRunning) {
- curTime = System.nanoTime();
- // PaperSpigot start - Further improve tick loop
- wait = TICK_TIME - (curTime - lastTick);
- if (wait > 0) {
- // TacoSpigot start - fix the tick loop improvements
- if (catchupTime < 2E6) {
- wait += Math.abs(catchupTime);
- } else if (wait < catchupTime) {
- catchupTime -= wait;
- wait = 0;
- } else {
- wait -= catchupTime;
- catchupTime = 0;
- }
- // TacoSpigot end
- }
- if (wait > 0) {
- Thread.sleep(wait / 1000000);
- curTime = System.nanoTime();
- wait = TICK_TIME - (curTime - lastTick);
- }
+ long i = ((curTime = System.nanoTime()) / (1000L * 1000L)) - this.nextTickTime; // Paper
- catchupTime = Math.min(MAX_CATCHUP_BUFFER, catchupTime - wait);
+ if (i > 5000L && this.nextTickTime - this.lastOverloadWarning >= 30000L) { // CraftBukkit
+ long j = i / 50L;
- if ( ++MinecraftServer.currentTick % SAMPLE_INTERVAL == 0 )
- {
+ if (this.server.getWarnOnOverload()) // CraftBukkit
+ MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j);
+ this.nextTickTime += j * 50L;
+ this.lastOverloadWarning = this.nextTickTime;
+ }
+
+ if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
final long diff = curTime - tickSection;
- double currentTps = 1E9 / diff * SAMPLE_INTERVAL;
+ BigDecimal currentTps = TPS_BASE.divide(new BigDecimal(diff), 30, RoundingMode.HALF_UP);
tps1.add(currentTps, diff);
tps5.add(currentTps, diff);
tps15.add(currentTps, diff);
// Backwards compat with bad plugins
- recentTps[0] = tps1.getAverage();
- recentTps[1] = tps5.getAverage();
- recentTps[2] = tps15.getAverage();
+ this.recentTps[0] = tps1.getAverage();
+ this.recentTps[1] = tps5.getAverage();
+ this.recentTps[2] = tps15.getAverage();
+ // Paper end
tickSection = curTime;
- // PaperSpigot end
}
+ // Spigot end
+
lastTick = curTime;
+ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
+ this.nextTickTime += 50L;
+ this.methodProfiler.a("tick"); // push
+ this.A(this::haveTime);
+ this.methodProfiler.c("nextTickWait"); // popPush
+ this.mayHaveDelayedTasks = true;
+ this.delayedTasksMaxNextTickTime = Math.max(getMillis() + 50L, this.nextTickTime);
+ this.waitUntilNextTick();
+ this.methodProfiler.b(); // pop
+ this.isReady = true;
- // NachoSpigot start - backport tick events from Paper
- this.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.ticks+1));
- this.A();
- long endTime = System.nanoTime();
- long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
- this.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.ticks, ((double)(endTime - lastTick) / 1000000D), remaining));
- // NachoSpigot end
- this.Q = true;
}
// Spigot end
} else {
@@ -651,12 +722,12 @@ public void run() {
this.a(crashreport);
} finally {
try {
- org.spigotmc.WatchdogThread.doStop();
this.isStopped = true;
this.stop();
} catch (Throwable throwable1) {
MinecraftServer.LOGGER.error("Exception stopping the server", throwable1);
} finally {
+ org.spigotmc.WatchdogThread.doStop();
// CraftBukkit start - Restore terminal to original settings
try {
reader.getTerminal().restore();
@@ -670,33 +741,98 @@ public void run() {
}
- private void a(ServerPing serverping) {
- File file = this.d("server-icon.png");
+ private boolean haveTime() {
+ // CraftBukkit start
+ if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
+ return this.forceTicks || this.runningTask() || getMillis() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTime : this.nextTickTime);
+ }
- if (file.isFile()) {
- ByteBuf bytebuf = Unpooled.buffer();
- ByteBuf bytebuf1 = null; // Paper - cleanup favicon bytebuf
+ // Paper start
+ boolean isOversleep = false;
+ private boolean canOversleep() {
+ return this.mayHaveDelayedTasks && getMillis() < this.delayedTasksMaxNextTickTime;
+ }
+
+ private boolean canSleepForTickNoOversleep() {
+ return this.forceTicks || this.runningTask() || getMillis() < this.nextTickTime;
+ }
+ // Paper end
+
+ private void executeModerately() {
+ this.runAllRunnable();
+ LockSupport.parkNanos("executing tasks", 1000L);
+ }
+ // CraftBukkit end
+ protected void waitUntilNextTick() {
+ this.controlTerminate(() -> !this.canSleepForTickNoOversleep());
+ }
+ @Override
+ protected TasksPerTick packUpRunnable(Runnable runnable) {
+ // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
+ if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
+ runnable.run();
+ runnable = () -> {};
+ }
+ // Paper end
+ return new TasksPerTick(this.ticks, runnable);
+ }
+
+ @Override
+ protected boolean shouldRun(TasksPerTick task) {
+ return task.getTick() + 3 < this.ticks || this.haveTime();
+ }
+
+ @Override
+ public boolean drawRunnable() {
+ boolean flag = this.pollTaskInternal();
+
+ this.mayHaveDelayedTasks = flag;
+ return flag;
+ }
+
+ // TODO: WorldServer ticker
+ private boolean pollTaskInternal() {
+ if (super.drawRunnable()) {
+ return true;
+ } else {
+ if (this.haveTime()) {
+
+// for (WorldServer worldserver : this.worldServer) {
+// if (worldserver.chunkProviderServer.pollTask()) {
+// return true;
+// }
+// }
+ }
+
+ return false;
+ }
+ }
+
+ @Override
+ public Thread getMainThread() {
+ return serverThread;
+ }
+ private void a(ServerPing serverping) {
+ Optional optional = Optional.of(this.d("server-icon.png")).filter(File::isFile);
+
+ optional.ifPresent(file -> {
try {
BufferedImage bufferedimage = ImageIO.read(file);
- Validate.validState(bufferedimage.getWidth() == 64, "Must be 64 pixels wide", new Object[0]);
- Validate.validState(bufferedimage.getHeight() == 64, "Must be 64 pixels high", new Object[0]);
- ImageIO.write(bufferedimage, "PNG", new ByteBufOutputStream(bytebuf));
- /*ByteBuf*/ bytebuf1 = Base64.encode(bytebuf); // Paper - cleanup favicon bytebuf
+ Validate.validState(bufferedimage.getWidth() == 64, "Must be 64 pixels wide");
+ Validate.validState(bufferedimage.getHeight() == 64, "Must be 64 pixels high");
+ ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream();
+
+ ImageIO.write(bufferedimage, "PNG", bytearrayoutputstream);
+ byte[] abyte = java.util.Base64.getEncoder().encode(bytearrayoutputstream.toByteArray());
+ String s = new String(abyte, StandardCharsets.UTF_8);
- serverping.setFavicon("data:image/png;base64," + bytebuf1.toString(Charsets.UTF_8));
+ serverping.setFavicon("data:image/png;base64," + s);
} catch (Exception exception) {
- MinecraftServer.LOGGER.error("Couldn\'t load server icon", exception);
- } finally {
- bytebuf.release();
- // Paper start - cleanup favicon bytebuf
- if (bytebuf1 != null) {
- bytebuf1.release();
- }
- // Paper end - cleanup favicon bytebuf
+ MinecraftServer.LOGGER.error("Couldn't load server icon", exception);
}
- }
+ });
}
@@ -708,9 +844,18 @@ protected void a(CrashReport crashreport) {}
protected void z() {}
- protected void A() throws ExceptionWorldConflict { // CraftBukkit - added throws
+ protected void A(BooleanSupplier shouldKeepTicking) throws ExceptionWorldConflict { // CraftBukkit - added throws
co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Spigot
- long i = System.nanoTime();
+ long i = getNanos();
+
+ // Paper start - move oversleep into full server tick
+ isOversleep = true;
+ SpigotTimings.serverOversleep.startTiming();
+ this.controlTerminate(() -> !this.canOversleep());
+ isOversleep = false;
+ SpigotTimings.serverOversleep.stopTiming();
+ // Paper end
+ new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.ticks+1).callEvent(); // Paper
++this.ticks;
if (this.T) {
@@ -720,7 +865,7 @@ protected void A() throws ExceptionWorldConflict { // CraftBukkit - added throws
}
this.methodProfiler.a("root");
- this.B();
+ this.B(shouldKeepTicking);
if (i - this.X >= 5000000000L) {
this.X = i;
this.r.setPlayerSample(new ServerPing.ServerPingPlayerSample(this.J(), this.I()));
@@ -735,11 +880,11 @@ protected void A() throws ExceptionWorldConflict { // CraftBukkit - added throws
this.r.b().a(agameprofile);
}
- if (autosavePeriod > 0 /*&& this.ticks % autosavePeriod == 0*/) { // CraftBukkit // Paper - Incremental Auto Saving
+ if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - Incremental Auto Saving
SpigotTimings.worldSaveTimer.startTiming(); // Spigot
this.methodProfiler.a("save");
//this.playerList.savePlayers();
- if (this.ticks % autosavePeriod == 0) this.playerList.savePlayers(); // Paper - Incremental Auto Saving
+ this.playerList.savePlayers(); // Paper - Incremental Auto Saving
// Spigot Start
// We replace this with saving each individual world as this.saveChunks(...) is broken,
// and causes the main thread to sleep for random amounts of time depending on chunk activity
@@ -754,6 +899,11 @@ protected void A() throws ExceptionWorldConflict { // CraftBukkit - added throws
this.methodProfiler.b();
SpigotTimings.worldSaveTimer.stopTiming(); // Spigot
}
+ // Paper start
+ long endTime = System.nanoTime();
+ long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
+ new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.ticks, ((double) (endTime - lastTick) / 1000000D), remaining).callEvent();
+ // Paper end
this.methodProfiler.a("tallying");
this.h[this.ticks % 100] = System.nanoTime() - i;
@@ -773,27 +923,15 @@ protected void A() throws ExceptionWorldConflict { // CraftBukkit - added throws
co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Spigot
}
- public void B() {
- SpigotTimings.minecraftSchedulerTimer.startTiming(); // Spigot
+ public void B(BooleanSupplier shouldKeepTicking) {
+ SpigotTimings.bukkitSchedulerTimer.startTiming();
+ this.server.getScheduler().mainThreadHeartbeat(this.ticks);
+ SpigotTimings.bukkitSchedulerTimer.stopTiming();
this.methodProfiler.a("jobs");
- Queue queue = this.j;
-
- // Spigot start
- FutureTask> entry;
- int count = this.j.size();
- while (count-- > 0 && (entry = this.j.poll()) != null) {
- SystemUtils.a(entry, MinecraftServer.LOGGER);
- }
- // Spigot end
- SpigotTimings.minecraftSchedulerTimer.stopTiming(); // Spigot
this.methodProfiler.c("levels");
- SpigotTimings.bukkitSchedulerTimer.startTiming(); // Spigot
// CraftBukkit start
- this.server.getScheduler().mainThreadHeartbeat(this.ticks);
- SpigotTimings.bukkitSchedulerTimer.stopTiming(); // Spigot
-
// Run tasks that are waiting on processing
SpigotTimings.processQueueTimer.startTiming(); // Spigot
while (!processQueue.isEmpty()) {
@@ -810,8 +948,7 @@ public void B() {
// Paper start - optimize time updates
int i;
- if ((this.ticks % 20) == 0)
- {
+ if (this.ticks % 20 == 0) {
for (i = 0; i < this.worlds.size(); ++i) {
WorldServer world = this.worlds.get(i);
@@ -839,12 +976,12 @@ public void B() {
for (i = 0; i < this.worlds.size(); ++i) {
WorldServer worldserver = this.worlds.get(i);
this.methodProfiler.a(worldserver.getWorldData().getName());
- this.methodProfiler.a("tick");
+ this.methodProfiler.a("tick"); // push
CrashReport crashreport;
try {
worldserver.timings.doTick.startTiming(); // Spigot
- worldserver.doTick();
+ worldserver.doTick(shouldKeepTicking);
worldserver.timings.doTick.stopTiming(); // Spigot
} catch (Throwable throwable) {
// Spigot Start
@@ -879,7 +1016,7 @@ public void B() {
worldserver.timings.tracker.startTiming(); // Spigot
if(this.getPlayerList().getPlayerCount() != 0) // Tuinity
{
- // Tuinity start - controlled flush for entity tracker packets
+ // Paper start - controlled flush for entity tracker packets
List disabledFlushes = new java.util.ArrayList<>(this.getPlayerList().getPlayerCount());
for (EntityPlayer player : this.getPlayerList().players) {
PlayerConnection connection = player.playerConnection;
@@ -895,7 +1032,7 @@ public void B() {
networkManager.enableAutomaticFlush();
}
}
- // Tuinity end - controlled flush for entity tracker packets
+ // Paper end - controlled flush for entity tracker packets
}
worldserver.timings.tracker.stopTiming(); // Spigot
this.methodProfiler.b();
@@ -904,7 +1041,7 @@ public void B() {
worldserver.movementCache.clear(); // IonSpigot - Movement Cache
}
- this.methodProfiler.c("connection");
+ this.methodProfiler.c("connection"); // popPush
SpigotTimings.connectionTimer.startTiming(); // Spigot
this.aq().c();
SpigotTimings.connectionTimer.stopTiming(); // Spigot
@@ -920,7 +1057,7 @@ public void B() {
}
SpigotTimings.tickablesTimer.stopTiming(); // Spigot
- this.methodProfiler.b();
+ this.methodProfiler.b(); // pop
}
public boolean getAllowNether() {
@@ -931,115 +1068,6 @@ public void a(IUpdatePlayerListBox iupdateplayerlistbox) {
this.p.add(iupdateplayerlistbox);
}
- public static void main(final OptionSet options) { // CraftBukkit - replaces main(String[] astring)
- DispenserRegistry.c();
-
- try {
- /* CraftBukkit start - Replace everything
- boolean flag = true;
- String s = null;
- String s1 = ".";
- String s2 = null;
- boolean flag1 = false;
- boolean flag2 = false;
- int i = -1;
-
- for (int j = 0; j < astring.length; ++j) {
- String s3 = astring[j];
- String s4 = j == astring.length - 1 ? null : astring[j + 1];
- boolean flag3 = false;
-
- if (!s3.equals("nogui") && !s3.equals("--nogui")) {
- if (s3.equals("--port") && s4 != null) {
- flag3 = true;
-
- try {
- i = Integer.parseInt(s4);
- } catch (NumberFormatException numberformatexception) {
- ;
- }
- } else if (s3.equals("--singleplayer") && s4 != null) {
- flag3 = true;
- s = s4;
- } else if (s3.equals("--universe") && s4 != null) {
- flag3 = true;
- s1 = s4;
- } else if (s3.equals("--world") && s4 != null) {
- flag3 = true;
- s2 = s4;
- } else if (s3.equals("--demo")) {
- flag1 = true;
- } else if (s3.equals("--bonusChest")) {
- flag2 = true;
- }
- } else {
- flag = false;
- }
-
- if (flag3) {
- ++j;
- }
- }
-
- final DedicatedServer dedicatedserver = new DedicatedServer(new File(s1));
-
- if (s != null) {
- dedicatedserver.i(s);
- }
-
- if (s2 != null) {
- dedicatedserver.setWorld(s2);
- }
-
- if (i >= 0) {
- dedicatedserver.setPort(i);
- }
-
- if (flag1) {
- dedicatedserver.b(true);
- }
-
- if (flag2) {
- dedicatedserver.c(true);
- }
-
- if (flag && !GraphicsEnvironment.isHeadless()) {
- dedicatedserver.aQ();
- }
-
- dedicatedserver.D();
- Runtime.getRuntime().addShutdownHook(new Thread("Server Shutdown Thread") {
- public void run() {
- dedicatedserver.stop();
- }
- });
- */
-
- DedicatedServer dedicatedserver = new DedicatedServer(options);
-
- if (options.has("port")) {
- int port = (Integer) options.valueOf("port");
- if (port > 0) {
- dedicatedserver.setPort(port);
- }
- }
-
- if (options.has("universe")) {
- dedicatedserver.universe = (File) options.valueOf("universe");
- }
-
- if (options.has("world")) {
- dedicatedserver.setWorld((String) options.valueOf("world"));
- }
-
- dedicatedserver.primaryThread.start();
- // CraftBukkit end
- } catch (Exception exception) {
- MinecraftServer.LOGGER.fatal("Failed to start the minecraft server", exception);
- }
-
- }
-
public void C() {
/* CraftBukkit start - prevent abuse
this.serverThread = new Thread(this, "Server thread");
@@ -1510,7 +1538,15 @@ public Proxy ay() {
}
public static long az() {
- return System.currentTimeMillis();
+ return getMillis();
+ }
+
+ public static long getMillis() {
+ return getNanos() / 1000000L;
+ }
+
+ public static long getNanos() {
+ return System.nanoTime(); // Paper
}
public int getIdleTimeout() {
@@ -1550,12 +1586,8 @@ public void aH() {
}
public Entity a(UUID uuid) {
- WorldServer[] aworldserver = this.worldServer;
- int i = aworldserver.length;
-
// CraftBukkit start
- for (int j = 0; j < worlds.size(); ++j) {
- WorldServer worldserver = worlds.get(j);
+ for (WorldServer worldserver : worlds) {
// CraftBukkit end
if (worldserver != null) {
@@ -1580,32 +1612,10 @@ public int aI() {
return 29999984;
}
- public ListenableFuture a(Callable callable) {
- Validate.notNull(callable);
- if (!this.isMainThread()) { // CraftBukkit && !this.isStopped()) {
- ListenableFutureTask listenablefuturetask = ListenableFutureTask.create(callable);
- Queue queue = this.j;
-
- // Spigot start
- this.j.add(listenablefuturetask);
- return listenablefuturetask;
- // Spigot end
- } else {
- try {
- return Futures.immediateFuture(callable.call());
- } catch (Exception exception) {
- return Futures.immediateFailedFuture(exception);
- }
- }
- }
-
public ListenableFuture postToMainThread(Runnable runnable) {
Validate.notNull(runnable);
- return this.a(Executors.callable(runnable));
- }
-
- public boolean isMainThread() {
- return Thread.currentThread() == this.serverThread;
+ execute(runnable);
+ return null;
}
public int aK() {
@@ -1613,7 +1623,7 @@ public int aK() {
}
public long aL() {
- return this.ab;
+ return this.nextTickTime;
}
public Thread aM() {
diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java
index b30367c93..1c8e91f51 100644
--- a/NachoSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java
@@ -39,7 +39,6 @@ public class NetworkManager extends SimpleChannelInboundHandler> {
private final EnumProtocolDirection h;
private final Queue i = Queues.newConcurrentLinkedQueue();
- private final ReentrantReadWriteLock j = new ReentrantReadWriteLock();
public Channel channel;
// Spigot Start // PAIL
public SocketAddress l;
@@ -51,6 +50,9 @@ public class NetworkManager extends SimpleChannelInboundHandler> {
private IChatBaseComponent n;
private boolean encrypted; // Nacho - deobfuscate
private boolean isDisconnectionHandled; // Nacho - deobfuscate
+ // Optimize network
+ public boolean isPending = true;
+ public EnumProtocol protocol;
// Tuinity start - allow controlled flushing
volatile boolean canFlush = true;
@@ -75,11 +77,14 @@ void enableAutomaticFlush() {
}
}
- private void flush()
- {
+ private void flush() {
if (this.channel.eventLoop().inEventLoop()) {
this.channel.flush();
- } //[Nacho-Spigot] Fixed RejectedExecutionException: event executor terminated by BeyazPolis
+ } else {
+ this.channel.eventLoop().execute(() -> {
+ this.channel.flush();
+ });
+ }
}
// Tuinity end - allow controlled flushing
@@ -108,6 +113,7 @@ public void setProtocol(EnumProtocol protocol) {
}
public void a(EnumProtocol protocol) {
+ this.protocol = protocol; // Paper
this.channel.attr(NetworkManager.ATTRIBUTE_PROTOCOL).set(protocol);
this.channel.config().setAutoRead(true);
}
@@ -170,35 +176,23 @@ public void a(PacketListener packetlistener) {
}
//sendPacket
- public void handle(Packet packet) {
+ public void handle(Packet> packet) {
if (this.isConnected()) {
- this.sendPacketQueue();
+ this.flushQueue();
this.dispatchPacket(packet, null, Boolean.TRUE);
} else {
- this.j.writeLock().lock();
-
- try {
- this.i.add(new NetworkManager.QueuedPacket(packet));
- } finally {
- this.j.writeLock().unlock();
- }
+ this.i.add(new NetworkManager.QueuedPacket(packet));
}
}
//sendPacket
- public void a(Packet packet, GenericFutureListener extends Future super Void>> listener, GenericFutureListener extends Future super Void>>... listeners) {
+ public void a(Packet> packet, GenericFutureListener extends Future super Void>> listener, GenericFutureListener extends Future super Void>>... listeners) {
if (this.isConnected()) {
- this.sendPacketQueue();
+ this.flushQueue();
this.dispatchPacket(packet, ArrayUtils.insert(0, listeners, listener), Boolean.TRUE);
} else {
- this.j.writeLock().lock();
-
- try {
- this.i.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.insert(0, listeners, listener)));
- } finally {
- this.j.writeLock().unlock();
- }
+ this.i.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.insert(0, listeners, listener)));
}
}
@@ -218,7 +212,7 @@ public void dispatchPacket(final Packet> packet, final GenericFutureListener
boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional;
final boolean flush = effectiveFlush || packet instanceof PacketPlayOutKeepAlive || packet instanceof PacketPlayOutKickDisconnect; // no delay for certain packets
final EnumProtocol enumprotocol = EnumProtocol.getProtocolForPacket(packet);
- final EnumProtocol enumprotocol1 = this.channel.attr(NetworkManager.ATTRIBUTE_PROTOCOL).get();
+ final EnumProtocol enumprotocol1 = getCurrentProtocol();
if (enumprotocol1 != enumprotocol) {
this.channel.config().setAutoRead(false);
}
@@ -233,85 +227,87 @@ public void dispatchPacket(final Packet> packet, final GenericFutureListener
channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
else {
- // Tuinity start - optimise packets that are not flushed
- Runnable choice1 = null;
- AbstractEventExecutor.LazyRunnable choice2 = null;
+ // Paper start - optimise packets that are not flushed
// note: since the type is not dynamic here, we need to actually copy the old executor code
// into two branches. On conflict, just re-copy - no changes were made inside the executor code.
- if (flush) {
- choice1 = () -> {
- if (enumprotocol != enumprotocol1) {
- this.setProtocol(enumprotocol);
- }
- try {
- ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter
- if (listeners != null) {
- channelfuture1.addListeners(listeners);
- }
- channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
- } catch (Exception e) {
- LOGGER.error("NetworkException: " + getPlayer(), e);
- close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
- }
- };
- } else {
- // explicitly declare a variable to make the lambda use the type
- choice2 = () -> {
- if (enumprotocol != enumprotocol1) {
- this.setProtocol(enumprotocol);
- }
- try {
- // Nacho - why not remove the check below if the check is done above? just code duplication...
- // even IntelliJ screamed at me for doing leaving it like that :shrug:
- ChannelFuture channelfuture1 = /* (flush) ? this.channel.writeAndFlush(packet) : */this.channel.write(packet); // Nacho - see above // Tuinity - add flush parameter
- if (listeners != null) {
- channelfuture1.addListeners(listeners);
- }
- channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
- } catch (Exception e) {
- LOGGER.error("NetworkException: " + getPlayer(), e);
- close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
- }
+ if (!flush) {
+ AbstractEventExecutor.LazyRunnable run = () -> {
+ this.addListener(packet, enumprotocol, enumprotocol1, flush, listeners); // Paper - add flush parameter
};
+ this.channel.eventLoop().execute(run);
+ } else { // Paper end - optimise packets that are not flushed
+ this.channel.eventLoop().execute(() -> {
+ this.addListener(packet, enumprotocol, enumprotocol1, flush, listeners); // Paper - add flush parameter // Paper - diff on change
+ });
+ } // Paper
+ }
+ }
+
+ @SafeVarargs
+ private final void addListener(Packet> packet, EnumProtocol enumProtocol1, EnumProtocol enumProtocol2, boolean flush, GenericFutureListener extends Future super Void>>... callback) {
+ if (enumProtocol1 != enumProtocol2) {
+ this.setProtocol(enumProtocol1);
+ }
+
+ EntityPlayer player = getPlayer();
+ try {
+ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Paper - add flush parameter
+
+ if (callback != null) {
+ channelfuture.addListeners(callback);
}
- this.channel.eventLoop().execute(choice1 != null ? choice1 : choice2);
- // Tuinity end - optimise packets that are not flushed
+ channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
+ } catch (Exception e) {
+ LOGGER.error("NetworkException: " + player, e);
+ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));
}
}
- private void a(final Packet packet, final GenericFutureListener extends Future super Void>>[] agenericfuturelistener) {
+ private EnumProtocol getCurrentProtocol() {
+ return this.channel.attr(NetworkManager.c).get();
+ }
+
+ private void a(final Packet> packet, final GenericFutureListener extends Future super Void>>[] agenericfuturelistener) {
this.dispatchPacket(packet, agenericfuturelistener, Boolean.TRUE);
}
- private void sendPacketQueue() {
- if(this.i.isEmpty()) return; // [Nacho-0019] :: Avoid lock every packet send
- if (this.channel != null && this.channel.isOpen()) {
- this.j.readLock().lock();
- boolean needsFlush = this.canFlush;
- boolean hasWrotePacket = false;
- try {
- Iterator iterator = this.i.iterator();
- while (iterator.hasNext()) {
- QueuedPacket queued = iterator.next();
- Packet packet = queued.a;
- if (hasWrotePacket && (needsFlush || this.canFlush)) flush();
- iterator.remove();
- this.dispatchPacket(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE);
- hasWrotePacket = true;
- }
- } finally {
- this.j.readLock().unlock();
+ // Paper start - rewrite this to be safer if ran off main thread
+ private void flushQueue() { // void -> boolean
+ if (MinecraftServer.getServer().isMainThread()) {
+ processQueue();
+ } else if (isPending) {
+ // Should only happen during login/status stages
+ synchronized (this.i) {
+ this.processQueue();
}
}
}
-
- private void m()
- {
- this.sendPacketQueue();
+ private void processQueue() {
+ if(this.i.isEmpty()) return;
+ // Paper start - make only one flush call per sendPacketQueue() call
+ final boolean needsFlush = this.canFlush;
+ boolean hasWrotePacket = false;
+ // Paper end - make only one flush call per sendPacketQueue() call
+ // If we are on main, we are safe here in that nothing else should be processing queue off main anymore
+ // But if we are not on main due to login/status, the parent is synchronized on packetQueue
+ java.util.Iterator iterator = this.i.iterator();
+ while (iterator.hasNext()) {
+ QueuedPacket queued = iterator.next(); // poll -> peek
+
+ Packet> packet = queued.a;
+ // Paper start - make only one flush call per sendPacketQueue() call
+ if (hasWrotePacket && (needsFlush || this.canFlush)) this.flush();
+
+ // Paper end - make only one flush call per sendPacketQueue() call
+ iterator.remove();
+ this.dispatchPacket(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Paper - make only one flush call per sendPacketQueue() call
+ hasWrotePacket = true; // Paper - make only one flush call per sendPacketQueue() call
+ }
}
+ // Paper end
public void tick() {
- this.sendPacketQueue();
+ this.flushQueue();
if (this.m instanceof IUpdatePlayerListBox) {
((IUpdatePlayerListBox) this.m).c();
}
@@ -451,11 +447,11 @@ protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet
}
static class QueuedPacket {
- private final Packet a; //packet
+ private final Packet> a; //packet
private final GenericFutureListener extends Future super Void>>[] b; //listener
@SafeVarargs
- public QueuedPacket(Packet packet, GenericFutureListener extends Future super Void>> ...listeners) {
+ public QueuedPacket(Packet> packet, GenericFutureListener extends Future super Void>> ...listeners) {
this.a = packet;
this.b = listeners;
}
diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java
index b244df8fa..53a0f5ebe 100644
--- a/NachoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java
+++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java
@@ -53,6 +53,7 @@ private void addPending() {
NetworkManager manager;
while ((manager = pending.poll()) != null) {
this.connectedChannels.add(manager); // Nacho - deobfuscate connectedChannels
+ manager.isPending = false;
}
}
// Paper end
diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java
index 4b8f45dc1..43b6dd0a1 100644
--- a/NachoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java
+++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java
@@ -10,6 +10,7 @@
// CraftBukkit start
import java.util.*;
+import java.util.function.BooleanSupplier;
import java.util.logging.Level;
import org.bukkit.WeatherType;
@@ -195,7 +196,7 @@ private boolean canSpawn(int x, int z) {
}
// CraftBukkit end
- public void doTick() {
+ public void doTick(BooleanSupplier booleanSupplier) {
super.doTick();
if (this.getWorldData().isHardcore() && this.getDifficulty() != EnumDifficulty.HARD) {
this.getWorldData().setDifficulty(EnumDifficulty.HARD);
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 259c85ee1..79837550f 100644
--- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -1698,7 +1698,7 @@ public int getAmbientSpawnLimit() {
@Override
public boolean isPrimaryThread() {
- return Thread.currentThread().equals(console.primaryThread);
+ return Thread.currentThread().equals(console.serverThread);
}
@Override
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/Main.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/Main.java
index 6b62211ba..d8d64458c 100644
--- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -11,6 +11,8 @@
import dev.cobblesword.nachospigot.Nacho;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
+import net.minecraft.server.DedicatedServer;
+import net.minecraft.server.DispenserRegistry;
import net.minecraft.server.MinecraftServer;
import org.apache.commons.lang3.JavaVersion;
@@ -234,7 +236,29 @@ public static void main(String[] args) {
// Spigot End
Nacho.LOGGER.info("Loading libraries, please wait...");
net.techcable.tacospigot.TacoSpigotConfig.init((File) options.valueOf("taco-settings")); // TacoSpigot - load config before we load libraries to allow access while loading
- MinecraftServer.main(options);
+
+ DispenserRegistry.c();
+ OptionSet finalOptions = options;
+
+ DedicatedServer server = MinecraftServer.spin(thread -> {
+ DedicatedServer dedicatedserver = new DedicatedServer(finalOptions, thread);
+
+ if (finalOptions.has("port")) {
+ int port = (Integer) finalOptions.valueOf("port");
+ if (port > 0) {
+ dedicatedserver.setPort(port);
+ }
+ }
+
+ if (finalOptions.has("universe")) {
+ dedicatedserver.universe = (File) finalOptions.valueOf("universe");
+ }
+
+ if (finalOptions.has("world")) {
+ dedicatedserver.setWorld((String) finalOptions.valueOf("world"));
+ }
+ return dedicatedserver;
+ });
} catch (Throwable t) {
t.printStackTrace();
}
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java
new file mode 100644
index 000000000..fb00324d9
--- /dev/null
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.bukkit.craftbukkit.scheduler;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.github.paperspigot.ServerSchedulerReportingWrapper;
+import org.github.paperspigot.event.ServerExceptionEvent;
+import org.github.paperspigot.exception.ServerSchedulerException;
+import org.bukkit.plugin.Plugin;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.*;
+
+public class CraftAsyncScheduler extends CraftScheduler {
+
+ private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(),
+ new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build());
+ private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
+ .setNameFormat("Craft Async Scheduler Management Thread").build());
+ private final List temp = new ArrayList<>();
+
+ CraftAsyncScheduler() {
+ super(true);
+ executor.allowCoreThreadTimeOut(true);
+ executor.prestartAllCoreThreads();
+ }
+
+ @Override
+ public void cancelTask(int taskId) {
+ this.management.execute(() -> this.removeTask(taskId));
+ }
+
+ private synchronized void removeTask(int taskId) {
+ parsePending();
+ this.pending.removeIf((task) -> {
+ if (task.getTaskId() == taskId) {
+ task.cancel0();
+ return true;
+ }
+ return false;
+ });
+ }
+
+ @Override
+ public void mainThreadHeartbeat(int currentTick) {
+ this.currentTick = currentTick;
+ this.management.execute(() -> this.runTasks(currentTick));
+ }
+
+ private synchronized void runTasks(int currentTick) {
+ parsePending();
+ while (!this.pending.isEmpty() && this.pending.peek().getNextRun() <= currentTick) {
+ CraftTask task = this.pending.remove();
+ if (executeTask(task)) {
+ final long period = task.getPeriod();
+ if (period > 0) {
+ task.setNextRun(currentTick + period);
+ temp.add(task);
+ }
+ }
+ parsePending();
+ }
+ this.pending.addAll(temp);
+ temp.clear();
+ }
+
+ private boolean executeTask(CraftTask task) {
+ if (isValid(task)) {
+ this.runners.put(task.getTaskId(), task);
+ this.executor.execute(new ServerSchedulerReportingWrapper(task));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public synchronized void cancelTasks(Plugin plugin) {
+ parsePending();
+ for (Iterator iterator = this.pending.iterator(); iterator.hasNext(); ) {
+ CraftTask task = iterator.next();
+ if (task.getTaskId() != -1 && (plugin == null || task.getOwner().equals(plugin))) {
+ task.cancel0();
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Task is not cancelled
+ * @param runningTask
+ * @return
+ */
+ static boolean isValid(CraftTask runningTask) {
+ return runningTask.getPeriod() >= CraftTask.NO_REPEATING;
+ }
+}
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
index f3da84a92..548fdb07f 100644
--- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
@@ -1,20 +1,20 @@
package org.bukkit.craftbukkit.scheduler;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-
import org.apache.commons.lang.UnhandledException;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitWorker;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
class CraftAsyncTask extends CraftTask {
- private final LinkedList workers = new LinkedList();
+ private final LinkedList workers = new LinkedList<>();
private final Map runners;
- CraftAsyncTask(final Map runners, final Plugin plugin, final Runnable task, final int id, final long delay) {
+ CraftAsyncTask(final Map runners, final Plugin plugin, final Object task, final int id, final long delay) {
super(plugin, task, id, delay);
this.runners = runners;
}
@@ -28,7 +28,7 @@ public boolean isSync() {
public void run() {
final Thread thread = Thread.currentThread();
synchronized(workers) {
- if (getPeriod() == -2) {
+ if (getPeriod() == CraftTask.CANCEL) {
// Never continue running after cancelled.
// Checking this with the lock is important!
return;
@@ -99,7 +99,7 @@ LinkedList getWorkers() {
boolean cancel0() {
synchronized (workers) {
// Synchronizing here prevents race condition for a completing task
- setPeriod(-2l);
+ setPeriod(CraftTask.CANCEL);
if (workers.isEmpty()) {
runners.remove(getTaskId());
}
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java
index 1baec564c..3fa618c2a 100644
--- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java
@@ -1,14 +1,9 @@
package org.bukkit.craftbukkit.scheduler;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
import org.bukkit.plugin.Plugin;
+import java.util.concurrent.*;
+
class CraftFuture extends CraftTask implements Future {
private final Callable callable;
@@ -16,27 +11,26 @@ class CraftFuture extends CraftTask implements Future {
private Exception exception = null;
CraftFuture(final Callable callable, final Plugin plugin, final int id) {
- super(plugin, null, id, -1l);
+ super(plugin, null, id, CraftTask.NO_REPEATING);
this.callable = callable;
}
+ @Override
public synchronized boolean cancel(final boolean mayInterruptIfRunning) {
- if (getPeriod() != -1l) {
+ if (getPeriod() != CraftTask.NO_REPEATING) {
return false;
}
- setPeriod(-2l);
+ setPeriod(CraftTask.CANCEL);
return true;
}
- public boolean isCancelled() {
- return getPeriod() == -2l;
- }
-
+ @Override
public boolean isDone() {
final long period = this.getPeriod();
- return period != -1l && period != -3l;
+ return period != CraftTask.NO_REPEATING && period != CraftTask.PROCESS_FOR_FUTURE;
}
+ @Override
public T get() throws CancellationException, InterruptedException, ExecutionException {
try {
return get(0, TimeUnit.MILLISECONDS);
@@ -45,16 +39,17 @@ public T get() throws CancellationException, InterruptedException, ExecutionExce
}
}
+ @Override
public synchronized T get(long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
timeout = unit.toMillis(timeout);
long period = this.getPeriod();
- long timestamp = timeout > 0 ? System.currentTimeMillis() : 0l;
+ long timestamp = timeout > 0 ? System.currentTimeMillis() : 0L;
while (true) {
- if (period == -1l || period == -3l) {
+ if (period == CraftTask.NO_REPEATING || period == CraftTask.PROCESS_FOR_FUTURE) {
this.wait(timeout);
period = this.getPeriod();
- if (period == -1l || period == -3l) {
- if (timeout == 0l) {
+ if (period == CraftTask.NO_REPEATING || period == CraftTask.PROCESS_FOR_FUTURE) {
+ if (timeout == 0L) {
continue;
}
timeout += timestamp - (timestamp = System.currentTimeMillis());
@@ -64,26 +59,26 @@ public synchronized T get(long timeout, final TimeUnit unit) throws InterruptedE
throw new TimeoutException();
}
}
- if (period == -2l) {
+ if (period == CraftTask.CANCEL) {
throw new CancellationException();
}
- if (period == -4l) {
+ if (period == CraftTask.DONE_FOR_FUTURE) {
if (exception == null) {
return value;
}
throw new ExecutionException(exception);
}
- throw new IllegalStateException("Expected " + -1l + " to " + -4l + ", got " + period);
+ throw new IllegalStateException("Expected " + CraftTask.NO_REPEATING + " to " + CraftTask.DONE_FOR_FUTURE + ", got " + period);
}
}
@Override
public void run() {
synchronized (this) {
- if (getPeriod() == -2l) {
+ if (getPeriod() == CraftTask.CANCEL) {
return;
}
- setPeriod(-3l);
+ setPeriod(CraftTask.PROCESS_FOR_FUTURE);
}
try {
value = callable.call();
@@ -91,17 +86,18 @@ public void run() {
exception = e;
} finally {
synchronized (this) {
- setPeriod(-4l);
+ setPeriod(CraftTask.DONE_FOR_FUTURE);
this.notifyAll();
}
}
}
+ @Override
synchronized boolean cancel0() {
- if (getPeriod() != -1l) {
+ if (getPeriod() != CraftTask.NO_REPEATING) {
return false;
}
- setPeriod(-2l);
+ setPeriod(CraftTask.CANCEL);
notifyAll();
return true;
}
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
index 9dae21518..97d361679 100644
--- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
@@ -1,19 +1,6 @@
package org.bukkit.craftbukkit.scheduler;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.PriorityQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.logging.Level;
-
+import co.aikar.timings.SpigotTimings;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.IllegalPluginAccessException;
import org.bukkit.plugin.Plugin;
@@ -21,6 +8,18 @@
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scheduler.BukkitWorker;
+import org.github.paperspigot.event.ServerExceptionEvent;
+import org.github.paperspigot.exception.ServerSchedulerException;
+
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.logging.Level;
/**
* The fundamental concepts for this implementation:
@@ -32,7 +31,7 @@
* Changing the period on a task is delicate.
* Any future task needs to notify waiting threads.
* Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}.
- * Another utility method is provided for this, cancelTask(CraftTask)
+ * Another utility method is provided for this, {@link #cancelTask(int)}
* {@link #runners} provides a moderately up-to-date view of active tasks.
* If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.
* Async tasks are responsible for removing themselves from runners
@@ -42,27 +41,25 @@
*/
public class CraftScheduler implements BukkitScheduler {
+ static Plugin MINECRAFT = new MinecraftInternalPlugin();
/**
* Counter for IDs. Order doesn't matter, only uniqueness.
*/
private final AtomicInteger ids = new AtomicInteger(1);
/**
- * Current head of linked-list. This reference is always stale, CraftTask#next is the live reference.
+ * Current head of linked-list. This reference is always stale, {@link CraftTask#next} is the live reference.
*/
private volatile CraftTask head = new CraftTask();
/**
* Tail of a linked-list. AtomicReference only matters when adding to queue
*/
private final AtomicReference tail = new AtomicReference(head);
+ // If the tasks should run on the same tick they should be run FIFO
/**
* Main thread logic only
*/
- private final PriorityQueue pending = new PriorityQueue(10,
- new Comparator() {
- public int compare(final CraftTask o1, final CraftTask o2) {
- return (int) (o1.getNextRun() - o2.getNextRun());
- }
- });
+ final PriorityQueue pending = new PriorityQueue(10, // Paper
+ Comparator.comparingLong(CraftTask::getNextRun).thenComparingInt(CraftTask::getTaskId));
/**
* Main thread logic only
*/
@@ -70,103 +67,196 @@ public int compare(final CraftTask o1, final CraftTask o2) {
/**
* These are tasks that are currently active. It's provided for 'viewing' the current state.
*/
- private final ConcurrentHashMap runners = new ConcurrentHashMap();
- private volatile int currentTick = -1;
- private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot
- private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}};
+ final ConcurrentHashMap runners = new ConcurrentHashMap(); // Paper
+ /**
+ * The sync task that is currently running on the main thread.
+ */
+ private volatile CraftTask currentTask = null;
+ // Paper start - Improved Async Task Scheduler
+ volatile int currentTick = -1;/*
+ private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build());
+ private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {
+ @Override
+ StringBuilder debugTo(StringBuilder string) {
+ return string;
+ }
+ };
private CraftAsyncDebugger debugTail = debugHead;
+
+ */ // Paper end
private static final int RECENT_TICKS;
static {
RECENT_TICKS = 30;
}
+
+ // Paper start
+ private final CraftScheduler asyncScheduler;
+ private final boolean isAsyncScheduler;
+ public CraftScheduler() {
+ this(false);
+ }
+
+ public CraftScheduler(boolean isAsync) {
+ this.isAsyncScheduler = isAsync;
+ if (isAsync) {
+ this.asyncScheduler = this;
+ } else {
+ this.asyncScheduler = new CraftAsyncScheduler();
+ }
+ }
+ // Paper end
+ @Override
public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) {
- return this.scheduleSyncDelayedTask(plugin, task, 0l);
+ return this.scheduleSyncDelayedTask(plugin, task, 0L);
}
+ @Override
public BukkitTask runTask(Plugin plugin, Runnable runnable) {
- return runTaskLater(plugin, runnable, 0l);
+ return runTaskLater(plugin, runnable, 0L);
+ }
+
+ @Override
+ public void runTask(Plugin plugin, Consumer task) throws IllegalArgumentException {
+ runTaskLater(plugin, task, 0L);
}
@Deprecated
+ @Override
public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task) {
- return this.scheduleAsyncDelayedTask(plugin, task, 0l);
+ return this.scheduleAsyncDelayedTask(plugin, task, 0L);
}
+ @Override
public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
- return runTaskLaterAsynchronously(plugin, runnable, 0l);
+ return runTaskLaterAsynchronously(plugin, runnable, 0L);
+ }
+
+ @Override
+ public void runTaskAsynchronously(Plugin plugin, Consumer task) throws IllegalArgumentException {
+ runTaskLaterAsynchronously(plugin, task, 0L);
}
+ @Override
public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) {
- return this.scheduleSyncRepeatingTask(plugin, task, delay, -1l);
+ return this.scheduleSyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING);
}
+ @Override
public BukkitTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
- return runTaskTimer(plugin, runnable, delay, -1l);
+ return runTaskTimer(plugin, runnable, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public void runTaskLater(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException {
+ runTaskTimer(plugin, task, delay, CraftTask.NO_REPEATING);
}
@Deprecated
+ @Override
public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) {
- return this.scheduleAsyncRepeatingTask(plugin, task, delay, -1l);
+ return this.scheduleAsyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING);
}
+ @Override
public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
- return runTaskTimerAsynchronously(plugin, runnable, delay, -1l);
+ return runTaskTimerAsynchronously(plugin, runnable, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public void runTaskLaterAsynchronously(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException {
+ runTaskTimerAsynchronously(plugin, task, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException {
+ runTaskTimerAsynchronously(plugin, (Object) task, delay, period);
}
+ @Override
public int scheduleSyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) {
return runTaskTimer(plugin, runnable, delay, period).getTaskId();
}
+ @Override
public BukkitTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
+ return runTaskTimer(plugin, (Object) runnable, delay, period);
+ }
+
+ @Override
+ public void runTaskTimer(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException {
+ runTaskTimer(plugin, (Object) task, delay, period);
+ }
+
+ public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) {
+ final CraftTask task = new CraftTask(run, nextId(), "Internal - " + (taskName != null ? taskName : "Unknown"));
+ task.internal = true;
+ return handle(task, delay);
+ }
+
+ public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) {
validate(plugin, runnable);
- if (delay < 0l) {
+ if (delay < 0L) {
delay = 0;
}
- if (period == 0l) {
- period = 1l;
- } else if (period < -1l) {
- period = -1l;
+ if (period == CraftTask.ERROR) {
+ period = 1L;
+ } else if (period < CraftTask.NO_REPEATING) {
+ period = CraftTask.NO_REPEATING;
}
return handle(new CraftTask(plugin, runnable, nextId(), period), delay);
}
@Deprecated
+ @Override
public int scheduleAsyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) {
return runTaskTimerAsynchronously(plugin, runnable, delay, period).getTaskId();
}
+ @Override
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
+ return runTaskTimerAsynchronously(plugin, (Object) runnable, delay, period);
+ }
+
+ public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Object runnable, long delay, long period) {
validate(plugin, runnable);
- if (delay < 0l) {
+ if (delay < 0L) {
delay = 0;
}
- if (period == 0l) {
- period = 1l;
- } else if (period < -1l) {
- period = -1l;
+ if (period == CraftTask.ERROR) {
+ period = 1L;
+ } else if (period < CraftTask.NO_REPEATING) {
+ period = CraftTask.NO_REPEATING;
}
- return handle(new CraftAsyncTask(runners, plugin, runnable, nextId(), period), delay);
+ return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper
}
+ @Override
public Future callSyncMethod(final Plugin plugin, final Callable task) {
validate(plugin, task);
final CraftFuture future = new CraftFuture(task, plugin, nextId());
- handle(future, 0l);
+ handle(future, 0L);
return future;
}
+ @Override
public void cancelTask(final int taskId) {
if (taskId <= 0) {
return;
}
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ this.asyncScheduler.cancelTask(taskId);
+ }
+ // Paper end
CraftTask task = runners.get(taskId);
if (task != null) {
task.cancel0();
}
task = new CraftTask(
new Runnable() {
+ @Override
public void run() {
if (!check(CraftScheduler.this.temp)) {
check(CraftScheduler.this.pending);
@@ -186,8 +276,9 @@ private boolean check(final Iterable collection) {
}
}
return false;
- }}){{this.timings=co.aikar.timings.SpigotTimings.getCancelTasksTimer();}}; // Spigot
- handle(task, 0l);
+ }
+ }){{this.timings=SpigotTimings.getCancelTasksTimer();}}; // Paper
+ handle(task, 0L);
for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
if (taskPending == task) {
return;
@@ -198,10 +289,17 @@ private boolean check(final Iterable collection) {
}
}
+ @Override
public void cancelTasks(final Plugin plugin) {
Validate.notNull(plugin, "Cannot cancel tasks of null plugin");
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ this.asyncScheduler.cancelTasks(plugin);
+ }
+ // Paper end
final CraftTask task = new CraftTask(
new Runnable() {
+ @Override
public void run() {
check(CraftScheduler.this.pending);
check(CraftScheduler.this.temp);
@@ -219,11 +317,11 @@ void check(final Iterable collection) {
}
}
}
- }){{this.timings=co.aikar.timings.SpigotTimings.getCancelTasksTimer(plugin);}}; // Spigot
- handle(task, 0l);
+ }){{this.timings=SpigotTimings.getCancelTasksTimer(plugin);}}; // Paper
+ handle(task, 0L);
for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
if (taskPending == task) {
- return;
+ break;
}
if (taskPending.getTaskId() != -1 && taskPending.getOwner().equals(plugin)) {
taskPending.cancel0();
@@ -236,59 +334,55 @@ void check(final Iterable collection) {
}
}
- public void cancelAllTasks() {
- final CraftTask task = new CraftTask(
- new Runnable() {
- public void run() {
- Iterator it = CraftScheduler.this.runners.values().iterator();
- while (it.hasNext()) {
- CraftTask task = it.next();
- task.cancel0();
- if (task.isSync()) {
- it.remove();
- }
- }
- CraftScheduler.this.pending.clear();
- CraftScheduler.this.temp.clear();
- }
- }){{this.timings=co.aikar.timings.SpigotTimings.getCancelTasksTimer();}}; // Spigot
- handle(task, 0l);
- for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
- if (taskPending == task) {
- break;
+ @Override
+ public boolean isCurrentlyRunning(final int taskId) {
+ // Paper start
+ if (!isAsyncScheduler) {
+ if (this.asyncScheduler.isCurrentlyRunning(taskId)) {
+ return true;
}
- taskPending.cancel0();
}
- for (CraftTask runner : runners.values()) {
- runner.cancel0();
- }
- }
-
- public boolean isCurrentlyRunning(final int taskId) {
+ // Paper end
final CraftTask task = runners.get(taskId);
- if (task == null || task.isSync()) {
+ if (task == null) {
return false;
}
+ if (task.isSync()) {
+ return (task == currentTask);
+ }
final CraftAsyncTask asyncTask = (CraftAsyncTask) task;
synchronized (asyncTask.getWorkers()) {
- return asyncTask.getWorkers().isEmpty();
+ return !asyncTask.getWorkers().isEmpty();
}
}
+ @Override
public boolean isQueued(final int taskId) {
if (taskId <= 0) {
return false;
}
+ // Paper start
+ if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) {
+ return true;
+ }
+ // Paper end
for (CraftTask task = head.getNext(); task != null; task = task.getNext()) {
if (task.getTaskId() == taskId) {
- return task.getPeriod() >= -1l; // The task will run
+ return task.getPeriod() >= CraftTask.NO_REPEATING; // The task will run
}
}
CraftTask task = runners.get(taskId);
- return task != null && task.getPeriod() >= -1l;
+ return task != null && task.getPeriod() >= CraftTask.NO_REPEATING;
}
+ @Override
public List getActiveWorkers() {
+ // Paper start
+ if (!isAsyncScheduler) {
+ //noinspection TailRecursion
+ return this.asyncScheduler.getActiveWorkers();
+ }
+ // Paper end
final ArrayList workers = new ArrayList();
for (final CraftTask taskObj : runners.values()) {
// Iterator will be a best-effort (may fail to grab very new values) if called from an async thread
@@ -304,6 +398,7 @@ public List getActiveWorkers() {
return workers;
}
+ @Override
public List getPendingTasks() {
final ArrayList truePending = new ArrayList();
for (CraftTask task = head.getNext(); task != null; task = task.getNext()) {
@@ -315,16 +410,21 @@ public List getPendingTasks() {
final ArrayList pending = new ArrayList();
for (CraftTask task : runners.values()) {
- if (task.getPeriod() >= -1l) {
+ if (task.getPeriod() >= CraftTask.NO_REPEATING) {
pending.add(task);
}
}
for (final CraftTask task : truePending) {
- if (task.getPeriod() >= -1l && !pending.contains(task)) {
+ if (task.getPeriod() >= CraftTask.NO_REPEATING && !pending.contains(task)) {
pending.add(task);
}
}
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ pending.addAll(this.asyncScheduler.getPendingTasks());
+ }
+ // Paper end
return pending;
}
@@ -332,12 +432,17 @@ public List getPendingTasks() {
* This method is designed to never block or wait for locks; an immediate execution of all current tasks.
*/
public void mainThreadHeartbeat(final int currentTick) {
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ this.asyncScheduler.mainThreadHeartbeat(currentTick);
+ }
+ // Paper end
this.currentTick = currentTick;
final List temp = this.temp;
parsePending();
while (isReady(currentTick)) {
final CraftTask task = pending.remove();
- if (task.getPeriod() < -1l) {
+ if (task.getPeriod() < CraftTask.NO_REPEATING) {
if (task.isSync()) {
runners.remove(task.getTaskId(), task);
}
@@ -345,21 +450,33 @@ public void mainThreadHeartbeat(final int currentTick) {
continue;
}
if (task.isSync()) {
+ currentTask = task;
try {
task.run();
} catch (final Throwable throwable) {
- task.getOwner().getLogger().log(
- Level.WARNING,
- String.format(
- "Task #%s for %s generated an exception",
- task.getTaskId(),
- task.getOwner().getDescription().getFullName()),
- throwable);
+ // Paper start
+ String msg = String.format(
+ "Task #%s for %s generated an exception",
+ task.getTaskId(),
+ task.getOwner().getDescription().getFullName());
+ if (task.getOwner() == MINECRAFT) {
+ net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
+ } else {
+ task.getOwner().getLogger().log(
+ Level.WARNING,
+ msg,
+ throwable);
+ }
+ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(
+ new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task)));
+ // Paper end
+ } finally {
+ currentTask = null;
}
parsePending();
} else {
- debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass()));
- executor.execute(task);
+ //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper
+ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper
// We don't need to parse pending
// (async tasks must live with race-conditions if they attempt to cancel between these few lines of code)
}
@@ -371,12 +488,14 @@ public void mainThreadHeartbeat(final int currentTick) {
runners.remove(task.getTaskId());
}
}
+ SpigotTimings.bukkitSchedulerFinishTimer.startTiming();
pending.addAll(temp);
temp.clear();
- debugHead = debugHead.getNextHead(currentTick);
+ SpigotTimings.bukkitSchedulerFinishTimer.stopTiming();
+ //debugHead = debugHead.getNextHead(currentTick); // Paper
}
- private void addTask(final CraftTask task) {
+ protected void addTask(final CraftTask task) {
final AtomicReference tail = this.tail;
CraftTask tailTask = tail.get();
while (!tail.compareAndSet(tailTask, task)) {
@@ -385,7 +504,13 @@ private void addTask(final CraftTask task) {
tailTask.setNext(task);
}
- private CraftTask handle(final CraftTask task, final long delay) {
+ protected CraftTask handle(final CraftTask task, final long delay) { // Paper
+ // Paper start
+ if (!this.isAsyncScheduler && !task.isSync()) {
+ this.asyncScheduler.handle(task, delay);
+ return task;
+ }
+ // Paper end
task.setNextRun(currentTick + delay);
addTask(task);
return task;
@@ -394,6 +519,7 @@ private CraftTask handle(final CraftTask task, final long delay) {
private static void validate(final Plugin plugin, final Object task) {
Validate.notNull(plugin, "Plugin cannot be null");
Validate.notNull(task, "Task cannot be null");
+ Validate.isTrue(task instanceof Runnable || task instanceof Consumer || task instanceof Callable, "Task must be Runnable, Consumer, or Callable");
if (!plugin.isEnabled()) {
throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
}
@@ -403,14 +529,15 @@ private int nextId() {
return ids.incrementAndGet();
}
- private void parsePending() {
+ void parsePending() { // Paper
+ if (!this.isAsyncScheduler) SpigotTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper
CraftTask head = this.head;
CraftTask task = head.getNext();
CraftTask lastTask = head;
for (; task != null; task = (lastTask = task).getNext()) {
if (task.getTaskId() == -1) {
task.run();
- } else if (task.getPeriod() >= -1l) {
+ } else if (task.getPeriod() >= CraftTask.NO_REPEATING) {
pending.add(task);
runners.put(task.getTaskId(), task);
}
@@ -418,10 +545,11 @@ private void parsePending() {
// We split this because of the way things are ordered for all of the async calls in CraftScheduler
// (it prevents race-conditions)
for (task = head; task != lastTask; task = head) {
- head = task.getNext();
- task.setNext(null);
+ head = task.getNext();
+ task.setNext(null);
}
this.head = lastTask;
+ if (!this.isAsyncScheduler) SpigotTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper
}
private boolean isReady(final int currentTick) {
@@ -430,63 +558,79 @@ private boolean isReady(final int currentTick) {
@Override
public String toString() {
+ // Paper start
+ return "";
+ /*
int debugTick = currentTick;
StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{');
debugHead.debugTo(string);
return string.append('}').toString();
+ */
+ // Paper end
}
@Deprecated
@Override
public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task, long delay) {
- return scheduleSyncDelayedTask(plugin, (Runnable) task, delay);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskLater(Plugin, long)");
}
@Deprecated
@Override
public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task) {
- return scheduleSyncDelayedTask(plugin, (Runnable) task);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTask(Plugin)");
}
@Deprecated
@Override
public int scheduleSyncRepeatingTask(Plugin plugin, BukkitRunnable task, long delay, long period) {
- return scheduleSyncRepeatingTask(plugin, (Runnable) task, delay, period);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimer(Plugin, long, long)");
}
@Deprecated
@Override
public BukkitTask runTask(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException {
- return runTask(plugin, (Runnable) task);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTask(Plugin)");
}
@Deprecated
@Override
public BukkitTask runTaskAsynchronously(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException {
- return runTaskAsynchronously(plugin, (Runnable) task);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskAsynchronously(Plugin)");
}
@Deprecated
@Override
public BukkitTask runTaskLater(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException {
- return runTaskLater(plugin, (Runnable) task, delay);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskLater(Plugin, long)");
}
@Deprecated
@Override
public BukkitTask runTaskLaterAsynchronously(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException {
- return runTaskLaterAsynchronously(plugin, (Runnable) task, delay);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskLaterAsynchronously(Plugin, long)");
}
@Deprecated
@Override
public BukkitTask runTaskTimer(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
- return runTaskTimer(plugin, (Runnable) task, delay, period);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimer(Plugin, long, long)");
}
@Deprecated
@Override
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
- return runTaskTimerAsynchronously(plugin, (Runnable) task, delay, period);
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)");
+ }
+
+ // Paper start - add getMainThreadExecutor
+ @Override
+ public Executor getMainThreadExecutor(Plugin plugin) {
+ Validate.notNull(plugin, "Plugin cannot be null");
+ return command -> {
+ Validate.notNull(command, "Command cannot be null");
+ this.runTask(plugin, command);
+ };
}
+ // Paper end
}
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
index 4b1e35236..1cd46862f 100644
--- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
@@ -1,15 +1,23 @@
package org.bukkit.craftbukkit.scheduler;
+import co.aikar.timings.NullTimingHandler;
+import co.aikar.timings.SpigotTimings;
+import co.aikar.timings.Timing;
import org.bukkit.Bukkit;
-import co.aikar.timings.SpigotTimings; // Spigot
-import co.aikar.timings.Timing; // Spigot
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
+import java.util.function.Consumer;
+
public class CraftTask implements BukkitTask, Runnable { // Spigot
private volatile CraftTask next = null;
+ public static final int ERROR = 0;
+ public static final int NO_REPEATING = -1;
+ public static final int CANCEL = -2;
+ public static final int PROCESS_FOR_FUTURE = -3;
+ public static final int DONE_FOR_FUTURE = -4;
/**
* -1 means no repeating
* -2 means cancel
@@ -20,26 +28,55 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
*/
private volatile long period;
private long nextRun;
- public final Runnable task; //Spigot
+ public final Runnable rTask; //Spigot
+ public final Consumer cTask; // Paper
public Timing timings; // Spigot
private final Plugin plugin;
private final int id;
CraftTask() {
- this(null, null, -1, -1);
+ this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING);
+ }
+
+ CraftTask(final Object rTask) {
+ this(null, rTask, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING);
}
- CraftTask(final Runnable task) {
- this(null, task, -1, -1);
+ // Paper start
+ public String taskName = null;
+ boolean internal = false;
+ CraftTask(final Object rTask, int id, String taskName) {
+ this.rTask = (Runnable) rTask;
+ this.cTask = null;
+ this.plugin = CraftScheduler.MINECRAFT;
+ this.taskName = taskName;
+ this.internal = true;
+ this.id = id;
+ this.period = CraftTask.NO_REPEATING;
+ this.taskName = taskName;
+ this.timings = SpigotTimings.getInternalTaskName(taskName);
}
+ // Paper end
// Spigot start
- CraftTask(final Plugin plugin, final Runnable task, final int id, final long period) {
+ CraftTask(final Plugin plugin, final Object rTask, final int id, final long period) {
this.plugin = plugin;
- this.task = task;
+ if (rTask instanceof Runnable) {
+ this.rTask = (Runnable) rTask;
+ this.cTask = null;
+ } else if (rTask instanceof Consumer) {
+ this.cTask = (Consumer) rTask;
+ this.rTask = null;
+ } else if (rTask == null) {
+ // Head or Future task
+ this.rTask = null;
+ this.cTask = null;
+ } else {
+ throw new AssertionError("Illegal task class " + rTask);
+ }
this.id = id;
this.period = period;
- timings = task != null ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot
+ timings = rTask != null ? SpigotTimings.getPluginTaskTimings(this, period) : NullTimingHandler.NULL; // Spigot
}
public final int getTaskId() {
@@ -55,9 +92,13 @@ public boolean isSync() {
}
public void run() {
- if (timings != null && isSync()) timings.startTiming(); // Spigot
- task.run();
- if (timings != null && isSync()) timings.stopTiming(); // Spigot
+ try (Timing ignored = timings.startTiming()) { // Paper
+ if (rTask != null) {
+ rTask.run();
+ } else {
+ cTask.accept(this);
+ }
+ } // Paper
}
long getPeriod() {
@@ -84,8 +125,13 @@ void setNext(CraftTask next) {
this.next = next;
}
- Class extends Runnable> getTaskClass() {
- return task.getClass();
+ public Class> getTaskClass() {
+ return (rTask != null) ? rTask.getClass() : ((cTask != null) ? cTask.getClass() : null);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return (period == CraftTask.CANCEL);
}
public void cancel() {
@@ -98,7 +144,7 @@ public void cancel() {
* @return false if it is a craft future task that has already begun execution, true otherwise
*/
boolean cancel0() {
- setPeriod(-2l);
+ setPeriod(CraftTask.CANCEL);
return true;
}
diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
new file mode 100644
index 000000000..15ee9d48e
--- /dev/null
+++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
@@ -0,0 +1,132 @@
+package org.bukkit.craftbukkit.scheduler;
+
+import com.avaje.ebean.EbeanServer;
+import org.bukkit.Server;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.generator.ChunkGenerator;
+import org.bukkit.plugin.PluginBase;
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.PluginLoader;
+import org.bukkit.plugin.PluginLogger;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+
+public class MinecraftInternalPlugin extends PluginBase {
+ private boolean enabled = true;
+
+ private final String pluginName;
+ private PluginDescriptionFile pdf;
+
+ public MinecraftInternalPlugin() {
+ this.pluginName = "Minecraft";
+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public File getDataFolder() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public PluginDescriptionFile getDescription() {
+ return pdf;
+ }
+
+ @Override
+ public FileConfiguration getConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public InputStream getResource(String filename) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void saveConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void saveDefaultConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void saveResource(String resourcePath, boolean replace) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void reloadConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public PluginLogger getLogger() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public PluginLoader getPluginLoader() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public Server getServer() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void onDisable() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void onLoad() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void onEnable() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean isNaggable() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void setNaggable(boolean canNag) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-Server/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java b/NachoSpigot-Server/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java
new file mode 100644
index 000000000..8b4d6eee2
--- /dev/null
+++ b/NachoSpigot-Server/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java
@@ -0,0 +1,38 @@
+package org.github.paperspigot;
+
+import com.google.common.base.Preconditions;
+import org.bukkit.craftbukkit.scheduler.CraftTask;
+import org.github.paperspigot.event.ServerExceptionEvent;
+import org.github.paperspigot.exception.ServerSchedulerException;
+
+/**
+ * Reporting wrapper to catch exceptions not natively
+ */
+public class ServerSchedulerReportingWrapper implements Runnable {
+
+ private final CraftTask internalTask;
+
+ public ServerSchedulerReportingWrapper(CraftTask internalTask) {
+ this.internalTask = Preconditions.checkNotNull(internalTask, "internalTask");
+ }
+
+ @Override
+ public void run() {
+ try {
+ internalTask.run();
+ } catch (RuntimeException e) {
+ internalTask.getOwner().getServer().getPluginManager().callEvent(
+ new ServerExceptionEvent(new ServerSchedulerException(e, internalTask))
+ );
+ throw e;
+ } catch (Throwable t) {
+ internalTask.getOwner().getServer().getPluginManager().callEvent(
+ new ServerExceptionEvent(new ServerSchedulerException(t, internalTask))
+ ); //Do not rethrow, since it is not permitted with Runnable#run
+ }
+ }
+
+ public CraftTask getInternalTask() {
+ return internalTask;
+ }
+}
\ No newline at end of file
diff --git a/NachoSpigot-Server/src/main/java/org/spigotmc/AsyncCatcher.java b/NachoSpigot-Server/src/main/java/org/spigotmc/AsyncCatcher.java
index 4b3aa85c9..aeed76972 100644
--- a/NachoSpigot-Server/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/NachoSpigot-Server/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -9,7 +9,7 @@ public class AsyncCatcher
public static void catchOp(String reason)
{
- if ( enabled && Thread.currentThread() != MinecraftServer.getServer().primaryThread )
+ if ( enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
{
throw new IllegalStateException( "Asynchronous " + reason + "!" );
}
diff --git a/NachoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java b/NachoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java
index 20cef1718..85c220a77 100644
--- a/NachoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/NachoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java
@@ -69,7 +69,7 @@ public void run()
//
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to NachoSpigot!):" );
- dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE ), log );
+ dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
log.log( Level.SEVERE, "------------------------------" );
//
log.log( Level.SEVERE, "Entire Thread Dump:" );
diff --git a/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/IAsyncHandler.java b/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/IAsyncHandler.java
new file mode 100644
index 000000000..f2866fc69
--- /dev/null
+++ b/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/IAsyncHandler.java
@@ -0,0 +1,140 @@
+package xyz.dysaido.nacho;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+
+public abstract class IAsyncHandler implements Executor {
+ private static final Logger LOGGER = LogManager.getLogger();
+ private final String name;
+ private final Queue pendingRunnables = new ConcurrentLinkedQueue<>();
+ private int terminateCount;
+
+ protected IAsyncHandler(String name) {
+ this.name = name;
+ }
+
+ protected abstract R packUpRunnable(Runnable runnable);
+
+ protected abstract boolean shouldRun(R task);
+
+ public boolean isMainThread() {
+ return Thread.currentThread() == this.getMainThread();
+ }
+
+ protected abstract Thread getMainThread();
+
+ protected boolean executables() {
+ return !this.isMainThread();
+ }
+
+ public int getPendingRunnables() {
+ return this.pendingRunnables.size();
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public CompletableFuture submit(Supplier task) {
+ return this.executables() ? CompletableFuture.supplyAsync(task, this) : CompletableFuture.completedFuture(task.get());
+ }
+
+ private CompletableFuture submitAsync(Runnable runnable) {
+ return CompletableFuture.supplyAsync(() -> {
+ runnable.run();
+ return null;
+ }, this);
+ }
+
+ public CompletableFuture submit(Runnable task) {
+ if (this.executables()) {
+ return this.submitAsync(task);
+ } else {
+ task.run();
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ public void performBlocking(Runnable runnable) {
+ if (!this.isMainThread()) {
+ this.submitAsync(runnable).join();
+ } else {
+ runnable.run();
+ }
+
+ }
+
+ public void call(R runnable) {
+ this.pendingRunnables.add(runnable);
+ LockSupport.unpark(this.getMainThread());
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (this.executables()) {
+ this.call(this.packUpRunnable(runnable));
+ } else {
+ runnable.run();
+ }
+
+ }
+
+ protected void clearAllRunnable() {
+ this.pendingRunnables.clear();
+ }
+
+ public void runAllRunnable() {
+ while(this.drawRunnable()) {
+ }
+ }
+
+ public boolean drawRunnable() {
+ R runnable = this.pendingRunnables.peek();
+ if (runnable == null) {
+ return false;
+ } else if (this.terminateCount == 0 && !this.shouldRun(runnable)) {
+ return false;
+ } else {
+ this.doRunnable(this.pendingRunnables.remove());
+ return true;
+ }
+ }
+
+ public void controlTerminate(BooleanSupplier stopCondition) {
+ ++this.terminateCount;
+
+ try {
+ while(!stopCondition.getAsBoolean()) {
+ if (!this.drawRunnable()) {
+ this.waitForRuns();
+ }
+ }
+ } finally {
+ --this.terminateCount;
+ }
+
+ }
+
+ protected void waitForRuns() {
+ Thread.yield();
+ LockSupport.parkNanos("waiting for tasks", 100000L);
+ }
+
+ protected void doRunnable(R task) {
+ try {
+ task.run();
+ } catch (Exception e) {
+ if (e.getCause() instanceof ThreadDeath) throw e; // Paper
+ LOGGER.fatal("Error executing task on {}", this.getName(), e);
+ }
+
+ }
+}
diff --git a/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/ReentrantIAsyncHandler.java b/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/ReentrantIAsyncHandler.java
new file mode 100644
index 000000000..34c07a714
--- /dev/null
+++ b/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/ReentrantIAsyncHandler.java
@@ -0,0 +1,29 @@
+package xyz.dysaido.nacho;
+
+public abstract class ReentrantIAsyncHandler extends IAsyncHandler {
+
+ private int count;
+
+ public ReentrantIAsyncHandler(String name) {
+ super(name);
+ }
+
+ @Override
+ protected boolean executables() {
+ return this.runningTask() || super.executables();
+ }
+
+ protected boolean runningTask() {
+ return this.count != 0;
+ }
+
+ @Override
+ protected void doRunnable(R task) {
+ ++this.count;
+ try {
+ super.doRunnable(task);
+ } finally {
+ --this.count;
+ }
+ }
+}
diff --git a/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/TasksPerTick.java b/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/TasksPerTick.java
new file mode 100644
index 000000000..f395b23a4
--- /dev/null
+++ b/NachoSpigot-Server/src/main/java/xyz/dysaido/nacho/TasksPerTick.java
@@ -0,0 +1,20 @@
+package xyz.dysaido.nacho;
+
+public class TasksPerTick implements Runnable {
+ private final int tick;
+ private final Runnable task;
+
+ public TasksPerTick(int creationTicks, Runnable task) {
+ this.tick = creationTicks;
+ this.task = task;
+ }
+
+ public int getTick() {
+ return tick;
+ }
+
+ @Override
+ public void run() {
+ task.run();
+ }
+}