Another attempt at unload queue, including EAR improvements.
should be fully working now as I pretty much fell back to existing methods so anything touching the unloadQueue set should behave correctly. And maintained NMS Reflection safe change too
This commit is contained in:
350
Spigot-Server-Patches/Optimize-Chunk-Unload-Queue.patch
Normal file
350
Spigot-Server-Patches/Optimize-Chunk-Unload-Queue.patch
Normal file
@@ -0,0 +1,350 @@
|
||||
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.
|
||||
|
||||
Also optimize EAR to use these methods.
|
||||
|
||||
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
|
||||
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 {
|
||||
return (chunk == null) ? getChunkAt(x, z) : chunk;
|
||||
}
|
||||
|
||||
- public Chunk getChunkIfLoaded(int x, int z) {
|
||||
- return chunks.get(LongHash.toLong(x, z));
|
||||
+ // Paper start
|
||||
+ public Chunk getLoadedChunkAtWithoutMarkingActive(int i, int j) {
|
||||
+ return chunks.get(LongHash.toLong(i, j));
|
||||
}
|
||||
|
||||
+ // 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;
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
public Chunk getLoadedChunkAt(int i, int j) {
|
||||
Chunk chunk = chunks.get(LongHash.toLong(i, j)); // CraftBukkit
|
||||
|
||||
- this.unloadQueue.remove(i, j); // CraftBukkit
|
||||
+ //this.unloadQueue.remove(i, j); // CraftBukkit // Paper
|
||||
+ markChunkActive(chunk); // Paper
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
+ markChunkActive(chunk); // Paper
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
|
||||
}
|
||||
|
||||
public Chunk getChunkAt(int i, int j, Runnable runnable) {
|
||||
- unloadQueue.remove(i, j);
|
||||
+ //unloadQueue.remove(i, j); // Paper
|
||||
Chunk chunk = chunks.get(LongHash.toLong(i, j));
|
||||
ChunkRegionLoader loader = null;
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
+ markChunkActive(chunk); // Paper
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public Chunk originalGetChunkAt(int i, int j) {
|
||||
- this.unloadQueue.remove(i, j);
|
||||
+ //this.unloadQueue.remove(i, j); // Paper
|
||||
Chunk chunk = this.chunks.get(LongHash.toLong(i, j));
|
||||
boolean newChunk = false;
|
||||
// CraftBukkit end
|
||||
@@ -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);
|
||||
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
|
||||
chunk.loadNearby(this, this.chunkGenerator);
|
||||
world.timings.syncChunkLoadTimer.stopTiming(); // Spigot
|
||||
}
|
||||
+ markChunkActive(chunk); // Paper
|
||||
|
||||
return chunk;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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 static void markChunkActive(Chunk chunk) {
|
||||
+ if (chunk != null) {
|
||||
+ chunk.isChunkActive = true;
|
||||
+ }
|
||||
+ }
|
||||
+ public class ChunkUnloadQueue extends LongHashSet { // Provide compat with NMS plugins
|
||||
+ private java.util.LinkedList<Chunk> unloadQueue = new java.util.LinkedList<Chunk>();
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isEmpty() {
|
||||
+ return unloadQueue.isEmpty();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean contains(long value) {
|
||||
+ throw new UnsupportedOperationException("contains on unload queue");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean add(long value) {
|
||||
+ throw new UnsupportedOperationException("add on unload queue");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean remove(long value) {
|
||||
+ throw new UnsupportedOperationException("remove on unload queue");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int size() {
|
||||
+ return unloadQueue.size();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Iterator iterator() {
|
||||
+ return unloadQueue.iterator();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean contains(int x, int z) {
|
||||
+ final Chunk chunk = chunks.get(LongHash.toLong(x, z));
|
||||
+ return (chunk != null && chunk.isInUnloadQueue);
|
||||
+ }
|
||||
+
|
||||
+ public void remove(int x, int z) {
|
||||
+ final Chunk chunk = chunks.get(LongHash.toLong(x, z));
|
||||
+ if (chunk != null) {
|
||||
+ chunk.isChunkActive = true;
|
||||
+ }
|
||||
+ }
|
||||
+ public boolean 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;
|
||||
+ return unloadQueue.add(chunk);
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+ // 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 );
|
||||
}
|
||||
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 {
|
||||
// Paper end
|
||||
|
||||
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
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
|
||||
// Use the default variant of loadChunk when generate == true.
|
||||
return world.getChunkProviderServer().getChunkAt(x, z) != null;
|
||||
}
|
||||
+ if (true) { return world.getChunkProviderServer().getOrLoadChunkAt(x, z) != null; } // Paper
|
||||
|
||||
world.getChunkProviderServer().unloadQueue.remove(x, z);
|
||||
net.minecraft.server.Chunk chunk = world.getChunkProviderServer().chunks.get(LongHash.toLong(x, z));
|
||||
@@ -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);
|
||||
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
|
||||
}
|
||||
|
||||
// Already unloading?
|
||||
- if (cps.unloadQueue.contains(chunk.locX, chunk.locZ)) {
|
||||
+ if (chunk.isInUnloadQueue) { // Paper
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
{
|
||||
for ( int j1 = k; j1 <= l; ++j1 )
|
||||
{
|
||||
- if ( world.getWorld().isChunkLoaded( i1, j1 ) )
|
||||
- {
|
||||
- activateChunkEntities( world.getChunkAt( i1, j1 ) );
|
||||
+ Chunk chunk = world.getChunkIfActive(i1, j1); // Paper
|
||||
+ if (chunk != null) { // Paper
|
||||
+ activateChunkEntities( chunk ); // Paper
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = isActive ? entity.world.getChunkIfActive( x >> 4, z >> 4 ) : null; // Paper
|
||||
if ( isActive && !( chunk != null && chunk.areNeighborsLoaded( 1 ) ) )
|
||||
{
|
||||
isActive = false;
|
||||
--
|
||||
Reference in New Issue
Block a user