From b9e65990e2f057112dd633f18ef04f0ed2b72af0 Mon Sep 17 00:00:00 2001 From: Geoff Crossland Date: Thu, 26 Jan 2017 20:54:55 +0000 Subject: [PATCH] Replace chunk loading / unloading rate throttling. Uses a scheme based on estimated tick slack time. See https://hub.spigotmc.org/stash/projects/SPIGOT/repos/spigot/pull-requests/71/overview diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index 62925a0c7..d35203cc5 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -272,13 +272,22 @@ public class ChunkProviderServer implements IChunkProvider { this.chunkLoader.c(); } + private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96; + public boolean unloadChunks() { if (!this.world.savingDisabled) { if (!this.unloadQueue.isEmpty()) { + // Spigot start + org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant; + activityAccountant.startActivity(0.5); + int targetSize = (int) (this.unloadQueue.size() * UNLOAD_QUEUE_RESIZE_FACTOR); + // Spigot end + LongIterator longiterator = this.unloadQueue.iterator(); - for (int i = 0; i < 100 && longiterator.hasNext(); longiterator.remove()) { + while (longiterator.hasNext()) { // Spigot Long olong = (Long) longiterator.next(); + longiterator.remove(); // Spigot Chunk chunk = (Chunk) this.chunks.get(olong); if (chunk != null && chunk.d) { @@ -288,9 +297,15 @@ public class ChunkProviderServer implements IChunkProvider { } // CraftBukkit end - ++i; + // Spigot start + if (this.unloadQueue.size() <= targetSize && activityAccountant.activityTimeIsExhausted()) { + break; + } + // Spigot end } } + + activityAccountant.endActivity(); // Spigot } this.f.a(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index d85fab954..71ee66a9b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -63,6 +63,7 @@ import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.Main; // CraftBukkit end import org.bukkit.craftbukkit.SpigotTimings; // Spigot +import org.spigotmc.SlackActivityAccountant; // Spigot public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStatistics, ICommandListener, Runnable { @@ -152,6 +153,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati public static final int TICK_TIME = 1000000000 / TPS; private static final int SAMPLE_INTERVAL = 100; public final double[] recentTps = new double[ 3 ]; + public final SlackActivityAccountant slackActivityAccountant = new SlackActivityAccountant(); // Spigot end public MinecraftServer(OptionSet options, Proxy proxy, DataFixer datafixer, CommandDispatcher commanddispatcher, YggdrasilAuthenticationService yggdrasilauthenticationservice, MinecraftSessionService minecraftsessionservice, GameProfileRepository gameprofilerepository, UserCache usercache) { @@ -804,6 +806,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati protected void v() { SpigotTimings.serverTickTimer.startTiming(); // Spigot + this.slackActivityAccountant.tickStarted(); // Spigot long i = SystemUtils.c(); ++this.ticks; @@ -864,6 +867,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati this.methodProfiler.e(); this.methodProfiler.e(); org.spigotmc.WatchdogThread.tick(); // Spigot + this.slackActivityAccountant.tickEnded(l); // Spigot SpigotTimings.serverTickTimer.stopTiming(); // Spigot org.spigotmc.CustomTimingsHandler.tick(); // Spigot } diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index d7c299a41..ac6d8cc6e 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -127,8 +127,11 @@ public class PlayerChunkMap { } if (!this.h.isEmpty()) { - long k = SystemUtils.c() + 50000000L; - int l = 49; + // Spigot start + org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant; + activityAccountant.startActivity(0.5); + // Spigot end + Iterator iterator1 = this.h.iterator(); while (iterator1.hasNext()) { @@ -143,8 +146,7 @@ public class PlayerChunkMap { this.g.remove(playerchunk1); } - --l; - if (l < 0 || SystemUtils.c() > k) { + if (activityAccountant.activityTimeIsExhausted()) { // Spigot break; } } @@ -154,6 +156,8 @@ public class PlayerChunkMap { } // CraftBukkit end } + + activityAccountant.endActivity(); // Spigot } if (!this.g.isEmpty()) { diff --git a/src/main/java/org/spigotmc/SlackActivityAccountant.java b/src/main/java/org/spigotmc/SlackActivityAccountant.java new file mode 100644 index 000000000..aabc7ad20 --- /dev/null +++ b/src/main/java/org/spigotmc/SlackActivityAccountant.java @@ -0,0 +1,78 @@ +package org.spigotmc; + +/** + * Keeps track of the time spent doing main thread activities that can be spread across ticks, + * so that such work doesn't exceed the current tick's estimated available slack time. Each + * activity is allotted a proportion of the expected slack time according to its weight, versus the + * estimated total weight of all activities. + */ +public class SlackActivityAccountant { + private double prevTickSlackWeightReciprocal = 1 / MIN_SLACK_WEIGHT; + private static final double MIN_SLACK_WEIGHT = 1 / 65536.0; + private double averageTickNonSlackNanos = 0; + private static final double AVERAGING_FACTOR = 0.375; + + private long currentActivityStartNanos; + private static final long OFF = -1; + private long currentActivityEndNanos; + private double tickSlackWeight; + private long tickSlackNanos; + + private double getSlackFraction(double slackWeight) { + return Math.min(slackWeight * this.prevTickSlackWeightReciprocal, 1); + } + + private int getEstimatedSlackNanos() { + return (int) Math.max(net.minecraft.server.MinecraftServer.TICK_TIME - (long) this.averageTickNonSlackNanos, 0); + } + + public void tickStarted() { + this.currentActivityStartNanos = OFF; + this.tickSlackWeight = 0; + this.tickSlackNanos = 0; + } + + public void startActivity(double slackWeight) { + double slackFraction0 = getSlackFraction(this.tickSlackWeight); + this.tickSlackWeight += slackWeight; + double slackFraction1 = getSlackFraction(this.tickSlackWeight); + + long t = System.nanoTime(); + this.currentActivityStartNanos = t; + this.currentActivityEndNanos = t + ((int) ((slackFraction1 - slackFraction0) * this.getEstimatedSlackNanos())); + } + + private void endActivity(long endNanos) { + this.tickSlackNanos += endNanos - this.currentActivityStartNanos; + this.currentActivityStartNanos = OFF; + } + + public boolean activityTimeIsExhausted() { + if (this.currentActivityStartNanos == OFF) { + return true; + } + + long t = System.nanoTime(); + if (t <= this.currentActivityEndNanos) { + return false; + } else { + this.endActivity(this.currentActivityEndNanos); + return true; + } + } + + public void endActivity() { + if (this.currentActivityStartNanos == OFF) { + return; + } + + this.endActivity(Math.min(System.nanoTime(), this.currentActivityEndNanos)); + } + + public void tickEnded(long tickNanos) { + this.prevTickSlackWeightReciprocal = 1 / Math.max(this.tickSlackWeight, MIN_SLACK_WEIGHT); + + long tickNonSlackNanos = tickNanos - this.tickSlackNanos; + this.averageTickNonSlackNanos = this.averageTickNonSlackNanos * (1 - AVERAGING_FACTOR) + tickNonSlackNanos * AVERAGING_FACTOR; + } +} -- 2.17.1