Fix asynchronous chunk loading (#2946)

Since the update to Minecraft 1.9.4 chunks were actually never loaded
asynchronously because a sync request was always made from the
PlayerChunkMap shortly after the chunk had been queued.

- PlayerChunkMapEntry now only loads chunks synchronously *after* the
  chunk failed to load asynchronously.
- Fixed some minor bugs that caused "Attempted to dequeue chunk" messages
- Simplified ChunkProviderServer patch. loadChunk no longer generates chunks,
  so there is no need to handle that.
- Moved loader and provider to ChunkIOProvider so there is no need for
  "hashCode abuse"
This commit is contained in:
Minecrell authored and LexManos committed 2016-06-04 02:51:27 -07:00
1 parent 14ee316d06
commit 279380b4f1
6 files changed
+93 -108

No files matched your search

@@ -2,7 +2,6 @@ package net.minecraftforge.common.chunkio;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -19,8 +18,8 @@ import net.minecraftforge.fml.common.FMLLog;
public class ChunkIOExecutor
{
static final int BASE_THREADS = 1;
static final int PLAYERS_PER_THREAD = 50;
private static final int BASE_THREADS = 1;
private static final int PLAYERS_PER_THREAD = 50;
private static final Map<QueuedChunk, ChunkIOProvider> tasks = Maps.newConcurrentMap();
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(BASE_THREADS, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
@@ -41,8 +40,8 @@ public class ChunkIOExecutor
//Load the chunk completely in this thread. Dequeue as needed...
public static Chunk syncChunkLoad(World world, AnvilChunkLoader loader, ChunkProviderServer provider, int x, int z)
{
QueuedChunk key = new QueuedChunk(x, z, loader, world, provider);
ChunkIOProvider task = tasks.get(key);
QueuedChunk key = new QueuedChunk(x, z, world);
ChunkIOProvider task = tasks.remove(key); // Remove task because we will call the sync callbacks directly
if (task != null)
{
if (!pool.remove(task)) // If it wasn't in the pool, and run hasn't finished, then wait for the async thread.
@@ -62,10 +61,15 @@ public class ChunkIOExecutor
}
}
}
else
{
// If the task was not run yet we still need to load the chunk
task.run();
}
}
else
{
task = new ChunkIOProvider(key);
task = new ChunkIOProvider(key, loader, provider);
task.run();
}
task.syncCallback();
@@ -75,11 +79,11 @@ public class ChunkIOExecutor
//Queue the chunk to be loaded, and call the runnable when finished
public static void queueChunkLoad(World world, AnvilChunkLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable)
{
QueuedChunk key = new QueuedChunk(x, z, loader, world, provider);
QueuedChunk key = new QueuedChunk(x, z, world);
ChunkIOProvider task = tasks.get(key);
if (task == null)
{
task = new ChunkIOProvider(key);
task = new ChunkIOProvider(key, loader, provider);
task.addCallback(runnable); // Add before calling execute for thread safety
tasks.put(key, task);
pool.execute(task);
@@ -90,11 +94,10 @@ public class ChunkIOExecutor
}
}
// Abuses the fact that hashCode and equals for QueuedChunk only use world and coords
// Remove the chunk from the queue if it's in the list.
public static void dropQueuedChunkLoad(World world, int x, int z, Runnable runnable)
{
QueuedChunk key = new QueuedChunk(x, z, null, world, null);
QueuedChunk key = new QueuedChunk(x, z, world);
ChunkIOProvider task = tasks.get(key);
if (task == null)
{
@@ -113,8 +116,7 @@ public class ChunkIOExecutor
public static void adjustPoolSize(int players)
{
int size = Math.max(BASE_THREADS, (int) Math.ceil(players / PLAYERS_PER_THREAD));
pool.setCorePoolSize(size);
pool.setCorePoolSize(Math.max(BASE_THREADS, players / PLAYERS_PER_THREAD));
}
public static void tick()
@@ -123,11 +125,13 @@ public class ChunkIOExecutor
while (itr.hasNext())
{
ChunkIOProvider task = itr.next();
if (task.runFinished() && task.hasCallback())
if (task.runFinished())
{
task.syncCallback();
if (task.hasCallback())
task.syncCallback();
itr.remove();
}
itr.remove();
}
}
}
}
@@ -13,15 +13,20 @@ import java.util.concurrent.ConcurrentLinkedQueue;
class ChunkIOProvider implements Runnable
{
private QueuedChunk chunkInfo;
private final QueuedChunk chunkInfo;
private final AnvilChunkLoader loader;
private final ChunkProviderServer provider;
private Chunk chunk;
private NBTTagCompound nbt;
private ConcurrentLinkedQueue<Runnable> callbacks = new ConcurrentLinkedQueue<Runnable>();
private final ConcurrentLinkedQueue<Runnable> callbacks = new ConcurrentLinkedQueue<Runnable>();
private boolean ran = false;
ChunkIOProvider(QueuedChunk chunk)
ChunkIOProvider(QueuedChunk chunk, AnvilChunkLoader loader, ChunkProviderServer provider)
{
this.chunkInfo = chunk;
this.loader = loader;
this.provider = provider;
}
public void addCallback(Runnable callback)
@@ -38,11 +43,10 @@ class ChunkIOProvider implements Runnable
{
synchronized(this)
{
AnvilChunkLoader loader = chunkInfo.loader;
Object[] data = null;
try
{
data = loader.loadChunk__Async(chunkInfo.world, chunkInfo.x, chunkInfo.z);
data = this.loader.loadChunk__Async(chunkInfo.world, chunkInfo.x, chunkInfo.z);
}
catch (IOException e)
{
@@ -63,30 +67,24 @@ class ChunkIOProvider implements Runnable
// sync stuff
public void syncCallback()
{
ChunkProviderServer provider = this.chunkInfo.provider;
if (chunk == null)
{
// If the chunk loading failed just do it synchronously (may generate)
this.chunk = provider.originalLoadChunk(this.chunkInfo.x, this.chunkInfo.z);
this.runCallbacks();
return;
}
// Load Entities
this.chunkInfo.loader.loadEntities(this.chunkInfo.world, this.nbt.getCompoundTag("Level"), this.chunk);
this.loader.loadEntities(this.chunkInfo.world, this.nbt.getCompoundTag("Level"), this.chunk);
MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Load(this.chunk, this.nbt)); // Don't call ChunkDataEvent.Load async
this.chunk.setLastSaveTime(provider.worldObj.getTotalWorldTime());
this.provider.chunkGenerator.recreateStructures(this.chunk, this.chunkInfo.x, this.chunkInfo.z);
provider.id2ChunkMap.put(ChunkPos.chunkXZ2Int(this.chunkInfo.x, this.chunkInfo.z), this.chunk);
this.chunk.onChunkLoad();
if (provider.chunkGenerator != null)
{
provider.chunkGenerator.recreateStructures(this.chunk, this.chunkInfo.x, this.chunkInfo.z);
}
this.chunk.populateChunk(provider, provider.chunkGenerator);
this.runCallbacks();
}
@@ -114,4 +112,4 @@ class ChunkIOProvider implements Runnable
this.callbacks.clear();
}
}
}
@@ -1,19 +1,16 @@
package net.minecraftforge.common.chunkio;
import net.minecraft.world.World;
class QueuedChunk {
final int x;
final int z;
final net.minecraft.world.chunk.storage.AnvilChunkLoader loader;
final net.minecraft.world.World world;
final net.minecraft.world.gen.ChunkProviderServer provider;
final World world;
public QueuedChunk(int x, int z, net.minecraft.world.chunk.storage.AnvilChunkLoader loader, net.minecraft.world.World world, net.minecraft.world.gen.ChunkProviderServer provider) {
public QueuedChunk(int x, int z, World world) {
this.x = x;
this.z = z;
this.loader = loader;
this.world = world;
this.provider = provider;
}
@Override
@@ -40,7 +37,6 @@ class QueuedChunk {
result.append(this.getClass().getName() + " {" + NEW_LINE);
result.append(" x: " + x + NEW_LINE);
result.append(" z: " + z + NEW_LINE);
result.append(" loader: " + loader + NEW_LINE );
result.append(" world: " + world.getWorldInfo().getWorldName() + NEW_LINE);
result.append(" dimension: " + world.provider.getDimension() + NEW_LINE);
result.append(" provider: " + world.provider.getClass().getName() + NEW_LINE);