2016-03-18 23:03:44 +01:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 18 Mar 2016 17:57:25 -0400
Subject: [PATCH] Optimize Chunk Unload Queue
Removing chunks from the unload queue when performing chunk lookups is a costly activity.
It drastically slows down server performance as many methods call getChunkAt, resulting in a bandaid
to skip removing chunks from the unload queue.
This patch optimizes the unload queue to instead use a boolean on the Chunk object itself to mark
if the chunk is active, and then insert into a LinkedList queue.
The benefits here is that all chunk unload queue actions are now O(1) constant time.
A LinkedList will never need to resize, and can be removed from in constant time when
used in a queue like method.
We mark the chunk as active in many places that notify it is still being used, so that
when the chunk unload queue reaches that chunk, and sees the chunk became active again,
it will skip it and move to next.
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -0,0 +0,0 @@ public class Chunk {
// Keep this synced with entitySlices.add() and entitySlices.remove()
private final int[] itemCounts = new int[16];
private final int[] inventoryEntityCounts = new int[16];
+ public boolean isChunkActive = true;
+ public boolean isInUnloadQueue = false;
// Paper end
// CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
@@ -0,0 +0,0 @@ public class Chunk {
}
public void addEntities() {
+ isChunkActive = true; // Paper
this.i = true;
this.world.b(this.tileEntities.values());
@@ -0,0 +0,0 @@ public class Chunk {
}
public void removeEntities() {
+ isChunkActive = false; // Paper
this.i = false;
Iterator iterator = this.tileEntities.values().iterator();
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -0,0 +0,0 @@ import org.bukkit.event.world.ChunkUnloadEvent;
public class ChunkProviderServer implements IChunkProvider {
private static final Logger a = LogManager.getLogger();
- public final LongHashSet unloadQueue = new LongHashSet(); // CraftBukkit - LongHashSet
+ public final ChunkUnloadQueue unloadQueue = new ChunkUnloadQueue(); // CraftBukkit - LongHashSet // Paper -> ChunkUnloadQueue
public final ChunkGenerator chunkGenerator; // CraftBukkit - public
private final IChunkLoader chunkLoader;
public LongObjectHashMap<Chunk> chunks = new LongObjectHashMap<Chunk>(); // CraftBukkit
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
// CraftBukkit start - Add async variant, provide compatibility
public Chunk getOrCreateChunkFast(int x, int z) {
- Chunk chunk = chunks.get(LongHash.toLong(x, z));
- return (chunk == null) ? getChunkAt(x, z) : chunk;
+ return getChunkAt(x, z); // Paper
2016-03-20 05:13:20 +01:00
+ }
+
2016-03-19 16:29:46 +01:00
+ // Paper start
+ public Chunk getLoadedChunkAtWithoutMarkingActive(int i, int j) {
+ return chunks.get(LongHash.toLong(i, j));
2016-03-20 05:13:20 +01:00
}
- public Chunk getChunkIfLoaded(int x, int z) {
2016-03-18 23:03:44 +01:00
- return chunks.get(LongHash.toLong(x, z));
2016-03-20 05:13:20 +01:00
+ // getChunkIfLoaded -> getChunkIfActive
+ // this is only used by CraftBukkit now, and plugins shouldnt mark things active
+ public Chunk getChunkIfActive(int x, int z) {
+ Chunk chunk = chunks.get(LongHash.toLong(x, z));
+ return (chunk != null && chunk.isChunkActive) ? chunk : null;
2016-03-18 23:03:44 +01:00
}
2016-03-20 05:13:20 +01:00
+ // Paper end
2016-03-18 23:03:44 +01:00
public Chunk getLoadedChunkAt(int i, int j) {
Chunk chunk = chunks.get(LongHash.toLong(i, j)); // CraftBukkit
this.unloadQueue.remove(i, j); // CraftBukkit
+ if (chunk != null) { chunk.isChunkActive = true; }// Paper
return chunk;
}
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
runnable.run();
}
+ chunk.isChunkActive = true; // Paper
return chunk;
}
2016-03-19 16:29:46 +01:00
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
continue;
}
- Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
+ Chunk neighbor = this.getLoadedChunkAtWithoutMarkingActive(chunk.locX + x, chunk.locZ + z); // Paper
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
2016-03-18 23:03:44 +01:00
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
if (!this.world.savingDisabled) {
// CraftBukkit start
Server server = this.world.getServer();
- for (int i = 0; i < 100 && !this.unloadQueue.isEmpty(); ++i) {
- long chunkcoordinates = this.unloadQueue.popFirst();
- Chunk chunk = this.chunks.get(chunkcoordinates);
- if (chunk == null) continue;
+ // Paper start
+ final Iterator<Chunk> iterator = this.unloadQueue.iterator();
+ for (int i = 0; i < 100 && iterator.hasNext(); ++i) {
+ Chunk chunk = iterator.next();
+ iterator.remove();
+ long chunkcoordinates = LongHash.toLong(chunk.locX, chunk.locZ);
+ chunk.isInUnloadQueue = false;
+ if (chunk.isChunkActive) {
+ continue;
+ }
+ // Paper end
ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk);
server.getPluginManager().callEvent(event);
2016-03-19 16:29:46 +01:00
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
continue;
}
- Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
+ Chunk neighbor = this.getLoadedChunkAtWithoutMarkingActive(chunk.locX + x, chunk.locZ + z); // Paper
if (neighbor != null) {
neighbor.setNeighborUnloaded(-x, -z);
chunk.setNeighborUnloaded(x, z);
2016-03-18 23:03:44 +01:00
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
public boolean e(int i, int j) {
return this.chunks.containsKey(LongHash.toLong(i, j)); // CraftBukkit
}
+
+ // Paper start
+ public class ChunkUnloadQueue extends java.util.LinkedList<Chunk> {
+ public void remove(int x, int z) {
+ // nothing! Just to reduce diff
+ }
+ public void add(int x, int z) {
+ final Chunk chunk = chunks.get(LongHash.toLong(x, z));
+ if (chunk != null) {
+ chunk.isChunkActive = false;
+ if (!chunk.isInUnloadQueue) {
+ chunk.isInUnloadQueue = true;
+ add(chunk);
+ }
+ }
+ }
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/SpawnerCreature.java
+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
@@ -0,0 +0,0 @@ public final class SpawnerCreature {
Long coord = it.next();
int x = LongHash.msw( coord );
int z = LongHash.lsw( coord );
- if ( !((ChunkProviderServer)server.chunkProvider).unloadQueue.contains( coord ) && server.isChunkLoaded( x, z, true ) )
+ if ( server.isChunkLoaded( x, z, true ) ) // Paper
{
i += server.getChunkAt( x, z ).entityCount.get( oClass );
}
2016-03-20 05:13:20 +01:00
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
}
public Chunk getChunkIfLoaded(int x, int z) {
- return ((ChunkProviderServer) this.chunkProvider).getChunkIfLoaded(x, z);
+ return ((ChunkProviderServer) this.chunkProvider).getLoadedChunkAtWithoutMarkingActive(x, z); // Paper - This is added by CB, and will not mark as active. Simply an alias
}
+ // Paper start
+ public Chunk getChunkIfActive(int x, int z) {
+ return ((ChunkProviderServer) this.chunkProvider).getChunkIfActive(x, z);
+ }
+ // Paper end
+
protected World(IDataManager idatamanager, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag, ChunkGenerator gen, org.bukkit.World.Environment env) {
this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper
2016-03-18 23:03:44 +01:00
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
2016-03-20 05:13:20 +01:00
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
}
// Paper start - Don't create a chunk just to unload it
- net.minecraft.server.Chunk chunk = world.getChunkProviderServer().getChunkIfLoaded(x, z);
+ net.minecraft.server.Chunk chunk = world.getChunkProviderServer().getChunkIfActive(x, z); // Paper
if (chunk == null) {
return false;
}
2016-03-19 16:29:46 +01:00
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
continue;
}
- net.minecraft.server.Chunk neighbor = world.getChunkProviderServer().getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
+ net.minecraft.server.Chunk neighbor = world.getChunkProviderServer().getLoadedChunkAtWithoutMarkingActive(chunk.locX + x, chunk.locZ + z); // Paper
if (neighbor != null) {
neighbor.setNeighborUnloaded(-xx, -zz);
chunk.setNeighborUnloaded(xx, zz);
2016-03-18 23:03:44 +01:00
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
world.timings.syncChunkLoadTimer.startTiming(); // Spigot
chunk = world.getChunkProviderServer().getOrLoadChunkAt(x, z);
world.timings.syncChunkLoadTimer.stopTiming(); // Spigot
- }
+ } else { chunk.isChunkActive = true; } // Paper
return chunk != null;
}
2016-03-19 16:29:46 +01:00
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
continue;
}
- net.minecraft.server.Chunk neighbor = world.getChunkProviderServer().getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
+ net.minecraft.server.Chunk neighbor = world.getChunkProviderServer().getLoadedChunkAtWithoutMarkingActive(chunk.locX + x, chunk.locZ + z); // Paper
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
2016-03-18 23:03:44 +01:00
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
}
// Already unloading?
- if (cps.unloadQueue.contains(chunk.locX, chunk.locZ)) {
+ if (!chunk.isChunkActive) { // Paper
continue;
}
2016-03-19 16:29:46 +01:00
diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
@@ -0,0 +0,0 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
continue;
}
- Chunk neighbor = queuedChunk.provider.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
+ Chunk neighbor = queuedChunk.provider.getLoadedChunkAtWithoutMarkingActive(chunk.locX + x, chunk.locZ + z); // Paper
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
2016-03-20 05:13:20 +01:00
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -0,0 +0,0 @@ public class ActivationRange
int x = MathHelper.floor( entity.locX );
int z = MathHelper.floor( entity.locZ );
// Make sure not on edge of unloaded chunk
- Chunk chunk = entity.world.getChunkIfLoaded( x >> 4, z >> 4 );
+ Chunk chunk = entity.world.getChunkIfActive( x >> 4, z >> 4 ); // Paper
if ( isActive && !( chunk != null && chunk.areNeighborsLoaded( 1 ) ) )
{
isActive = false;
2016-03-18 23:03:44 +01:00
--