From 1e9573a2fb6f8757a7f5ef540d162b19a40f0e4b Mon Sep 17 00:00:00 2001 From: Aikar <aikar@aikar.co> Date: Sat, 18 Jun 2016 23:22:12 -0400 Subject: [PATCH] Delay Chunk Unloads based on Player Movement When players are moving in the world, doing things such as building or exploring, they will commonly go back and forth in a small area. This causes a ton of chunk load and unload activity on the edge chunks of their view distance. A simple back and forth movement in 6 blocks could spam a chunk to thrash a loading and unload cycle over and over again. This is very wasteful. This system introduces a delay of inactivity on a chunk before it actually unloads, which is maintained separately from ChunkGC. This allows servers with smaller worlds who do less long distance exploring to stop wasting cpu cycles on saving/unloading/reloading chunks repeatedly. diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index b9b0f74..cda516f 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -351,4 +351,13 @@ public class PaperWorldConfig { private void isHopperPushBased() { isHopperPushBased = getBoolean("hopper.push-based", false); } + + public long delayChunkUnloadsBy; + private void delayChunkUnloadsBy() { + delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "10s")); + if (delayChunkUnloadsBy > 0) { + log("Delaying chunk unloads by " + delayChunkUnloadsBy + " seconds"); + delayChunkUnloadsBy *= 1000; + } + } } diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 98f2cff..88437d7 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -30,6 +30,7 @@ public class Chunk { private boolean j; public final World world; public final int[] heightMap; + public Long scheduledForUnload; // Paper - delay chunk unloads public final int locX; public final int locZ; private boolean m; diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index bd44764..7a56a64 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -300,6 +300,19 @@ public class ChunkProviderServer implements IChunkProvider { } } } + // Paper start - delayed chunk unloads + long now = System.currentTimeMillis(); + long unloadAfter = world.paperConfig.delayChunkUnloadsBy; + if (unloadAfter > 0) { + //noinspection Convert2streamapi + for (Chunk chunk : chunks.values()) { + if (chunk.scheduledForUnload != null && now - chunk.scheduledForUnload > unloadAfter) { + chunk.scheduledForUnload = null; + unload(chunk); + } + } + } + // Paper end this.chunkLoader.a(); } diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java index dd40e98..f109e98 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -32,8 +32,16 @@ public class PlayerChunk { public void run() { loadInProgress = false; PlayerChunk.this.chunk = PlayerChunk.this.playerChunkMap.getWorld().getChunkProviderServer().getOrLoadChunkAt(location.x, location.z); + markChunkUsed(); // Paper - delay chunk unloads } }; + // Paper start - delay chunk unloads + public final void markChunkUsed() { + if (chunk != null && chunk.scheduledForUnload != null) { + chunk.scheduledForUnload = null; + } + } + // Paper end // CraftBukkit end public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) { @@ -42,6 +50,7 @@ public class PlayerChunk { // CraftBukkit start loadInProgress = true; this.chunk = playerchunkmap.getWorld().getChunkProviderServer().getChunkAt(i, j, loadedRunnable, false); + markChunkUsed(); // Paper - delay chunk unloads // CraftBukkit end } @@ -110,6 +119,7 @@ public class PlayerChunk { if (!loadInProgress) { loadInProgress = true; this.chunk = playerChunkMap.getWorld().getChunkProviderServer().getChunkAt(this.location.x, this.location.z, loadedRunnable, flag); + markChunkUsed(); // Paper - delay chunk unloads } // CraftBukkit end diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index d970e2f..d3c454c 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -483,7 +483,13 @@ public class PlayerChunkMap { Chunk chunk = playerchunk.f(); if (chunk != null) { - this.getWorld().getChunkProviderServer().unload(chunk); + // Paper start - delay chunk unloads + if (world.paperConfig.delayChunkUnloadsBy <= 0) { + this.getWorld().getChunkProviderServer().unload(chunk); + } else { + chunk.scheduledForUnload = System.currentTimeMillis(); + } + // Paper end } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 98da2ea..ade5ba0 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1557,7 +1557,7 @@ public class CraftWorld implements World { ChunkProviderServer cps = world.getChunkProviderServer(); for (net.minecraft.server.Chunk chunk : cps.chunks.values()) { // If in use, skip it - if (isChunkInUse(chunk.locX, chunk.locZ)) { + if (isChunkInUse(chunk.locX, chunk.locZ) || chunk.scheduledForUnload != null) { // Paper - delayed chunk unloads continue; } -- 2.9.3