mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-23 19:15:32 +01:00
Load all already generated chunks via async chunk system
Currently we use the async chunk loading system only when players trigger chunk loading. If a chunk is loaded for any other reason it goes through a separate codepath. This means if a player has trigged a chunk load and before the chunk loads something else wants the same chunk we will load it twice and throw away the second result. With this change we instead use the sync processing feature of the AsynchronousExecutor to load the chunk which will pull it out of the queue if it was already queued to load. This means we only ever load a chunk once. Because chunk generation is not thread safe we still fallback to the vanilla chunk loading system if the chunk does not currently exist.
This commit is contained in:
parent
4a6a81e724
commit
d7d81fa68f
@ -14,6 +14,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
|
||||||
import org.bukkit.craftbukkit.util.LongHash;
|
import org.bukkit.craftbukkit.util.LongHash;
|
||||||
import org.bukkit.craftbukkit.util.LongHashSet;
|
import org.bukkit.craftbukkit.util.LongHashSet;
|
||||||
import org.bukkit.craftbukkit.util.LongObjectHashMap;
|
import org.bukkit.craftbukkit.util.LongObjectHashMap;
|
||||||
@ -90,20 +91,37 @@ public class ChunkProviderServer implements IChunkProvider {
|
|||||||
|
|
||||||
public Chunk getChunkAt(int i, int j, Runnable runnable) {
|
public Chunk getChunkAt(int i, int j, Runnable runnable) {
|
||||||
this.unloadQueue.remove(i, j);
|
this.unloadQueue.remove(i, j);
|
||||||
Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j));
|
Chunk chunk = this.chunks.get(LongHash.toLong(i, j));
|
||||||
boolean newChunk = false;
|
|
||||||
ChunkRegionLoader loader = null;
|
ChunkRegionLoader loader = null;
|
||||||
|
|
||||||
if (this.f instanceof ChunkRegionLoader) {
|
if (this.f instanceof ChunkRegionLoader) {
|
||||||
loader = (ChunkRegionLoader) this.f;
|
loader = (ChunkRegionLoader) this.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the chunk exists but isn't loaded do it async
|
// We can only use the queue for already generated chunks
|
||||||
if (chunk == null && runnable != null && loader != null && loader.chunkExists(this.world, i, j)) {
|
if (chunk == null && loader != null && loader.chunkExists(this.world, i, j)) {
|
||||||
org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.queueChunkLoad(this.world, loader, this, i, j, runnable);
|
if (runnable != null) {
|
||||||
|
ChunkIOExecutor.queueChunkLoad(this.world, loader, this, i, j, runnable);
|
||||||
return null;
|
return null;
|
||||||
|
} else {
|
||||||
|
chunk = ChunkIOExecutor.syncChunkLoad(this.world, loader, this, i, j);
|
||||||
}
|
}
|
||||||
// CraftBukkit end
|
} else if (chunk == null) {
|
||||||
|
chunk = this.originalGetChunkAt(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't load the chunk async and have a callback run it now
|
||||||
|
if (runnable != null) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk originalGetChunkAt(int i, int j) {
|
||||||
|
this.unloadQueue.remove(i, j);
|
||||||
|
Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j));
|
||||||
|
boolean newChunk = false;
|
||||||
|
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
chunk = this.loadChunk(i, j);
|
chunk = this.loadChunk(i, j);
|
||||||
@ -143,12 +161,6 @@ public class ChunkProviderServer implements IChunkProvider {
|
|||||||
chunk.a(this, this, i, j);
|
chunk.a(this, this, i, j);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CraftBukkit start - If we didn't need to load the chunk run the callback now
|
|
||||||
if (runnable != null) {
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
// CraftBukkit end
|
|
||||||
|
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import net.minecraft.server.ChunkProviderServer;
|
|||||||
import net.minecraft.server.ChunkRegionLoader;
|
import net.minecraft.server.ChunkRegionLoader;
|
||||||
import net.minecraft.server.World;
|
import net.minecraft.server.World;
|
||||||
import org.bukkit.craftbukkit.util.AsynchronousExecutor;
|
import org.bukkit.craftbukkit.util.AsynchronousExecutor;
|
||||||
import org.bukkit.craftbukkit.util.LongHash;
|
|
||||||
|
|
||||||
public class ChunkIOExecutor {
|
public class ChunkIOExecutor {
|
||||||
static final int BASE_THREADS = 1;
|
static final int BASE_THREADS = 1;
|
||||||
@ -13,12 +12,12 @@ public class ChunkIOExecutor {
|
|||||||
|
|
||||||
private static final AsynchronousExecutor<QueuedChunk, Chunk, Runnable, RuntimeException> instance = new AsynchronousExecutor<QueuedChunk, Chunk, Runnable, RuntimeException>(new ChunkIOProvider(), BASE_THREADS);
|
private static final AsynchronousExecutor<QueuedChunk, Chunk, Runnable, RuntimeException> instance = new AsynchronousExecutor<QueuedChunk, Chunk, Runnable, RuntimeException>(new ChunkIOProvider(), BASE_THREADS);
|
||||||
|
|
||||||
public static void waitForChunkLoad(World world, int x, int z) {
|
public static Chunk syncChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z) {
|
||||||
instance.get(new QueuedChunk(LongHash.toLong(x, z), null, world, null));
|
return instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void queueChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable) {
|
public static void queueChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable) {
|
||||||
instance.add(new QueuedChunk(LongHash.toLong(x, z), loader, world, provider), runnable);
|
instance.add(new QueuedChunk(x, z, loader, world, provider), runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void adjustPoolSize(int players) {
|
public static void adjustPoolSize(int players) {
|
||||||
|
@ -16,7 +16,7 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
|
|||||||
// async stuff
|
// async stuff
|
||||||
public Chunk callStage1(QueuedChunk queuedChunk) throws RuntimeException {
|
public Chunk callStage1(QueuedChunk queuedChunk) throws RuntimeException {
|
||||||
ChunkRegionLoader loader = queuedChunk.loader;
|
ChunkRegionLoader loader = queuedChunk.loader;
|
||||||
Object[] data = loader.loadChunk(queuedChunk.world, LongHash.msw(queuedChunk.coords), LongHash.lsw(queuedChunk.coords));
|
Object[] data = loader.loadChunk(queuedChunk.world, queuedChunk.x, queuedChunk.z);
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
queuedChunk.compound = (NBTTagCompound) data[1];
|
queuedChunk.compound = (NBTTagCompound) data[1];
|
||||||
@ -30,27 +30,17 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
|
|||||||
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.getChunkAt(LongHash.msw(queuedChunk.coords), LongHash.lsw(queuedChunk.coords));
|
queuedChunk.provider.originalGetChunkAt(queuedChunk.x, queuedChunk.z);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x = LongHash.msw(queuedChunk.coords);
|
|
||||||
int z = LongHash.lsw(queuedChunk.coords);
|
|
||||||
|
|
||||||
// See if someone already loaded this chunk while we were working on it (API, etc)
|
|
||||||
if (queuedChunk.provider.chunks.containsKey(queuedChunk.coords)) {
|
|
||||||
// Make sure it isn't queued for unload, we need it
|
|
||||||
queuedChunk.provider.unloadQueue.remove(queuedChunk.coords);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedChunk.loader.loadEntities(chunk, queuedChunk.compound.getCompound("Level"), queuedChunk.world);
|
queuedChunk.loader.loadEntities(chunk, queuedChunk.compound.getCompound("Level"), queuedChunk.world);
|
||||||
chunk.p = queuedChunk.provider.world.getTime();
|
chunk.p = queuedChunk.provider.world.getTime();
|
||||||
queuedChunk.provider.chunks.put(queuedChunk.coords, chunk);
|
queuedChunk.provider.chunks.put(LongHash.toLong(queuedChunk.x, queuedChunk.z), chunk);
|
||||||
chunk.addEntities();
|
chunk.addEntities();
|
||||||
|
|
||||||
if (queuedChunk.provider.chunkProvider != null) {
|
if (queuedChunk.provider.chunkProvider != null) {
|
||||||
queuedChunk.provider.chunkProvider.recreateStructures(x, z);
|
queuedChunk.provider.chunkProvider.recreateStructures(queuedChunk.x, queuedChunk.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
Server server = queuedChunk.provider.world.getServer();
|
Server server = queuedChunk.provider.world.getServer();
|
||||||
@ -58,7 +48,7 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.a(queuedChunk.provider, queuedChunk.provider, x, z);
|
chunk.a(queuedChunk.provider, queuedChunk.provider, queuedChunk.x, queuedChunk.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void callStage3(QueuedChunk queuedChunk, Chunk chunk, Runnable runnable) throws RuntimeException {
|
public void callStage3(QueuedChunk queuedChunk, Chunk chunk, Runnable runnable) throws RuntimeException {
|
||||||
|
@ -6,14 +6,16 @@ import net.minecraft.server.NBTTagCompound;
|
|||||||
import net.minecraft.server.World;
|
import net.minecraft.server.World;
|
||||||
|
|
||||||
class QueuedChunk {
|
class QueuedChunk {
|
||||||
final long coords;
|
final int x;
|
||||||
|
final int z;
|
||||||
final ChunkRegionLoader loader;
|
final ChunkRegionLoader loader;
|
||||||
final World world;
|
final World world;
|
||||||
final ChunkProviderServer provider;
|
final ChunkProviderServer provider;
|
||||||
NBTTagCompound compound;
|
NBTTagCompound compound;
|
||||||
|
|
||||||
public QueuedChunk(long coords, ChunkRegionLoader loader, World world, ChunkProviderServer provider) {
|
public QueuedChunk(int x, int z, ChunkRegionLoader loader, World world, ChunkProviderServer provider) {
|
||||||
this.coords = coords;
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
@ -21,14 +23,14 @@ class QueuedChunk {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return (int) coords ^ world.hashCode();
|
return (x * 31 + z * 29) ^ world.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object object) {
|
public boolean equals(Object object) {
|
||||||
if (object instanceof QueuedChunk) {
|
if (object instanceof QueuedChunk) {
|
||||||
QueuedChunk other = (QueuedChunk) object;
|
QueuedChunk other = (QueuedChunk) object;
|
||||||
return coords == other.coords && world == other.world;
|
return x == other.x && z == other.z && world == other.world;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user