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 58df649b64..d5225db86a 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -118,7 +118,7 @@ private int playerIdleTimeout; private final long[] tickTimesNanos; private long aggregatedTickTimesNanos; -@@ -277,6 +302,25 @@ +@@ -277,6 +302,26 @@ private final SuppressedExceptionCollector suppressedExceptions; private final DiscontinuousFrame tickFrame; @@ -127,7 +127,7 @@ + public org.bukkit.craftbukkit.CraftServer server; + public OptionSet options; + public org.bukkit.command.ConsoleCommandSender console; -+ public static int currentTick = (int) (System.currentTimeMillis() / 50); ++ public static int currentTick; // Paper - improve tick loop + public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>(); + public int autosavePeriod; + public Commands vanillaCommandDispatcher; @@ -136,7 +136,8 @@ + // Spigot start + public static final int TPS = 20; + public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS; -+ private static final int SAMPLE_INTERVAL = 100; ++ private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop ++ @Deprecated(forRemoval = true) // Paper + public final double[] recentTps = new double[ 3 ]; + // Spigot end + public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files @@ -144,7 +145,7 @@ public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) { AtomicReference<S> atomicreference = new AtomicReference(); Thread thread = new Thread(() -> { -@@ -290,14 +334,14 @@ +@@ -290,14 +335,14 @@ thread.setPriority(8); } @@ -161,7 +162,7 @@ super("Server"); this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; this.onMetricsRecordingStopped = (methodprofilerresults) -> { -@@ -319,36 +363,68 @@ +@@ -319,36 +364,68 @@ this.scoreboard = new ServerScoreboard(this); this.customBossEvents = new CustomBossEvents(); this.suppressedExceptions = new SuppressedExceptionCollector(); @@ -245,7 +246,7 @@ } private void readScoreboard(DimensionDataStorage persistentStateManager) { -@@ -357,7 +433,7 @@ +@@ -357,7 +434,7 @@ protected abstract boolean initServer() throws IOException; @@ -254,7 +255,7 @@ if (!JvmProfiler.INSTANCE.isRunning()) { ; } -@@ -365,12 +441,8 @@ +@@ -365,12 +442,8 @@ boolean flag = false; ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted(); @@ -268,7 +269,7 @@ if (profiledduration != null) { profiledduration.finish(true); } -@@ -387,23 +459,218 @@ +@@ -387,23 +460,218 @@ protected void forceDifficulty() {} @@ -501,7 +502,7 @@ if (!iworlddataserver.isInitialized()) { try { -@@ -427,30 +694,8 @@ +@@ -427,30 +695,8 @@ iworlddataserver.setInitialized(true); } @@ -533,7 +534,7 @@ private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) { if (debugWorld) { -@@ -458,6 +703,21 @@ +@@ -458,6 +704,21 @@ } else { ServerChunkCache chunkproviderserver = world.getChunkSource(); ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); @@ -555,7 +556,7 @@ int i = chunkproviderserver.getGenerator().getSpawnHeight(world); if (i < world.getMinY()) { -@@ -516,31 +776,36 @@ +@@ -516,31 +777,36 @@ iworlddataserver.setGameType(GameType.SPECTATOR); } @@ -603,7 +604,7 @@ ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks"); if (forcedchunk != null) { -@@ -555,10 +820,17 @@ +@@ -555,10 +821,17 @@ } } @@ -625,7 +626,7 @@ } public GameType getDefaultGameType() { -@@ -588,12 +860,16 @@ +@@ -588,12 +861,16 @@ worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force); } @@ -644,7 +645,7 @@ if (flush) { Iterator iterator1 = this.getAllLevels().iterator(); -@@ -628,18 +904,41 @@ +@@ -628,18 +905,41 @@ this.stopServer(); } @@ -686,7 +687,7 @@ } MinecraftServer.LOGGER.info("Saving worlds"); -@@ -693,6 +992,15 @@ +@@ -693,6 +993,15 @@ } catch (IOException ioexception1) { MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1); } @@ -702,21 +703,76 @@ } -@@ -720,6 +1028,13 @@ - - } - +@@ -715,10 +1024,68 @@ + 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) + { + return ( avg * exp ) + ( tps * ( 1 - exp ) ); + } -+ // Spigot End + ++ // Paper start - Further improve server tick loop ++ private static final long SEC_IN_NANO = 1000000000; ++ private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L; ++ private long lastTick = 0; ++ private long catchupTime = 0; ++ public final RollingAverage tps1 = new RollingAverage(60); ++ public final RollingAverage tps5 = new RollingAverage(60 * 5); ++ public final RollingAverage tps15 = new RollingAverage(60 * 15); ++ ++ public static class RollingAverage { ++ private final int size; ++ private long time; ++ private java.math.BigDecimal total; ++ private int index = 0; ++ private final java.math.BigDecimal[] samples; ++ private final long[] times; ++ ++ RollingAverage(int size) { ++ this.size = size; ++ this.time = size * SEC_IN_NANO; ++ 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] = dec(TPS); ++ this.times[i] = SEC_IN_NANO; ++ } ++ } ++ ++ private static java.math.BigDecimal dec(long t) { ++ return new java.math.BigDecimal(t); ++ } ++ public void add(java.math.BigDecimal x, long t) { ++ time -= times[index]; ++ total = total.subtract(samples[index].multiply(dec(times[index]))); ++ samples[index] = x; ++ times[index] = t; ++ time += t; ++ total = total.add(x.multiply(dec(t))); ++ if (++index == size) { ++ index = 0; + } + } + ++ 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 +1042,12 @@ +@@ -727,9 +1094,15 @@ } this.nextTickTimeNanos = Util.getNanos(); @@ -726,11 +782,14 @@ + // Spigot start + Arrays.fill( this.recentTps, 20 ); -+ long tickSection = Util.getMillis(), tickCount = 1; ++ // Paper start - further improve server tick loop ++ long tickSection = Util.getNanos(); ++ long currentTime; ++ // Paper end - further improve server tick loop while (this.running) { long i; -@@ -744,11 +1062,23 @@ +@@ -744,12 +1117,31 @@ if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) { long k = j / i; @@ -741,28 +800,37 @@ } } + // Spigot start -+ if ( tickCount++ % MinecraftServer.SAMPLE_INTERVAL == 0 ) -+ { -+ long curTime = Util.getMillis(); -+ double currentTps = 1E3 / ( curTime - tickSection ) * MinecraftServer.SAMPLE_INTERVAL; -+ this.recentTps[0] = MinecraftServer.calcTps( this.recentTps[0], 0.92, currentTps ); // 1/exp(5sec/1min) -+ this.recentTps[1] = MinecraftServer.calcTps( this.recentTps[1], 0.9835, currentTps ); // 1/exp(5sec/5min) -+ this.recentTps[2] = MinecraftServer.calcTps( this.recentTps[2], 0.9945, currentTps ); // 1/exp(5sec/15min) -+ tickSection = curTime; -+ } -+ // Spigot end ++ // Paper start - further improve server tick loop ++ currentTime = Util.getNanos(); ++ if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) { ++ final long diff = currentTime - tickSection; ++ final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP); ++ tps1.add(currentTps, diff); ++ tps5.add(currentTps, diff); ++ tps15.add(currentTps, diff); ++ // Backwards compat with bad plugins ++ this.recentTps[0] = tps1.getAverage(); ++ this.recentTps[1] = tps5.getAverage(); ++ this.recentTps[2] = tps15.getAverage(); ++ tickSection = currentTime; ++ } ++ // Paper end - further improve server tick loop ++ // Spigot end ++ boolean flag = i == 0L; -@@ -757,6 +1087,7 @@ + if (this.debugCommandProfilerDelayStart) { +@@ -757,6 +1149,8 @@ this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount); } -+ MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit ++ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time ++ lastTick = currentTime; this.nextTickTimeNanos += i; try { -@@ -830,6 +1161,13 @@ +@@ -830,6 +1224,13 @@ this.services.profileCache().clearExecutor(); } @@ -776,7 +844,7 @@ this.onServerExit(); } -@@ -889,9 +1227,16 @@ +@@ -889,9 +1290,16 @@ } private boolean haveTime() { @@ -794,7 +862,7 @@ public static boolean throwIfFatalException() { RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get(); -@@ -903,7 +1248,7 @@ +@@ -903,7 +1311,7 @@ } public static void setFatalException(RuntimeException exception) { @@ -803,7 +871,7 @@ } @Override -@@ -977,7 +1322,7 @@ +@@ -977,7 +1385,7 @@ } } @@ -812,7 +880,7 @@ Profiler.get().incrementCounter("runTask"); super.doRunTask(ticktask); } -@@ -1025,6 +1370,7 @@ +@@ -1025,6 +1433,7 @@ } public void tickServer(BooleanSupplier shouldKeepTicking) { @@ -820,7 +888,7 @@ long i = Util.getNanos(); int j = this.pauseWhileEmptySeconds() * 20; -@@ -1041,11 +1387,13 @@ +@@ -1041,11 +1450,13 @@ this.autoSave(); } @@ -834,7 +902,7 @@ ++this.tickCount; this.tickRateManager.tick(); this.tickChildren(shouldKeepTicking); -@@ -1055,7 +1403,7 @@ +@@ -1055,7 +1466,7 @@ } --this.ticksUntilAutosave; @@ -843,7 +911,7 @@ this.autoSave(); } -@@ -1071,10 +1419,13 @@ +@@ -1071,10 +1482,13 @@ this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float) k / (float) TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F; this.logTickMethodTime(i); gameprofilerfiller.pop(); @@ -858,7 +926,7 @@ MinecraftServer.LOGGER.debug("Autosave started"); ProfilerFiller gameprofilerfiller = Profiler.get(); -@@ -1082,6 +1433,7 @@ +@@ -1082,6 +1496,7 @@ this.saveEverything(true, false, false); gameprofilerfiller.pop(); MinecraftServer.LOGGER.debug("Autosave finished"); @@ -866,7 +934,7 @@ } private void logTickMethodTime(long tickStartTime) { -@@ -1123,7 +1475,7 @@ +@@ -1123,7 +1538,7 @@ private ServerStatus buildServerStatus() { ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus(); @@ -875,7 +943,7 @@ } private ServerStatus.Players buildPlayerStatus() { -@@ -1154,11 +1506,35 @@ +@@ -1154,11 +1569,35 @@ this.getPlayerList().getPlayers().forEach((entityplayer) -> { entityplayer.connection.suspendFlushing(); }); @@ -911,7 +979,7 @@ while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); -@@ -1167,16 +1543,20 @@ +@@ -1167,16 +1606,20 @@ return s + " " + String.valueOf(worldserver.dimension().location()); }); @@ -932,7 +1000,7 @@ } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); -@@ -1189,18 +1569,24 @@ +@@ -1189,18 +1632,24 @@ } gameprofilerfiller.popPush("connection"); @@ -957,12 +1025,10 @@ gameprofilerfiller.popPush("send chunks"); iterator = this.playerList.getPlayers().iterator(); -@@ -1265,7 +1651,23 @@ - @Nullable - public ServerLevel getLevel(ResourceKey<Level> key) { +@@ -1267,6 +1716,22 @@ return (ServerLevel) this.levels.get(key); -+ } -+ + } + + // CraftBukkit start + public void addLevel(ServerLevel level) { + Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels; @@ -976,12 +1042,13 @@ + Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); + newLevels.remove(level.dimension()); + this.levels = Collections.unmodifiableMap(newLevels); - } ++ } + // CraftBukkit end - ++ public Set<ResourceKey<Level>> levelKeys() { return this.levels.keySet(); -@@ -1296,7 +1698,7 @@ + } +@@ -1296,7 +1761,7 @@ @DontObfuscate public String getServerModName() { @@ -990,7 +1057,7 @@ } public SystemReport fillSystemReport(SystemReport details) { -@@ -1347,7 +1749,7 @@ +@@ -1347,7 +1812,7 @@ @Override public void sendSystemMessage(Component message) { @@ -999,7 +1066,7 @@ } public KeyPair getKeyPair() { -@@ -1481,10 +1883,20 @@ +@@ -1481,10 +1946,20 @@ @Override public String getMotd() { @@ -1021,7 +1088,7 @@ this.motd = motd; } -@@ -1507,7 +1919,7 @@ +@@ -1507,7 +1982,7 @@ } public ServerConnectionListener getConnection() { @@ -1030,7 +1097,7 @@ } public boolean isReady() { -@@ -1634,11 +2046,11 @@ +@@ -1634,11 +2109,11 @@ public CompletableFuture<Void> reloadResources(Collection<String> dataPacks) { CompletableFuture<Void> completablefuture = CompletableFuture.supplyAsync(() -> { @@ -1044,7 +1111,7 @@ }, this).thenCompose((immutablelist) -> { MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist); List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess()); -@@ -1654,6 +2066,7 @@ +@@ -1654,6 +2129,7 @@ }).thenAcceptAsync((minecraftserver_reloadableresources) -> { this.resources.close(); this.resources = minecraftserver_reloadableresources; @@ -1052,7 +1119,7 @@ this.packRepository.setSelected(dataPacks); WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()); -@@ -1952,7 +2365,7 @@ +@@ -1952,7 +2428,7 @@ final List<String> list = Lists.newArrayList(); final GameRules gamerules = this.getGameRules(); @@ -1061,7 +1128,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 +2471,7 @@ +@@ -2058,7 +2534,7 @@ try { label51: { @@ -1070,33 +1137,32 @@ try { arraylist = Lists.newArrayList(NativeModuleLister.listModules()); -@@ -2105,9 +2518,25 @@ +@@ -2105,8 +2581,24 @@ if (bufferedwriter != null) { bufferedwriter.close(); } + + } - ++ + // CraftBukkit start + public boolean isDebugging() { + return false; - } - ++ } ++ + @Deprecated + public static MinecraftServer getServer() { + return (Bukkit.getServer() instanceof CraftServer) ? ((CraftServer) Bukkit.getServer()).getServer() : null; + } -+ + + @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 +2654,24 @@ +@@ -2225,18 +2717,24 @@ } public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) { 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 5c54c5c525..c9920b60a5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -2680,7 +2680,11 @@ public final class CraftServer implements Server { @Override public double[] getTPS() { - return new double[]{0, 0, 0}; // TODO + return new double[] { + net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), + net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), + net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() + }; } // Paper start - adventure sounds diff --git a/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java index d9ec48be0f..9eb2823cc8 100644 --- a/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java +++ b/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java @@ -15,6 +15,12 @@ public class TicksPerSecondCommand extends Command this.usageMessage = "/tps"; this.setPermission( "bukkit.command.tps" ); } + // Paper start + private static final net.kyori.adventure.text.Component WARN_MSG = net.kyori.adventure.text.Component.text() + .append(net.kyori.adventure.text.Component.text("Warning: ", net.kyori.adventure.text.format.NamedTextColor.RED)) + .append(net.kyori.adventure.text.Component.text("Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention.", net.kyori.adventure.text.format.NamedTextColor.GOLD)) + .build(); + // Paper end @Override public boolean execute(CommandSender sender, String currentAlias, String[] args) @@ -24,22 +30,40 @@ public class TicksPerSecondCommand extends Command return true; } - StringBuilder sb = new StringBuilder( ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " ); - for ( double tps : MinecraftServer.getServer().recentTps ) - { - sb.append( this.format( tps ) ); - sb.append( ", " ); + // Paper start - Further improve tick handling + double[] tps = org.bukkit.Bukkit.getTPS(); + net.kyori.adventure.text.Component[] tpsAvg = new net.kyori.adventure.text.Component[tps.length]; + + for ( int i = 0; i < tps.length; i++) { + tpsAvg[i] = TicksPerSecondCommand.format( tps[i] ); } - sender.sendMessage( sb.substring( 0, sb.length() - 2 ) ); - sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " - + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); + + net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); + builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)); + builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg)); + sender.sendMessage(builder.asComponent()); + if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { + sender.sendMessage(net.kyori.adventure.text.Component.text() + .append(net.kyori.adventure.text.Component.text("Current Memory Usage: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)) + .append(net.kyori.adventure.text.Component.text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", net.kyori.adventure.text.format.NamedTextColor.GREEN)) + ); + if (!this.hasShownMemoryWarning) { + sender.sendMessage(WARN_MSG); + this.hasShownMemoryWarning = true; + } + } + // Paper end return true; } - private String format(double tps) + private boolean hasShownMemoryWarning; // Paper + private static net.kyori.adventure.text.Component format(double tps) // Paper - Made static { - return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() - + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); + // Paper + net.kyori.adventure.text.format.TextColor color = ( ( tps > 18.0 ) ? net.kyori.adventure.text.format.NamedTextColor.GREEN : ( tps > 16.0 ) ? net.kyori.adventure.text.format.NamedTextColor.YELLOW : net.kyori.adventure.text.format.NamedTextColor.RED ); + String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0 ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise + return net.kyori.adventure.text.Component.text(amount, color); + // Paper end } }