diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java b/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java new file mode 100644 index 0000000000..92fbc4f95d --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java @@ -0,0 +1,32 @@ +package org.bukkit.craftbukkit.chunkio; + +import net.minecraft.server.Chunk; +import net.minecraft.server.ChunkProviderServer; +import net.minecraft.server.ChunkRegionLoader; +import net.minecraft.server.World; +import org.bukkit.craftbukkit.util.AsynchronousExecutor; +import org.bukkit.craftbukkit.util.LongHash; + +public class ChunkIOExecutor { + static final int BASE_THREADS = 1; + static final int PLAYERS_PER_THREAD = 50; + + private static final AsynchronousExecutor instance = new AsynchronousExecutor(new ChunkIOProvider(), BASE_THREADS); + + public static void waitForChunkLoad(World world, int x, int z) { + instance.get(new QueuedChunk(LongHash.toLong(x, z), null, world, null)); + } + + 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); + } + + public static void adjustPoolSize(int players) { + int size = Math.max(BASE_THREADS, (int) Math.ceil(players / PLAYERS_PER_THREAD)); + instance.setActiveThreads(size); + } + + public static void tick() { + instance.finishActive(); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java new file mode 100644 index 0000000000..48cf5bac09 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java @@ -0,0 +1,73 @@ +package org.bukkit.craftbukkit.chunkio; + +import net.minecraft.server.Chunk; +import net.minecraft.server.ChunkRegionLoader; +import net.minecraft.server.NBTTagCompound; + +import org.bukkit.Server; +import org.bukkit.craftbukkit.util.AsynchronousExecutor; +import org.bukkit.craftbukkit.util.LongHash; + +import java.util.concurrent.atomic.AtomicInteger; + +class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider { + private final AtomicInteger threadNumber = new AtomicInteger(1); + + // async stuff + public Chunk callStage1(QueuedChunk queuedChunk) throws RuntimeException { + ChunkRegionLoader loader = queuedChunk.loader; + Object[] data = loader.loadChunk(queuedChunk.world, LongHash.msw(queuedChunk.coords), LongHash.lsw(queuedChunk.coords)); + + if (data != null) { + queuedChunk.compound = (NBTTagCompound) data[1]; + return (Chunk) data[0]; + } + + return null; + } + + // sync stuff + public void callStage2(QueuedChunk queuedChunk, Chunk chunk) throws RuntimeException { + if(chunk == null) { + // If the chunk loading failed just do it synchronously (may generate) + queuedChunk.provider.getChunkAt(LongHash.msw(queuedChunk.coords), LongHash.lsw(queuedChunk.coords)); + 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; + } + + queuedChunk.loader.loadEntities(chunk, queuedChunk.compound.getCompound("Level"), queuedChunk.world); + chunk.n = queuedChunk.provider.world.getTime(); + queuedChunk.provider.chunks.put(queuedChunk.coords, chunk); + chunk.addEntities(); + + if (queuedChunk.provider.chunkProvider != null) { + queuedChunk.provider.chunkProvider.recreateStructures(x, z); + } + + Server server = queuedChunk.provider.world.getServer(); + if (server != null) { + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, false)); + } + + chunk.a(queuedChunk.provider, queuedChunk.provider, x, z); + } + + public void callStage3(QueuedChunk queuedChunk, Chunk chunk, Runnable runnable) throws RuntimeException { + runnable.run(); + } + + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, "Chunk I/O Executor Thread-" + threadNumber.getAndIncrement()); + thread.setDaemon(true); + return thread; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunk.java b/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunk.java new file mode 100644 index 0000000000..299b1d8acd --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunk.java @@ -0,0 +1,36 @@ +package org.bukkit.craftbukkit.chunkio; + +import net.minecraft.server.ChunkProviderServer; +import net.minecraft.server.ChunkRegionLoader; +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.World; + +class QueuedChunk { + long coords; + ChunkRegionLoader loader; + World world; + ChunkProviderServer provider; + NBTTagCompound compound; + + public QueuedChunk(long coords, ChunkRegionLoader loader, World world, ChunkProviderServer provider) { + this.coords = coords; + this.loader = loader; + this.world = world; + this.provider = provider; + } + + @Override + public int hashCode() { + return (int) coords ^ world.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof QueuedChunk) { + QueuedChunk other = (QueuedChunk) object; + return coords == other.coords && world == other.world; + } + + return false; + } +}