mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-30 16:19:03 +01:00
dd60ba4869
The lighting queue spreads out the processing of light updates across multiple ticks based on how much free time the server has left at the end of the tick.
350 lines
No EOL
16 KiB
Diff
350 lines
No EOL
16 KiB
Diff
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
|
|
if (chunk.hasLightUpdates()) continue; // Paper - Don't unload chunks with pending light updates.
|
|
|
|
ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk);
|
|
@@ -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;
|
|
--
|