From 8135f1abbc84ae533bfdc7cff553095dbe60dc5a 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 f0ce5ba7f..9dcab42de 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -245,13 +245,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 + Iterator iterator = this.unloadQueue.iterator(); - for (int i = 0; i < 100 && iterator.hasNext(); iterator.remove()) { + while (iterator.hasNext()) { // Spigot Long olong = (Long) iterator.next(); + iterator.remove(); // Spigot Chunk chunk = (Chunk) this.chunks.get(olong); if (chunk != null && chunk.d) { @@ -261,9 +270,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.chunkLoader.b(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 989b4a175..e396b3822 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -48,6 +48,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 ICommandListener, Runnable, IAsyncTaskHandler, IMojangStatistics { @@ -122,6 +123,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs 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, DataConverterManager dataconvertermanager, YggdrasilAuthenticationService yggdrasilauthenticationservice, MinecraftSessionService minecraftsessionservice, GameProfileRepository gameprofilerepository, UserCache usercache) { @@ -662,6 +664,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs protected void C() throws ExceptionWorldConflict { // CraftBukkit - added throws SpigotTimings.serverTickTimer.startTiming(); // Spigot + this.slackActivityAccountant.tickStarted(); // Spigot long i = System.nanoTime(); ++this.ticks; @@ -707,7 +710,10 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs } this.methodProfiler.a("tallying"); - this.h[this.ticks % 100] = System.nanoTime() - i; + // Spigot start + long tickNanos; + this.h[this.ticks % 100] = tickNanos = System.nanoTime() - i; + // Spigot end this.methodProfiler.b(); this.methodProfiler.a("snooper"); if (getSnooperEnabled() && !this.m.d() && this.ticks > 100) { // Spigot @@ -720,7 +726,9 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs this.methodProfiler.b(); this.methodProfiler.b(); + org.spigotmc.WatchdogThread.tick(); // Spigot + this.slackActivityAccountant.tickEnded(tickNanos); // 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 7e9fc5145..eeac34998 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -153,8 +153,11 @@ public class PlayerChunkMap { } if (!this.h.isEmpty()) { - long k = System.nanoTime() + 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()) { @@ -169,8 +172,7 @@ public class PlayerChunkMap { this.g.remove(playerchunk1); } - --l; - if (l < 0 || System.nanoTime() > k) { + if (activityAccountant.activityTimeIsExhausted()) { // Spigot break; } } @@ -180,6 +182,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.11.0