Paper/patches/server/0025-Further-improve-server-tick-loop.patch
Jake Potrebic ac554ad46d
Updated Upstream (Bukkit/CraftBukkit) (#10691)
Updated Upstream (Bukkit/CraftBukkit)

Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
fa99e752 PR-1007: Add ItemMeta#getAsComponentString()
94a91782 Fix copy-pasted BlockType.Typed documentation
9b34ac8c Largely restore deprecated PotionData API
51a6449b PR-1008: Deprecate ITEMS_TOOLS, removed in 1.20.5
702d15fe Fix Javadoc reference
42f6cdf4 PR-919: Add internal ItemType and BlockType, delegate Material methods to them
237bb37b SPIGOT-1166, SPIGOT-7647: Expose Damager BlockState in EntityDamageByBlockEvent
035ea146 SPIGOT-6993: Allow #setVelocity to change the speed of a fireball and add a note to #setDirection about it
8c7880fb PR-1004: Improve field rename handling and centralize conversion between bukkit and string more
87c90e93 SPIGOT-7650: Add DamageSource for EntityDeathEvent and PlayerDeathEvent

CraftBukkit Changes:
4af0f22e8 SPIGOT-7664: Item meta should prevail over block states
c2ccc46ec SPIGOT-7666: Fix access to llama and horse special slot
124ac66d7 SPIGOT-7665: Fix ThrownPotion#getEffects() implementation only bringing custom effects
66f1f439a Restore null page behaviour of signed books even though not strictly allowed by API
6118e5398 Fix regression listening to minecraft:brand custom payloads
c1a26b366 Fix unnecessary and potential not thread-safe chat visibility check
12360a7ec Remove unused imports
147b098b4 PR-1397: Add ItemMeta#getAsComponentString()
428aefe0e Largely restore deprecated PotionData API
afe5b5ee9 PR-1275: Add internal ItemType and BlockType, delegate Material methods to them
8afeafa7d SPIGOT-1166, SPIGOT-7647: Expose Damager BlockState in EntityDamageByBlockEvent
4e7d749d4 SPIGOT-6993: Allow #setVelocity to change the speed of a fireball and add a note to #setDirection about it
441880757 Support both entity_data and bucket_entity_data on axolotl/fish buckets
0e22fdd1e Fix custom direct BlockState being not correctly set in DamageSource
f2182ed47 SPIGOT-7659: TropicalFishBucketMeta should use BUCKET_ENTITY_DATA
2a6207fe1 PR-1393: Improve field rename handling and centralize conversion between bukkit and string more
c024a5039 SPIGOT-7650: Add DamageSource for EntityDeathEvent and PlayerDeathEvent
741b84480 PR-1390: Improve internal handling of damage sources
0364df4e1 SPIGOT-7657: Error when loading angry entities
2024-05-11 23:48:37 +02:00

234 lines
13 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Tue, 1 Mar 2016 23:09:29 -0600
Subject: [PATCH] Further improve server tick loop
Improves how the catchup buffer is handled, allowing it to roll both ways
increasing the effeciency of the thread sleep so it only will sleep once.
Also increases the buffer of the catchup to ensure server stays at 20 TPS unless extreme conditions
Previous implementation did not calculate TPS correctly.
Switch to a realistic rolling average and factor in std deviation as an extra reporting variable
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index c91eb69bbab3ca563d77de7165d5ef39f0a6b532..8ead44e27598c01249f710e10a765dd1796ed249 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -289,7 +289,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
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;
@@ -298,7 +298,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// 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
@@ -1013,6 +1014,57 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
{
return ( avg * exp ) + ( tps * ( 1 - exp ) );
}
+
+ // 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() {
@@ -1027,7 +1079,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// 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;
@@ -1050,15 +1105,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Spigot start
++MinecraftServer.currentTickLong; // Paper - track current tick as a long
- 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;
+ // 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;
@@ -1068,7 +1130,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
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;
this.startMetricsRecordingTick();
this.profiler.push("tick");
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index b1605bcb0379f4b274db4c7aac50e1dae5bdc831..85e2293efd3f02234372f4ad3a07ffdf774bd13c 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2634,7 +2634,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/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
index d9ec48be0fdd2bfea938aa29e36b0f6ffa839ab2..9eb2823cc8f83bad2626fc77578b0162d9ed5782 100644
--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java
+++ b/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] );
+ }
+
+ 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;
+ }
}
- 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)");
+ // 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
}
}