From 7294ae3022c89b2d05e70e977e9c098edcebc0ca Mon Sep 17 00:00:00 2001 From: Abel <abelvanhulst@gmail.com> Date: Tue, 12 Nov 2024 22:25:20 +0100 Subject: [PATCH] API to allow/disallow tick sleeping --- .../server/MinecraftServer.java.patch | 171 ++++++++++-------- .../org/bukkit/craftbukkit/CraftServer.java | 5 + 2 files changed, 104 insertions(+), 72 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index 0368f5fb48..025c76fa20 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -150,7 +150,7 @@ @Nullable private KeyPair keyPair; @Nullable -@@ -277,6 +308,27 @@ +@@ -277,6 +308,28 @@ private final SuppressedExceptionCollector suppressedExceptions; private final DiscontinuousFrame tickFrame; @@ -174,11 +174,12 @@ + // Spigot end + public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files + public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked ++ private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping + public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) { AtomicReference<S> atomicreference = new AtomicReference(); Thread thread = new Thread(() -> { -@@ -286,19 +338,21 @@ +@@ -286,19 +339,21 @@ thread.setUncaughtExceptionHandler((thread1, throwable) -> { MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable); }); @@ -202,7 +203,7 @@ this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; this.onMetricsRecordingStopped = (methodprofilerresults) -> { this.stopRecordingMetrics(); -@@ -319,36 +373,67 @@ +@@ -319,36 +374,67 @@ this.scoreboard = new ServerScoreboard(this); this.customBossEvents = new CustomBossEvents(); this.suppressedExceptions = new SuppressedExceptionCollector(); @@ -285,7 +286,7 @@ } private void readScoreboard(DimensionDataStorage persistentStateManager) { -@@ -357,7 +442,7 @@ +@@ -357,7 +443,7 @@ protected abstract boolean initServer() throws IOException; @@ -294,7 +295,7 @@ if (!JvmProfiler.INSTANCE.isRunning()) { ; } -@@ -365,12 +450,8 @@ +@@ -365,12 +451,8 @@ boolean flag = false; ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted(); @@ -308,7 +309,7 @@ if (profiledduration != null) { profiledduration.finish(true); } -@@ -387,23 +468,246 @@ +@@ -387,23 +469,246 @@ protected void forceDifficulty() {} @@ -569,7 +570,7 @@ if (!iworlddataserver.isInitialized()) { try { -@@ -427,30 +731,8 @@ +@@ -427,30 +732,8 @@ iworlddataserver.setInitialized(true); } @@ -601,7 +602,7 @@ private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) { if (debugWorld) { -@@ -458,6 +740,21 @@ +@@ -458,6 +741,21 @@ } else { ServerChunkCache chunkproviderserver = world.getChunkSource(); ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); @@ -623,7 +624,7 @@ int i = chunkproviderserver.getGenerator().getSpawnHeight(world); if (i < world.getMinY()) { -@@ -516,31 +813,36 @@ +@@ -516,31 +814,36 @@ iworlddataserver.setGameType(GameType.SPECTATOR); } @@ -671,7 +672,7 @@ ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks"); if (forcedchunk != null) { -@@ -555,10 +857,17 @@ +@@ -555,10 +858,17 @@ } } @@ -693,7 +694,7 @@ } public GameType getDefaultGameType() { -@@ -588,12 +897,16 @@ +@@ -588,12 +898,16 @@ worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force); } @@ -712,7 +713,7 @@ if (flush) { Iterator iterator1 = this.getAllLevels().iterator(); -@@ -628,18 +941,46 @@ +@@ -628,18 +942,46 @@ this.stopServer(); } @@ -760,7 +761,7 @@ } MinecraftServer.LOGGER.info("Saving worlds"); -@@ -693,6 +1034,15 @@ +@@ -693,6 +1035,15 @@ } catch (IOException ioexception1) { MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1); } @@ -776,7 +777,7 @@ } -@@ -709,6 +1059,14 @@ +@@ -709,16 +1060,82 @@ } public void halt(boolean waitForShutdown) { @@ -791,10 +792,14 @@ this.running = false; if (waitForShutdown) { try { -@@ -720,6 +1078,64 @@ - - } - + this.serverThread.join(); + } catch (InterruptedException interruptedexception) { + MinecraftServer.LOGGER.error("Error while shutting down", interruptedexception); ++ } ++ } ++ ++ } ++ + // Spigot Start + private static double calcTps(double avg, double exp, double tps) + { @@ -827,9 +832,9 @@ + for (int i = 0; i < size; i++) { + this.samples[i] = dec(TPS); + this.times[i] = SEC_IN_NANO; -+ } -+ } -+ + } + } + + private static java.math.BigDecimal dec(long t) { + return new java.math.BigDecimal(t); + } @@ -848,15 +853,14 @@ + public double getAverage() { + return total.divide(dec(time), 30, java.math.RoundingMode.HALF_UP).doubleValue(); + } -+ } + } + private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL)); + // Paper end + // Spigot End -+ + protected void runServer() { try { - if (!this.initServer()) { -@@ -727,9 +1143,27 @@ +@@ -727,9 +1144,27 @@ } this.nextTickTimeNanos = Util.getNanos(); @@ -885,7 +889,7 @@ while (this.running) { long i; -@@ -744,12 +1178,31 @@ +@@ -744,12 +1179,31 @@ if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) { long k = j / i; @@ -917,7 +921,7 @@ boolean flag = i == 0L; if (this.debugCommandProfilerDelayStart) { -@@ -757,6 +1210,8 @@ +@@ -757,6 +1211,8 @@ this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount); } @@ -926,7 +930,7 @@ this.nextTickTimeNanos += i; try { -@@ -830,6 +1285,14 @@ +@@ -830,6 +1286,14 @@ this.services.profileCache().clearExecutor(); } @@ -941,7 +945,7 @@ this.onServerExit(); } -@@ -889,9 +1352,16 @@ +@@ -889,9 +1353,16 @@ } private boolean haveTime() { @@ -959,7 +963,7 @@ public static boolean throwIfFatalException() { RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get(); -@@ -903,7 +1373,7 @@ +@@ -903,7 +1374,7 @@ } public static void setFatalException(RuntimeException exception) { @@ -968,7 +972,7 @@ } @Override -@@ -961,6 +1431,7 @@ +@@ -961,6 +1432,7 @@ if (super.pollTask()) { return true; } else { @@ -976,7 +980,7 @@ if (this.tickRateManager.isSprinting() || this.haveTime()) { Iterator iterator = this.getAllLevels().iterator(); -@@ -968,16 +1439,16 @@ +@@ -968,16 +1440,16 @@ ServerLevel worldserver = (ServerLevel) iterator.next(); if (worldserver.getChunkSource().pollTask()) { @@ -996,7 +1000,7 @@ Profiler.get().incrementCounter("runTask"); super.doRunTask(ticktask); } -@@ -1025,6 +1496,7 @@ +@@ -1025,27 +1497,45 @@ } public void tickServer(BooleanSupplier shouldKeepTicking) { @@ -1004,7 +1008,13 @@ long i = Util.getNanos(); int j = this.pauseWhileEmptySeconds() * 20; -@@ -1036,16 +1508,32 @@ ++ this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping + if (j > 0) { +- if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting()) { ++ if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping + ++this.emptyTicks; + } else { + this.emptyTicks = 0; } if (this.emptyTicks >= j) { @@ -1037,7 +1047,7 @@ ++this.tickCount; this.tickRateManager.tick(); this.tickChildren(shouldKeepTicking); -@@ -1055,12 +1543,20 @@ +@@ -1055,12 +1545,20 @@ } --this.ticksUntilAutosave; @@ -1059,7 +1069,7 @@ gameprofilerfiller.push("tallying"); long k = Util.getNanos() - i; int l = this.tickCount % 100; -@@ -1069,12 +1565,17 @@ +@@ -1069,12 +1567,17 @@ this.aggregatedTickTimesNanos += k; this.tickTimesNanos[l] = k; this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float) k / (float) TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F; @@ -1078,7 +1088,7 @@ MinecraftServer.LOGGER.debug("Autosave started"); ProfilerFiller gameprofilerfiller = Profiler.get(); -@@ -1123,7 +1624,7 @@ +@@ -1123,7 +1626,7 @@ private ServerStatus buildServerStatus() { ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus(); @@ -1087,7 +1097,7 @@ } private ServerStatus.Players buildPlayerStatus() { -@@ -1133,7 +1634,7 @@ +@@ -1133,7 +1636,7 @@ if (this.hidesOnlinePlayers()) { return new ServerStatus.Players(i, list.size(), List.of()); } else { @@ -1096,7 +1106,7 @@ ObjectArrayList<GameProfile> objectarraylist = new ObjectArrayList(j); int k = Mth.nextInt(this.random, 0, list.size() - j); -@@ -1154,24 +1655,72 @@ +@@ -1154,24 +1657,72 @@ this.getPlayerList().getPlayers().forEach((entityplayer) -> { entityplayer.connection.suspendFlushing(); }); @@ -1121,7 +1131,7 @@ gameprofilerfiller.popPush("levels"); - Iterator iterator = this.getAllLevels().iterator(); + //Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; moved down - ++ + // CraftBukkit start + // Run tasks that are waiting on processing + while (!this.processQueue.isEmpty()) { @@ -1147,7 +1157,7 @@ + // Paper end - Perf: Optimize time updates + } + } -+ + + this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked + Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; move down while (iterator.hasNext()) { @@ -1170,7 +1180,7 @@ gameprofilerfiller.push("tick"); -@@ -1186,7 +1735,9 @@ +@@ -1186,7 +1737,9 @@ gameprofilerfiller.pop(); gameprofilerfiller.pop(); @@ -1180,7 +1190,7 @@ gameprofilerfiller.popPush("connection"); this.tickConnection(); -@@ -1267,6 +1818,22 @@ +@@ -1267,6 +1820,22 @@ return (ServerLevel) this.levels.get(key); } @@ -1203,7 +1213,7 @@ public Set<ResourceKey<Level>> levelKeys() { return this.levels.keySet(); } -@@ -1296,7 +1863,7 @@ +@@ -1296,7 +1865,7 @@ @DontObfuscate public String getServerModName() { @@ -1212,7 +1222,7 @@ } public SystemReport fillSystemReport(SystemReport details) { -@@ -1347,7 +1914,7 @@ +@@ -1347,7 +1916,7 @@ @Override public void sendSystemMessage(Component message) { @@ -1221,7 +1231,7 @@ } public KeyPair getKeyPair() { -@@ -1385,11 +1952,14 @@ +@@ -1385,11 +1954,14 @@ } } @@ -1241,7 +1251,7 @@ } } -@@ -1403,7 +1973,7 @@ +@@ -1403,7 +1975,7 @@ while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); @@ -1250,7 +1260,7 @@ } } -@@ -1481,10 +2051,20 @@ +@@ -1481,10 +2053,20 @@ @Override public String getMotd() { @@ -1272,7 +1282,7 @@ this.motd = motd; } -@@ -1507,7 +2087,7 @@ +@@ -1507,7 +2089,7 @@ } public ServerConnectionListener getConnection() { @@ -1281,7 +1291,7 @@ } public boolean isReady() { -@@ -1593,7 +2173,7 @@ +@@ -1593,7 +2175,7 @@ @Override public void executeIfPossible(Runnable runnable) { if (this.isStopped()) { @@ -1290,7 +1300,7 @@ } else { super.executeIfPossible(runnable); } -@@ -1632,13 +2212,19 @@ +@@ -1632,13 +2214,19 @@ return this.functionManager; } @@ -1312,7 +1322,7 @@ }, this).thenCompose((immutablelist) -> { MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist); List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess()); -@@ -1652,6 +2238,7 @@ +@@ -1652,6 +2240,7 @@ return new MinecraftServer.ReloadableResources(resourcemanager, datapackresources); }); }).thenAcceptAsync((minecraftserver_reloadableresources) -> { @@ -1320,7 +1330,7 @@ this.resources.close(); this.resources = minecraftserver_reloadableresources; this.packRepository.setSelected(dataPacks); -@@ -1660,11 +2247,23 @@ +@@ -1660,11 +2249,23 @@ this.worldData.setDataConfiguration(worlddataconfiguration); this.resources.managers.updateStaticRegistryTags(); this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures()); @@ -1344,7 +1354,7 @@ }, this); if (this.isSameThread()) { -@@ -1789,14 +2388,15 @@ +@@ -1789,14 +2390,15 @@ if (this.isEnforceWhitelist()) { PlayerList playerlist = source.getServer().getPlayerList(); UserWhiteList whitelist = playerlist.getWhiteList(); @@ -1362,7 +1372,7 @@ } } -@@ -1952,7 +2552,7 @@ +@@ -1952,7 +2554,7 @@ final List<String> list = Lists.newArrayList(); final GameRules gamerules = this.getGameRules(); @@ -1371,7 +1381,7 @@ @Override public <T extends GameRules.Value<T>> void visit(GameRules.Key<T> key, GameRules.Type<T> type) { list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key))); -@@ -2058,7 +2658,7 @@ +@@ -2058,7 +2660,7 @@ try { label51: { @@ -1380,10 +1390,13 @@ try { arraylist = Lists.newArrayList(NativeModuleLister.listModules()); -@@ -2108,6 +2708,21 @@ - - } - +@@ -2105,8 +2707,23 @@ + if (bufferedwriter != null) { + bufferedwriter.close(); + } ++ ++ } ++ + // CraftBukkit start + public boolean isDebugging() { + return false; @@ -1392,17 +1405,16 @@ + public static MinecraftServer getServer() { + return SERVER; // Paper + } -+ + + @Deprecated + public static RegistryAccess getDefaultRegistryAccess() { + return CraftRegistry.getMinecraftRegistry(); -+ } + } + // CraftBukkit end -+ + private ProfilerFiller createProfiler() { if (this.willStartRecordingMetrics) { - this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> { -@@ -2225,18 +2840,24 @@ +@@ -2225,18 +2842,24 @@ } public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) { @@ -1431,17 +1443,15 @@ } public boolean logIPs() { -@@ -2377,6 +2998,38 @@ - } - +@@ -2379,4 +3002,53 @@ public static record ServerResourcePackInfo(UUID id, String url, String hash, boolean isRequired, @Nullable Component prompt) { -+ -+ } + + } + + // Paper start - Add tick times API and /mspt command + public static class TickTimes { + private final long[] times; - ++ + public TickTimes(int length) { + times = new long[length]; + } @@ -1461,12 +1471,29 @@ + } + return ((double) total / (double) times.length) * 1.0E-6D; + } - } ++ } + // Paper end - Add tick times API and /mspt command + + // Paper start - API to check if the server is sleeping + public boolean isTickPaused() { + return this.emptyTicks > 0 && this.emptyTicks >= this.pauseWhileEmptySeconds() * 20; + } ++ ++ public void addPluginAllowingSleep(final String pluginName, final boolean value) { ++ if (!value) { ++ this.pluginsBlockingSleep.add(pluginName); ++ } else { ++ this.pluginsBlockingSleep.remove(pluginName); ++ } ++ } ++ ++ private void removeDisabledPluginsBlockingSleep() { ++ if (this.pluginsBlockingSleep.isEmpty()) { ++ return; ++ } ++ this.pluginsBlockingSleep.removeIf(plugin -> ( ++ !io.papermc.paper.plugin.manager.PaperPluginManagerImpl.getInstance().isPluginEnabled(plugin) ++ )); ++ } + // Paper end - API to check if the server is sleeping } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index df9cfdcc27..6370b780af 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -3264,5 +3264,10 @@ public final class CraftServer implements Server { public boolean isPaused() { return this.console.isTickPaused(); } + + @Override + public void allowPausing(final Plugin plugin, final boolean value) { + this.console.addPluginAllowingSleep(plugin.getName(), value); + } // Paper end - API to check if the server is sleeping }