Fix issue causing island scans never finish.

There was an issue in 1.8 specifically(where PaperLib doesn't has async chunks) that caused a similar error to this https://pastebin.com/tHtKdH5y.

The issue was caused by the async task trying to take an snapshot of a chunk that couldn't be loaded async correctly, which caused issues like the shown above.

The fix works in the next way. All the chunks that don't have an available snapshot are sent into a pendingChunks list where a sync task will try to process a certain amount of chunks every tick. After the async processing is finished, the task remains waiting for the pending chunks to be available. When all the pending chunks are ready to be processed(have an available snapshot) the async task resumes and process the chunks that were pending.
This commit is contained in:
FixedDev 2020-12-05 20:08:31 -06:00 committed by Brianna
parent 41e87c55b9
commit 489bfd14bc

View File

@ -7,6 +7,7 @@ import com.songoda.skyblock.SkyBlock;
import com.songoda.skyblock.island.Island;
import com.songoda.skyblock.island.IslandEnvironment;
import com.songoda.skyblock.world.WorldManager;
import io.papermc.lib.PaperLib;
import org.bukkit.Bukkit;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
@ -15,26 +16,33 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public final class BlockScanner extends BukkitRunnable {
private static final Method ID_FIELD;
private static final int MAX_CHUNKS_PER_ITERATION = 2;
private static final int MAX_EMPTY_ITERATIONS = 20;
static {
Method temp = null;
try {
temp = ChunkSnapshot.class.getMethod("getBlockTypeId", int.class, int.class, int.class);
} catch (NoSuchMethodException ignored) {}
} catch (NoSuchMethodException ignored) {
}
ID_FIELD = temp;
}
@ -122,7 +130,22 @@ public final class BlockScanner extends BukkitRunnable {
private void queueWork(World world, int scanY, List<CachedChunk> subList) {
WorldManager worldManager = SkyBlock.getInstance().getWorldManager();
// The chunks that couldn't be taken snapshot async
List<CachedChunk> pendingChunks = new ArrayList<>();
// The chunks that are ready to be processed asynchronously
List<CachedChunk> readyChunks = new ArrayList<>();
// This lock will help to make the bukkit task wait after all the chunks that could be processed async are processed
Lock lock = new ReentrantLock();
// This is the actual object that we will use to wait
Condition emptyCondition = lock.newCondition();
Bukkit.getServer().getScheduler().runTaskAsynchronously(SkyBlock.getInstance(), () -> {
// We need to hold the lock on the thread calling the await
lock.lock();
LocationBounds bounds = null;
if (island != null) {
Location islandLocation = island.getLocation(worldManager.getIslandWorld(world), IslandEnvironment.Island);
@ -138,7 +161,98 @@ public final class BlockScanner extends BukkitRunnable {
bounds = new LocationBounds(minX, minZ, maxX, maxZ);
}
for (CachedChunk shot : subList) {
if (!shot.isSnapshotAvailable() && !areAsyncChunksAvailable()) {
pendingChunks.add(shot);
continue;
}
processCachedChunk(world, scanY, shot, bounds);
}
// Don't wait for the condition if the async chunks are available, since it would never be signalled
if (areAsyncChunksAvailable()) {
increment();
lock.unlock();
return;
}
try {
emptyCondition.await();
} catch (InterruptedException e) {
// Pass the interruption
Thread.currentThread().interrupt();
}
// process the pending chunks
for (CachedChunk shot : readyChunks) {
processCachedChunk(world, scanY, shot, bounds);
}
lock.unlock();
increment();
});
if (!areAsyncChunksAvailable()) {
startChunkSnapshotTask(pendingChunks, readyChunks, emptyCondition, lock);
}
}
private boolean areAsyncChunksAvailable() {
return PaperLib.isVersion(9) && PaperLib.isPaper();
}
private void startChunkSnapshotTask(List<CachedChunk> pendingChunks, List<CachedChunk> readyChunks, Condition emptyCondition, Lock lock) {
new BukkitRunnable() {
// The number of iterations with the pendingChunks list empty
private int emptyIterations = 0;
@Override
public void run() {
lock.lock();
int updatedChunks = 0;
Iterator<CachedChunk> chunkIterator = pendingChunks.iterator();
try {
while (chunkIterator.hasNext()) {
CachedChunk pendingChunk = chunkIterator.next();
if (updatedChunks >= MAX_CHUNKS_PER_ITERATION) {
break;
}
// take the snapshot
pendingChunk.takeSnapshot();
chunkIterator.remove();
readyChunks.add(pendingChunk);
updatedChunks++;
}
if (pendingChunks.isEmpty()) {
if (emptyIterations >= MAX_EMPTY_ITERATIONS) {
// Send the signal to unlock the async thread and continue with the processing
emptyCondition.signalAll();
this.cancel();
return;
}
emptyIterations++;
}
} finally {
lock.unlock();
}
}
}.runTaskTimer(SkyBlock.getInstance(), 1, 1);
}
private void processCachedChunk(World world, int scanY, CachedChunk shot, LocationBounds bounds) {
final int cX = shot.getX() << 4;
final int cZ = shot.getZ() << 4;
@ -176,9 +290,6 @@ public final class BlockScanner extends BukkitRunnable {
}
}
}
increment();
});
}
private synchronized int increment() {
return completedNum.getAndIncrement();