papermc/Spigot-Server-Patches/0123-Delay-Chunk-Unloads-based-on-Player-Movement.patch
Zach Brown a2ad49b22f
Update upstream BD/B/CB/S
Note to other developers: This commit may require you to wipe your
workspace as a result of the changes to BD.

--- work/BuildData
Submodule work/BuildData f527a8ff..d56672db:
  > Mappings Update

--- work/Bukkit
Submodule work/Bukkit 0c1d258bb..db06c80d7:
  > Add list of entities to EntityTransformEvent
  > SPIGOT-4347: Add API to allow storing arbitrary values on ItemStacks

---work/CraftBukkit
Submodule work/CraftBukkit 6a398ac44..068dab5be:
  > Enable optional source JAR shading via profile shadeSourcesJar
  > Use ImmutableList rather than AbstractList for CraftMetaBook
  > Fix setRecipes(List) not setting Knowledge Book recipes.
  > Mappings Update
  > Add list of entities to EntityTransformEvent & move die calls
  > SPIGOT-4347: Add API to allow storing arbitrary values on ItemStacks
  > Add Vanilla help to default permissions

--- work/Spigot
Submodule work/Spigot a1f2566f6..e769fe4d9:
  > Mappings Update
  > Rebuild patches
2018-12-08 05:09:55 -05:00

231 lines
11 KiB
Diff

From 675812f4004abac1cb4ee36a8564d3c268195253 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.
This also makes the Chunk GC System useless, by auto scheduling unload as soon as
a spare chunk is added to the server thats outside of view distance.
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index ff1a2046f..0cd15c17e 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -296,4 +296,18 @@ public class PaperWorldConfig {
preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false);
log("Prevent TNT from moving in water: " + preventTntFromMovingInWater);
}
+
+ 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;
+ }
+ }
+
+ public boolean skipEntityTickingInChunksScheduledForUnload = true;
+ private void skipEntityTickingInChunksScheduledForUnload() {
+ skipEntityTickingInChunksScheduledForUnload = getBoolean("skip-entity-ticking-in-chunks-scheduled-for-unload", skipEntityTickingInChunksScheduledForUnload);
+ }
}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 757105c6f..9328c21cd 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -36,6 +36,7 @@ public class Chunk implements IChunkAccess {
private boolean i;public boolean isLoaded() { return i; } // Paper - OBFHELPER
public final World world;
public final Map<HeightMap.Type, HeightMap> heightMap;
+ public Long scheduledForUnload; // Paper - delay chunk unloads
public final int locX;
public final int locZ;
private boolean l;
diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
index 2ebb2d834..e14ae2b42 100644
--- a/src/main/java/net/minecraft/server/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/ChunkMap.java
@@ -48,6 +48,15 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
}
}
}
+ // Paper start - if this is a spare chunk (not part of any players view distance), go ahead and queue it for unload.
+ if (!((WorldServer)chunk.world).getPlayerChunkMap().isChunkInUse(chunk.locX, chunk.locZ)) {
+ if (chunk.world.paperConfig.delayChunkUnloadsBy > 0) {
+ chunk.scheduledForUnload = System.currentTimeMillis();
+ } else {
+ ((WorldServer) chunk.world).getChunkProviderServer().unload(chunk);
+ }
+ }
+ // Paper end
chunk.world.timings.syncChunkLoadPostTimer.stopTiming(); // Paper
// CraftBukkit end
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 1d08ec37f..516a583a8 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -308,6 +308,19 @@ public class ChunkProviderServer implements IChunkProvider {
}
activityAccountant.endActivity(); // Spigot
}
+ // 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.chunkScheduler.a(booleansupplier);
}
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index c7ea17e25..7cb1327a8 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -33,8 +33,22 @@ public class PlayerChunk {
public void run() {
loadInProgress = false;
PlayerChunk.this.chunk = PlayerChunk.this.playerChunkMap.getWorld().getChunkProviderServer().getChunkAt(location.x, location.z, true, true);
+ markChunkUsed(); // Paper - delay chunk unloads
}
};
+ // Paper start - delay chunk unloads
+ private void markChunkUsed() {
+ if (chunk == null) {
+ return;
+ }
+ if (chunkHasPlayers) {
+ chunk.scheduledForUnload = null;
+ } else if (chunk.scheduledForUnload == null) {
+ chunk.scheduledForUnload = System.currentTimeMillis();
+ }
+ }
+ private boolean chunkHasPlayers = false;
+ // Paper end
// CraftBukkit end
public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) {
@@ -44,6 +58,7 @@ public class PlayerChunk {
chunkproviderserver.a(i, j);
this.chunk = chunkproviderserver.getChunkAt(i, j, true, false);
+ markChunkUsed(); // Paper - delay chunk unloads
}
public ChunkCoordIntPair a() {
@@ -56,6 +71,8 @@ public class PlayerChunk {
} else {
if (this.c.isEmpty()) {
this.i = this.playerChunkMap.getWorld().getTime();
+ chunkHasPlayers = true; // Paper - delay chunk unloads
+ markChunkUsed(); // Paper - delay chunk unloads
}
this.c.add(entityplayer);
@@ -74,6 +91,8 @@ public class PlayerChunk {
this.c.remove(entityplayer);
if (this.c.isEmpty()) {
+ chunkHasPlayers = false; // Paper - delay chunk unloads
+ markChunkUsed(); // Paper - delay chunk unloads
this.playerChunkMap.b(this);
}
@@ -85,6 +104,7 @@ public class PlayerChunk {
return true;
} else {
this.chunk = this.playerChunkMap.getWorld().getChunkProviderServer().getChunkAt(this.location.x, this.location.z, true, flag);
+ markChunkUsed(); // Paper - delay chunk unloads
return this.chunk != null;
}
}
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index b8d8fc779..e5605c309 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -460,7 +460,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/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 49432f2eb..288152775 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1342,7 +1342,13 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
if (!tileentity.x() && tileentity.hasWorld()) {
BlockPosition blockposition = tileentity.getPosition();
- if (this.isLoaded(blockposition) && this.K.a(blockposition)) {
+ // Paper start - Skip ticking in chunks scheduled for unload
+ net.minecraft.server.Chunk chunk = this.getChunkIfLoaded(blockposition);
+ boolean shouldTick = chunk != null;
+ if(this.paperConfig.skipEntityTickingInChunksScheduledForUnload)
+ shouldTick = shouldTick && chunk.scheduledForUnload == null;
+ if (shouldTick && this.K.a(blockposition)) {
+ // Paper end
try {
this.methodProfiler.a(() -> {
return String.valueOf(TileEntityTypes.a(tileentity.C()));
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 24e504c27..f0e974dff 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1757,7 +1757,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;
}
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index d08ef3fe1..081789a8f 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -323,6 +323,11 @@ public class ActivationRange
{
isActive = false;
}
+ // Paper start - Skip ticking in chunks scheduled for unload
+ else if (entity.world.paperConfig.skipEntityTickingInChunksScheduledForUnload && (chunk == null || chunk.scheduledForUnload != null)) {
+ isActive = false;
+ }
+ // Paper end
return isActive;
}
}
--
2.19.2