diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/FullServerTickHandler.java b/NachoSpigot-API/src/main/java/co/aikar/timings/FullServerTickHandler.java index cb4e7ba83..f3a310fea 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/FullServerTickHandler.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/FullServerTickHandler.java @@ -15,13 +15,13 @@ public class FullServerTickHandler extends TimingHandler { } @Override - public void startTiming() { - if (TimingsManager.needsFullReset) { - TimingsManager.resetTimings(); - } else if (TimingsManager.needsRecheckEnabled) { - TimingsManager.recheckEnabled(); + public Timing startTiming() { + if (needsFullReset) { + resetTimings(); + } else if (needsRecheckEnabled) { + recheckEnabled(); } - super.startTiming(); + return super.startTiming(); } @Override @@ -48,7 +48,7 @@ public void stopTiming() { } long start = System.nanoTime(); - TimingsManager.tick(); + tick(); long diff = System.nanoTime() - start; CURRENT = TIMINGS_TICK; TIMINGS_TICK.addDiff(diff); @@ -68,8 +68,8 @@ public void stopTiming() { minuteData.reset(); } if (TimingHistory.timedTicks % Timings.getHistoryInterval() == 0) { - TimingsManager.HISTORY.add(new TimingHistory()); - TimingsManager.resetTimings(); + HISTORY.add(new TimingHistory()); + resetTimings(); } } diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/NullTimingHandler.java b/NachoSpigot-API/src/main/java/co/aikar/timings/NullTimingHandler.java index c73b617c5..72df3bfc3 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/NullTimingHandler.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/NullTimingHandler.java @@ -24,9 +24,11 @@ package co.aikar.timings; public final class NullTimingHandler implements Timing { - @Override - public void startTiming() { + public static final Timing NULL = new NullTimingHandler(); + @Override + public Timing startTiming() { + return this; } @Override @@ -35,8 +37,8 @@ public void stopTiming() { } @Override - public void startTimingIfSync() { - + public Timing startTimingIfSync() { + return this; } @Override @@ -54,6 +56,11 @@ public TimingHandler getTimingHandler() { return null; } + @Override + public double getAverage() { + return 0; + } + @Override public void close() { diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/Timing.java b/NachoSpigot-API/src/main/java/co/aikar/timings/Timing.java index 4d990b130..a4eddc631 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/Timing.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/Timing.java @@ -30,7 +30,7 @@ public interface Timing extends AutoCloseable { /** * Starts timing the execution until {@link #stopTiming()} is called. */ - public void startTiming(); + public Timing startTiming(); /** *

Stops timing and records the data. Propagates the data up to group handlers.

@@ -44,7 +44,7 @@ public interface Timing extends AutoCloseable { * * But only if we are on the primary thread. */ - public void startTimingIfSync(); + public Timing startTimingIfSync(); /** *

Stops timing and records the data. Propagates the data up to group handlers.

@@ -67,6 +67,8 @@ public interface Timing extends AutoCloseable { */ TimingHandler getTimingHandler(); + double getAverage(); + @Override void close(); } diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHandler.java b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHandler.java index dccd6b93e..18909957d 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHandler.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHandler.java @@ -23,9 +23,9 @@ */ package co.aikar.timings; +import co.aikar.util.LoadingIntMap; import gnu.trove.map.hash.TIntObjectHashMap; import org.bukkit.Bukkit; -import co.aikar.util.LoadingIntMap; import java.util.logging.Level; @@ -42,6 +42,8 @@ class TimingHandler implements Timing { final TimingData record; final TimingHandler groupHandler; + public int count; + public int total; long start = 0; int timingDepth = 0; boolean added; @@ -83,10 +85,11 @@ void processTick(boolean violated) { } @Override - public void startTimingIfSync() { + public Timing startTimingIfSync() { if (Bukkit.isPrimaryThread()) { startTiming(); } + return this; } @Override @@ -96,12 +99,13 @@ public void stopTimingIfSync() { } } - public void startTiming() { + public Timing startTiming() { if (enabled && ++timingDepth == 1) { start = System.nanoTime(); parent = TimingsManager.CURRENT; TimingsManager.CURRENT = this; } + return this; } public void stopTiming() { @@ -112,12 +116,9 @@ public void stopTiming() { start = 0; return; } - try - { + try { addDiff(System.nanoTime() - start); - } - catch (ArrayIndexOutOfBoundsException ex) - { + } catch (ArrayIndexOutOfBoundsException ex) { ex.printStackTrace(); } start = 0; @@ -132,6 +133,12 @@ public void abort() { } void addDiff(long diff) { + total += diff; + count++; + if (count > 20) { + count--; + total -= ((double) total / (double) count); + } if (TimingsManager.CURRENT == this) { TimingsManager.CURRENT = parent; if (parent != null) { @@ -172,6 +179,12 @@ public TimingHandler getTimingHandler() { return this; } + @Override + public double getAverage() { + if (count < 20) return 0; + return total / count; + } + @Override public boolean equals(Object o) { return (this == o); diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHistory.java b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHistory.java index 7a0477093..1cd490dfe 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHistory.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingHistory.java @@ -23,6 +23,8 @@ */ package co.aikar.timings; +import co.aikar.util.LoadingMap; +import co.aikar.util.MRUMapCache; import com.google.common.base.Function; import com.google.common.collect.Sets; import org.bukkit.Bukkit; @@ -33,15 +35,9 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; -import co.aikar.util.LoadingMap; -import co.aikar.util.MRUMapCache; import java.lang.management.ManagementFactory; -import java.util.Collection; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static co.aikar.timings.TimingsManager.FULL_SERVER_TICK; import static co.aikar.timings.TimingsManager.MINUTE_REPORTS; @@ -202,10 +198,10 @@ static class MinuteReport { final TicksRecord ticksRecord = new TicksRecord(); final PingRecord pingRecord = new PingRecord(); - final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone(); + final TimingData fst = FULL_SERVER_TICK.minuteData.clone(); final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed; - final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory; - final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory; + final double usedMemory = FULL_SERVER_TICK.avgUsedMemory; + final double freeMemory = FULL_SERVER_TICK.avgFreeMemory; final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); public List export() { @@ -235,7 +231,7 @@ static class TicksRecord { final long activatedEntity; TicksRecord() { - timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200); + timed = timedTicks - (MINUTE_REPORTS.size() * 1200); player = playerTicks; entity = entityTicks; tileEntity = tileEntityTicks; diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingIdentifier.java b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingIdentifier.java index 623dda49c..bac6c84b3 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingIdentifier.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingIdentifier.java @@ -23,9 +23,9 @@ */ package co.aikar.timings; -import com.google.common.base.Function; import co.aikar.util.LoadingMap; import co.aikar.util.MRUMapCache; +import com.google.common.base.Function; import java.util.ArrayDeque; import java.util.Map; diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsExport.java b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsExport.java index a3f78d521..1ae41c51f 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsExport.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsExport.java @@ -23,7 +23,6 @@ */ package co.aikar.timings; -import com.google.common.base.Function; import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; @@ -35,7 +34,6 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemorySection; import org.bukkit.entity.EntityType; -import org.bukkit.plugin.Plugin; import org.json.simple.JSONObject; import org.json.simple.JSONValue; @@ -43,12 +41,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Set; @@ -80,19 +78,19 @@ class TimingsExport extends Thread { */ static void reportTimings(CommandSender sender) { Map parent = createObject( - // Get some basic system details about the server - pair("version", Bukkit.getVersion()), - pair("maxplayers", Bukkit.getMaxPlayers()), - pair("start", TimingsManager.timingStart / 1000), - pair("end", System.currentTimeMillis() / 1000), - pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000) + // Get some basic system details about the server + pair("version", Bukkit.getVersion()), + pair("maxplayers", Bukkit.getMaxPlayers()), + pair("start", TimingsManager.timingStart / 1000), + pair("end", System.currentTimeMillis() / 1000), + pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000) ); if (!TimingsManager.privacy) { appendObjectData(parent, - pair("server", Bukkit.getServerName()), - pair("motd", Bukkit.getServer().getMotd()), - pair("online-mode", Bukkit.getServer().getOnlineMode()), - pair("icon", Bukkit.getServer().getServerIcon().getData()) + pair("server", Bukkit.getServerName()), + pair("motd", Bukkit.getServer().getMotd()), + pair("online-mode", Bukkit.getServer().getOnlineMode()), + pair("icon", Bukkit.getServer().getServerIcon().getData()) ); } @@ -109,13 +107,8 @@ static void reportTimings(CommandSender sender) { pair("cpu", runtime.availableProcessors()), pair("runtime", ManagementFactory.getRuntimeMXBean().getUptime()), pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")), - pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), new Function() { - @Override - public JSONPair apply(GarbageCollectorMXBean input) { - return pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())); - } - })) - ) + pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())))) + ) ); Set tileEntityTypeSet = Sets.newHashSet(); @@ -142,58 +135,32 @@ public JSONPair apply(GarbageCollectorMXBean input) { continue; } handlers.put(id.id, toArray( - group.id, - id.name + group.id, + id.name )); } } parent.put("idmap", createObject( - pair("groups", toObjectMapper( - TimingIdentifier.GROUP_MAP.values(), new Function() { - @Override - public JSONPair apply(TimingIdentifier.TimingGroup group) { - return pair(group.id, group.name); - } - })), - pair("handlers", handlers), - pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), new Function, JSONPair>() { - @Override - public JSONPair apply(Map.Entry input) { - return pair(input.getValue(), input.getKey()); - } - })), - pair("tileentity", - toObjectMapper(tileEntityTypeSet, new Function() { - @Override - public JSONPair apply(Material input) { - return pair(input.getId(), input.name()); - } - })), - pair("entity", - toObjectMapper(entityTypeSet, new Function() { - @Override - public JSONPair apply(EntityType input) { - return pair(input.getTypeId(), input.name()); - } - })) + pair("groups", toObjectMapper( + TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name))), + pair("handlers", handlers), + pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))), + pair("tileentity", + toObjectMapper(tileEntityTypeSet, input -> pair(input.getId(), input.name()))), + pair("entity", + toObjectMapper(entityTypeSet, input -> pair(input.getTypeId(), input.name()))) )); // Information about loaded plugins parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(), - new Function() { - @Override - public JSONPair apply(Plugin plugin) { - return pair(plugin.getName(), createObject( + plugin -> pair(plugin.getName(), createObject( pair("version", plugin.getDescription().getVersion()), pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()), pair("website", plugin.getDescription().getWebsite()), pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", ")) - )); - } - })); - + )))); // Information on the users Config @@ -263,12 +230,7 @@ private static Object valAsJSON(Object val, final String parentKey) { if (!(val instanceof MemorySection)) { if (val instanceof List) { Iterable v = (Iterable) val; - return toArrayMapper(v, new Function() { - @Override - public Object apply(Object input) { - return valAsJSON(input, parentKey); - } - }); + return toArrayMapper(v, input -> valAsJSON(input, parentKey)); } else { return val.toString(); } @@ -283,7 +245,7 @@ public synchronized void start() { if (sender instanceof RemoteConsoleCommandSender) { sender.sendMessage(ChatColor.RED + "Warning: Timings report done over RCON will cause lag spikes."); sender.sendMessage(ChatColor.RED + "You should use " + ChatColor.YELLOW + - "/timings report" + ChatColor.RED + " in game or console."); + "/timings report" + ChatColor.RED + " in game or console."); run(); } else { super.start(); @@ -295,12 +257,7 @@ public void run() { sender.sendMessage(ChatColor.GREEN + "Preparing Timings Report..."); - out.put("data", toArrayMapper(history, new Function() { - @Override - public Object apply(TimingHistory input) { - return input.export(); - } - })); + out.put("data", toArrayMapper(history, TimingHistory::export)); String response = null; @@ -315,14 +272,14 @@ public Object apply(TimingHistory input) { this.def.setLevel(7); }}; - request.write(JSONValue.toJSONString(out).getBytes("UTF-8")); + request.write(JSONValue.toJSONString(out).getBytes(StandardCharsets.UTF_8)); request.close(); response = getResponse(con); if (con.getResponseCode() != 302) { sender.sendMessage( - ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage()); + ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage()); sender.sendMessage(ChatColor.RED + "Check your logs for more information"); if (response != null) { Bukkit.getLogger().log(Level.SEVERE, response); @@ -349,9 +306,7 @@ public Object apply(TimingHistory input) { } private String getResponse(HttpURLConnection con) throws IOException { - InputStream is = null; - try { - is = con.getInputStream(); + try (InputStream is = con.getInputStream()) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; @@ -365,10 +320,6 @@ private String getResponse(HttpURLConnection con) throws IOException { sender.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex); return null; - } finally { - if (is != null) { - is.close(); - } } } } diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsManager.java b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsManager.java index 67c39df8a..8df10ce17 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsManager.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/TimingsManager.java @@ -23,35 +23,24 @@ */ package co.aikar.timings; -import com.google.common.base.Function; +import co.aikar.util.LoadingMap; import com.google.common.collect.EvictingQueue; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.PluginClassLoader; -import co.aikar.util.LoadingMap; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.logging.Level; public final class TimingsManager { static final Map TIMING_MAP = Collections.synchronizedMap(LoadingMap.newHashMap( - new Function() { - @Override - public TimingHandler apply(TimingIdentifier id) { - return (id.protect ? - new UnsafeTimingHandler(id) : - new TimingHandler(id) - ); - } - }, + id -> (id.protect ? + new UnsafeTimingHandler(id) : + new TimingHandler(id) + ), 256, .5F )); public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler(); diff --git a/NachoSpigot-API/src/main/java/co/aikar/timings/UnsafeTimingHandler.java b/NachoSpigot-API/src/main/java/co/aikar/timings/UnsafeTimingHandler.java index e3b0ed837..3acbce1f9 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/timings/UnsafeTimingHandler.java +++ b/NachoSpigot-API/src/main/java/co/aikar/timings/UnsafeTimingHandler.java @@ -38,9 +38,9 @@ private static void checkThread() { } @Override - public void startTiming() { + public Timing startTiming() { checkThread(); - super.startTiming(); + return super.startTiming(); } @Override diff --git a/NachoSpigot-API/src/main/java/co/aikar/util/LoadingIntMap.java b/NachoSpigot-API/src/main/java/co/aikar/util/LoadingIntMap.java index 102aa8562..8d0f26919 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/util/LoadingIntMap.java +++ b/NachoSpigot-API/src/main/java/co/aikar/util/LoadingIntMap.java @@ -32,7 +32,6 @@ public class LoadingIntMap extends TIntObjectHashMap { * @param loader The loader */ public LoadingIntMap(Function loader) { - super(200); this.loader = loader; } diff --git a/NachoSpigot-API/src/main/java/co/aikar/util/LoadingMap.java b/NachoSpigot-API/src/main/java/co/aikar/util/LoadingMap.java index a0bc8e3e4..27065e191 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/util/LoadingMap.java +++ b/NachoSpigot-API/src/main/java/co/aikar/util/LoadingMap.java @@ -27,12 +27,7 @@ import com.google.common.base.Function; import java.lang.reflect.Constructor; -import java.util.AbstractMap; -import java.util.Collection; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Allows you to pass a Loader function that when a key is accessed that doesn't exists, diff --git a/NachoSpigot-API/src/main/java/co/aikar/util/MRUMapCache.java b/NachoSpigot-API/src/main/java/co/aikar/util/MRUMapCache.java index 3a288d2af..1028dd331 100644 --- a/NachoSpigot-API/src/main/java/co/aikar/util/MRUMapCache.java +++ b/NachoSpigot-API/src/main/java/co/aikar/util/MRUMapCache.java @@ -85,7 +85,7 @@ public void clear() { public Collection values() {return backingMap.values();} - public Set> entrySet() {return backingMap.entrySet();} + public Set> entrySet() {return backingMap.entrySet();} /** * Wraps the specified map with a most recently used cache diff --git a/NachoSpigot-API/src/main/java/org/bukkit/event/Event.java b/NachoSpigot-API/src/main/java/org/bukkit/event/Event.java index 6677e1bd6..47dbc7218 100644 --- a/NachoSpigot-API/src/main/java/org/bukkit/event/Event.java +++ b/NachoSpigot-API/src/main/java/org/bukkit/event/Event.java @@ -33,6 +33,22 @@ public Event(boolean isAsync) { this.async = isAsync; } + // Paper start + /** + * Calls the event and tests if cancelled. + * + * @return false if event was cancelled, if cancellable. otherwise true. + */ + public boolean callEvent() { + org.bukkit.Bukkit.getPluginManager().callEvent(this); + if (this instanceof Cancellable) { + return !((Cancellable) this).isCancelled(); + } else { + return true; + } + } + // Paper end + /** * Convenience method for providing a user-friendly identifier. By * default, it is the event's class's {@linkplain Class#getSimpleName() diff --git a/NachoSpigot-API/src/main/java/org/bukkit/plugin/Plugin.java b/NachoSpigot-API/src/main/java/org/bukkit/plugin/Plugin.java index 7bdc809c6..f856e029f 100644 --- a/NachoSpigot-API/src/main/java/org/bukkit/plugin/Plugin.java +++ b/NachoSpigot-API/src/main/java/org/bukkit/plugin/Plugin.java @@ -139,24 +139,6 @@ public interface Plugin extends TabExecutor { */ public void setNaggable(boolean canNag); - /** - * Gets the {@link EbeanServer} tied to this plugin. This will only be - * available if enabled in the {@link - * PluginDescriptionFile#isDatabaseEnabled()} - *

- * For more information on the use of - * Avaje Ebeans ORM, see Avaje Ebeans - * Documentation - *

- * For an example using Ebeans ORM, see Bukkit's Homebukkit Plugin - * - * - * @return ebean server instance or null if not enabled - */ - public EbeanServer getDatabase(); - /** * Gets a {@link ChunkGenerator} for use in a default world, as specified * in the server configuration diff --git a/NachoSpigot-API/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java b/NachoSpigot-API/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java index c82928ef8..24ecc3223 100644 --- a/NachoSpigot-API/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java +++ b/NachoSpigot-API/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java @@ -84,10 +84,6 @@ * {@link #getPrefix()} * The token to prefix plugin log entries * - * database - * {@link #isDatabaseEnabled()} - * Indicator to enable database support - * * load * {@link #getLoad()} * The phase of server-startup this plugin will load during @@ -221,7 +217,6 @@ public PluginAwareness.Flags construct(final Node node) { private List authors = null; private String website = null; private String prefix = null; - private boolean database = false; private PluginLoadOrder order = PluginLoadOrder.POSTWORLD; private List permissions = null; private Map lazyPermissions = null; @@ -427,25 +422,6 @@ public String getWebsite() { return website; } - /** - * Gives if the plugin uses a database. - *

    - *
  • Using a database is non-trivial. - *
  • Valid values include true and false - *
- *

- * In the plugin.yml, this entry is named database. - *

- * Example: - *

database: false
- * - * @return if this plugin requires a database - * @see Plugin#getDatabase() - */ - public boolean isDatabaseEnabled() { - return database; - } - /** * Gives a list of other plugins that the plugin requires. *
    @@ -873,10 +849,6 @@ public String getClassLoaderOf() { return classLoaderOf; } - public void setDatabaseEnabled(boolean database) { - this.database = database; - } - /** * Saves this PluginDescriptionFile to the given writer * @@ -956,13 +928,6 @@ private void loadMap(Map map) throws InvalidDescriptionException { softDepend = makePluginNameList(map, "softdepend"); loadBefore = makePluginNameList(map, "loadbefore"); - if (map.get("database") != null) { - try { - database = (Boolean) map.get("database"); - } catch (ClassCastException ex) { - throw new InvalidDescriptionException(ex, "database is of wrong type"); - } - } if (map.get("website") != null) { website = map.get("website").toString(); @@ -1061,7 +1026,6 @@ private Map 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 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> listener, GenericFutureListener>... listeners) { + public void a(Packet packet, GenericFutureListener> listener, GenericFutureListener>... 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 packet, final GenericFutureListener { - 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>... 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>[] agenericfuturelistener) { + private EnumProtocol getCurrentProtocol() { + return this.channel.attr(NetworkManager.c).get(); + } + + private void a(final Packet packet, final GenericFutureListener>[] 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>[] b; //listener @SafeVarargs - public QueuedPacket(Packet packet, GenericFutureListener> ...listeners) { + public QueuedPacket(Packet packet, GenericFutureListener> ...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 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(); + } +}