Avoid checking for loaded chunks for lighting and entity ticking.

When a chunk is loaded the server tries to ensure it has its initial light
calculations done before sending it to the player. When ticking entities
the server tries to ensure the entity does not walk into an unloaded chunk.
To accomplish these the server checks a one chunk radius around the chunk
to be lit or a two chunk radius around the chunk the entity is in. These
lookups happen every tick even though their result is unlikely to change
that often. To reduce the cost of these checks we replace them with a
system to keep track of what neighbor chunks a chunk has loaded and update
it when chunks load or unload which is a much less frequent action. On a
server with ten players this change removes about 100,000 calls a tick to
LongObjectHashMap's containsKey method.
This commit is contained in:
Travis Watkins 2014-06-16 19:01:55 -05:00
parent ea126f98ad
commit 5b9950b5f7
4 changed files with 92 additions and 5 deletions

View File

@ -41,6 +41,34 @@ public class Chunk {
public long s; public long s;
private int x; private int x;
// CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
private int neighbors = 0x1 << 12;
public boolean areNeighborsLoaded(final int radius) {
switch(radius) {
case 2:
return this.neighbors == Integer.MAX_VALUE >> 6;
case 1:
final int mask =
// x z offset x z offset x z offset
( 0x1 << (1 * 5 + 1 + 12) ) | ( 0x1 << (0 * 5 + 1 + 12) ) | ( 0x1 << (-1 * 5 + 1 + 12) ) |
( 0x1 << (1 * 5 + 0 + 12) ) | ( 0x1 << (0 * 5 + 0 + 12) ) | ( 0x1 << (-1 * 5 + 0 + 12) ) |
( 0x1 << (1 * 5 + -1 + 12) ) | ( 0x1 << (0 * 5 + -1 + 12) ) | ( 0x1 << (-1 * 5 + -1 + 12) );
return (this.neighbors & mask) == mask;
default:
throw new UnsupportedOperationException(String.valueOf(radius));
}
}
public void setNeighborLoaded(final int x, final int z) {
this.neighbors |= 0x1 << (x * 5 + 12 + z);
}
public void setNeighborUnloaded(final int x, final int z) {
this.neighbors &= ~(0x1 << (x * 5 + 12 + z));
}
// CraftBukkit end
public Chunk(World world, int i, int j) { public Chunk(World world, int i, int j) {
this.sections = new ChunkSection[16]; this.sections = new ChunkSection[16];
this.v = new byte[256]; this.v = new byte[256];

View File

@ -94,6 +94,10 @@ public class ChunkProviderServer implements IChunkProvider {
} }
// CraftBukkit start - Add async variant, provide compatibility // CraftBukkit start - Add async variant, provide compatibility
public Chunk getChunkIfLoaded(int x, int z) {
return this.chunks.get(LongHash.toLong(x, z));
}
public Chunk getChunkAt(int i, int j) { public Chunk getChunkAt(int i, int j) {
return getChunkAt(i, j, null); return getChunkAt(i, j, null);
} }
@ -166,6 +170,21 @@ public class ChunkProviderServer implements IChunkProvider {
*/ */
server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, newChunk)); server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, newChunk));
} }
// Update neighbor counts
for (int x = -2; x < 3; x++) {
for (int z = -2; z < 3; z++) {
if (x == 0 && z == 0) {
continue;
}
Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
}
}
}
// CraftBukkit end // CraftBukkit end
chunk.a(this, this, i, j); chunk.a(this, this, i, j);
} }
@ -327,6 +346,21 @@ public class ChunkProviderServer implements IChunkProvider {
// this.unloadQueue.remove(olong); // this.unloadQueue.remove(olong);
// this.chunks.remove(olong.longValue()); // this.chunks.remove(olong.longValue());
// Update neighbor counts
for (int x = -2; x < 3; x++) {
for (int z = -2; z < 3; z++) {
if (x == 0 && z == 0) {
continue;
}
Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
if (neighbor != null) {
neighbor.setNeighborUnloaded(-x, -z);
chunk.setNeighborUnloaded(x, z);
}
}
}
} }
} }
// CraftBukkit end // CraftBukkit end
@ -357,8 +391,8 @@ public class ChunkProviderServer implements IChunkProvider {
} }
public int getLoadedChunks() { public int getLoadedChunks() {
// CraftBukkit - this.chunks.count() -> .values().size() // CraftBukkit - this.chunks.count() -> this.chunks.size()
return this.chunks.values().size(); return this.chunks.size();
} }
public void recreateStructures(int i, int j) {} public void recreateStructures(int i, int j) {}

View File

@ -115,6 +115,10 @@ public abstract class World implements IBlockAccess {
return (CraftServer) Bukkit.getServer(); return (CraftServer) Bukkit.getServer();
} }
public Chunk getChunkIfLoaded(int x, int z) {
return ((ChunkProviderServer) this.chunkProvider).getChunkIfLoaded(x, z);
}
// Changed signature - added gen and env // Changed signature - added gen and env
public World(IDataManager idatamanager, String s, WorldSettings worldsettings, WorldProvider worldprovider, MethodProfiler methodprofiler, ChunkGenerator gen, org.bukkit.World.Environment env) { public World(IDataManager idatamanager, String s, WorldSettings worldsettings, WorldProvider worldprovider, MethodProfiler methodprofiler, ChunkGenerator gen, org.bukkit.World.Environment env) {
this.generator = gen; this.generator = gen;
@ -1396,7 +1400,10 @@ public abstract class World implements IBlockAccess {
int j = MathHelper.floor(entity.locZ); int j = MathHelper.floor(entity.locZ);
byte b0 = 32; byte b0 = 32;
if (!flag || this.b(i - b0, 0, j - b0, i + b0, 0, j + b0)) { // CraftBukkit start - Use neighbor cache instead of looking up
Chunk startingChunk = this.getChunkIfLoaded(i >> 4, j >> 4);
if (!flag || (startingChunk != null && startingChunk.areNeighborsLoaded(2)) /* this.b(i - b0, 0, j - b0, i + b0, 0, j + b0) */) {
// CraftBukkit end
entity.S = entity.locX; entity.S = entity.locX;
entity.T = entity.locY; entity.T = entity.locY;
entity.U = entity.locZ; entity.U = entity.locZ;
@ -2165,7 +2172,10 @@ public abstract class World implements IBlockAccess {
} }
public boolean c(EnumSkyBlock enumskyblock, int i, int j, int k) { public boolean c(EnumSkyBlock enumskyblock, int i, int j, int k) {
if (!this.areChunksLoaded(i, j, k, 17)) { // CraftBukkit start - Use neighbor cache instead of looking up
Chunk chunk = this.getChunkIfLoaded(i >> 4, k >> 4);
if (chunk == null || !chunk.areNeighborsLoaded(1) /* !this.areChunksLoaded(i, j, k, 17)*/) {
// CraftBukkit end
return false; return false;
} else { } else {
int l = 0; int l = 0;

View File

@ -28,7 +28,7 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
// sync stuff // sync stuff
public void callStage2(QueuedChunk queuedChunk, Chunk chunk) throws RuntimeException { public void callStage2(QueuedChunk queuedChunk, Chunk chunk) throws RuntimeException {
if(chunk == null) { if (chunk == null) {
// If the chunk loading failed just do it synchronously (may generate) // If the chunk loading failed just do it synchronously (may generate)
queuedChunk.provider.originalGetChunkAt(queuedChunk.x, queuedChunk.z); queuedChunk.provider.originalGetChunkAt(queuedChunk.x, queuedChunk.z);
return; return;
@ -48,6 +48,21 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, false)); server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, false));
} }
// Update neighbor counts
for (int x = -2; x < 3; x++) {
for (int z = -2; z < 3; z++) {
if (x == 0 && z == 0) {
continue;
}
Chunk neighbor = queuedChunk.provider.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
}
}
}
chunk.a(queuedChunk.provider, queuedChunk.provider, queuedChunk.x, queuedChunk.z); chunk.a(queuedChunk.provider, queuedChunk.provider, queuedChunk.x, queuedChunk.z);
} }