From fb0eed177a8fd6bed39571003fbc6852478c9b07 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Mon, 10 Dec 2012 09:38:26 -0600 Subject: [PATCH] [Bleeding] Implement periodic chunk garbage collector This adds two settings to bukkit.yml, allowing activation and control of two chunk garbage collection triggering conditions: chunk-gc/period-in-ticks controls a periodic GC, run once every N ticks (default is 600); chunk-gc/load-threshold causes the GC to run once after every N calls to loadChunk() on a given world (this call is an API call used by plugins, and is distinct from the path taken for routine player movement-based loading). In both cases, setting to zero will disable the given GC scheduling strategy. In either case, the act of doing the GC is simply one of scanning the loaded chunks, seeing which are NOT being used by one or more players (due to view-distance) and which are not already queued for unload, and queueing them for a normal unload. Ultimately, the unload is then processed the same as if the chunk were unloaded due to leaving the view-distance range of all players, so the impact on plugins should be no different (and strategies such as handling the ChunkUnloadEvent in order to prevent unload will still work). The initial interval for the periodic GC is randomized on a per-world basis, in order to avoid all world being GCed at the same time - minimizing potential lag spikes. --- .../net/minecraft/server/WorldServer.java | 2 + .../org/bukkit/craftbukkit/CraftServer.java | 6 +++ .../org/bukkit/craftbukkit/CraftWorld.java | 38 +++++++++++++++++++ src/main/resources/configurations/bukkit.yml | 3 ++ 4 files changed, 49 insertions(+) diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index 48fdcab53e..b426d5d963 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -181,6 +181,8 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate this.P.a(this.getTime()); this.methodProfiler.b(); this.V(); + + this.getWorld().processChunkGC(); // CraftBukkit } public BiomeMeta a(EnumCreatureType enumcreaturetype, int i, int j, int k) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 9642959f44..936cbc64a1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -157,6 +157,8 @@ public final class CraftServer implements Server { private int animalSpawn = -1; private int waterAnimalSpawn = -1; private int ambientSpawn = -1; + public int chunkGCPeriod = -1; + public int chunkGCLoadThresh = 0; private File container; private WarningState warningState = WarningState.DEFAULT; private final BooleanWrapper online = new BooleanWrapper(); @@ -202,6 +204,8 @@ public final class CraftServer implements Server { ambientSpawn = configuration.getInt("spawn-limits.ambient"); console.autosavePeriod = configuration.getInt("ticks-per.autosave"); warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); + chunkGCPeriod = configuration.getInt("chunk-gc.period-in-ticks"); + chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold"); updater = new AutoUpdater(new BukkitDLUpdaterService(configuration.getString("auto-updater.host")), getLogger(), configuration.getString("auto-updater.preferred-channel")); updater.setEnabled(configuration.getBoolean("auto-updater.enabled")); @@ -537,6 +541,8 @@ public final class CraftServer implements Server { ambientSpawn = configuration.getInt("spawn-limits.ambient"); warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); console.autosavePeriod = configuration.getInt("ticks-per.autosave"); + chunkGCPeriod = configuration.getInt("chunk-gc.period-in-ticks"); + chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold"); for (WorldServer world : console.worlds) { world.difficulty = difficulty; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index b4646d1bf0..cb200667c4 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1,6 +1,7 @@ package org.bukkit.craftbukkit; import java.io.File; +import java.util.Iterator; import java.util.Set; import java.util.ArrayList; import java.util.Collection; @@ -58,6 +59,8 @@ public class CraftWorld implements World { private int animalSpawn = -1; private int waterAnimalSpawn = -1; private int ambientSpawn = -1; + private int chunkLoadCount = 0; + private int chunkGCTickCount; private static final Random rand = new Random(); @@ -66,6 +69,10 @@ public class CraftWorld implements World { this.generator = gen; environment = env; + + if (server.chunkGCPeriod > 0) { + chunkGCTickCount = rand.nextInt(server.chunkGCPeriod); + } } public Block getBlockAt(int x, int y, int z) { @@ -226,6 +233,7 @@ public class CraftWorld implements World { } public boolean loadChunk(int x, int z, boolean generate) { + chunkLoadCount++; if (generate) { // Use the default variant of loadChunk when generate == true. return world.chunkProviderServer.getChunkAt(x, z) != null; @@ -1233,4 +1241,34 @@ public class CraftWorld implements World { public boolean isGameRule(String rule) { return getHandle().getGameRules().e(rule); } + + public void processChunkGC() { + chunkGCTickCount++; + + if (chunkLoadCount >= server.chunkGCLoadThresh && server.chunkGCLoadThresh > 0) { + chunkLoadCount = 0; + } else if (chunkGCTickCount >= server.chunkGCPeriod && server.chunkGCPeriod > 0) { + chunkGCTickCount = 0; + } else { + return; + } + + ChunkProviderServer cps = world.chunkProviderServer; + Iterator iter = cps.chunks.values().iterator(); + while (iter.hasNext()) { + net.minecraft.server.Chunk chunk = iter.next(); + // If in use, skip it + if (isChunkInUse(chunk.x, chunk.z)) { + continue; + } + + // Already unloading? + if (cps.unloadQueue.contains(chunk.x, chunk.z)) { + continue; + } + + // Add unload request + cps.queueUnload(chunk.x, chunk.z); + } + } } diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index e4d6d96fe6..61a95e3373 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -30,6 +30,9 @@ spawn-limits: animals: 15 water-animals: 5 ambient: 15 +chunk-gc: + period-in-ticks: 600 + load-threshold: 0 ticks-per: animal-spawns: 400 monster-spawns: 1