package net.minecraftforge.common.chunkio;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; // Sponge
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import com.google.common.collect.Maps;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
import net.minecraft.world.gen.ChunkProviderServer;
import org.spongepowered.common.SpongeImpl; // Sponge
//import net.minecraftforge.fml.common.FMLLog; // Sponge
public class ChunkIOExecutor
{
private static final int BASE_THREADS = 1;
private static final int PLAYERS_PER_THREAD = 50;
private static final AtomicInteger threadCounter = new AtomicInteger(); // Sponge: Add static thread counter
private static final Map<QueuedChunk, ChunkIOProvider> tasks = new ConcurrentHashMap<>(); // Sponge: Construct ConcurrentHashMap directly
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(BASE_THREADS, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
/*new ThreadFactory()
{
private AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r, "Chunk I/O Executor Thread-" + count.getAndIncrement());
thread.setDaemon(true);
return thread;
}
}*/
// Sponge start: Use lambda
r -> {
Thread thread = new Thread(r, "Chunk I/O Thread #" + threadCounter.incrementAndGet());
thread.setDaemon(true);
return thread;
}
// Sponge end
);
//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, 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.
{
synchronized(task)
{
while (!task.runFinished())
{
try
{
task.wait();
}
catch (InterruptedException e)
{
// Sponge start: Rethrow interruption
//e.printStackTrace(); // Something happened? Log it?
Thread.currentThread().interrupt();
throw new RuntimeException("Failed to wait for chunk load", e);
// Sponge end
}
}
}
}
else
{
// If the task was not run yet we still need to load the chunk
task.run();
}
}
else
{
task = new ChunkIOProvider(key, loader, provider);
task.run();
}
task.syncCallback();
return task.getChunk();
}
//Queue the chunk to be loaded, and call the runnable when finished
// Sponge: Runnable -> Consumer<Chunk>
public static void queueChunkLoad(World world, AnvilChunkLoader loader, ChunkProviderServer provider, int x, int z, Consumer<Chunk> runnable)
{
QueuedChunk key = new QueuedChunk(x, z, world);
ChunkIOProvider task = tasks.get(key);
if (task == null)
{
task = new ChunkIOProvider(key, loader, provider);
task.addCallback(runnable); // Add before calling execute for thread safety
tasks.put(key, task);
pool.execute(task);
}
else
{
task.addCallback(runnable);
}
}
// Remove the chunk from the queue if it's in the list.
public static void dropQueuedChunkLoad(World world, int x, int z, Consumer<Chunk> runnable) // Sponge: Runnable -> Consumer<Chunk>
{
QueuedChunk key = new QueuedChunk(x, z, world);
ChunkIOProvider task = tasks.get(key);
if (task == null)
{
// Sponge: Use Sponge logging
//FMLLog.warning("Attempted to dequeue chunk that wasn't queued? %d @ (%d, %d)", world.provider.getDimension(), x, z);
SpongeImpl.getLogger().warn("Attempting to drop chunk that wasn't queued in {} @ ({}, {})", world, x, z);
return;
}
task.removeCallback(runnable);
if (!task.hasCallback())
{
tasks.remove(key);
pool.remove(task);
}
}
public static void adjustPoolSize(int players)
{
pool.setCorePoolSize(Math.max(BASE_THREADS, players / PLAYERS_PER_THREAD));
}
public static void tick()
{
Iterator<ChunkIOProvider> itr = tasks.values().iterator();
while (itr.hasNext())
{
ChunkIOProvider task = itr.next();
if (task.runFinished())
{
if (task.hasCallback())
task.syncCallback();
itr.remove();
}
}
}
}