Skip to content

Instantly share code, notes, and snippets.

@aikar
Created September 2, 2018 02:11
Show Gist options
  • Save aikar/7306b9b87b87bda3aafb814cae6c742c to your computer and use it in GitHub Desktop.
Save aikar/7306b9b87b87bda3aafb814cae6c742c to your computer and use it in GitHub Desktop.
diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
index 48cdc97377..d8b26d6129 100644
--- a/src/main/java/net/minecraft/server/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/ChunkMap.java
@@ -50,7 +50,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
}
chunk.world.timings.syncChunkLoadPostTimer.stopTiming(); // Paper
- ((ChunkProviderServer) chunk.world.getChunkProvider()).asyncTaskHandler.postToMainThread(() -> {// Paper
+ //((ChunkProviderServer) chunk.world.getChunkProvider()).asyncTaskHandler.postToMainThread(() -> {// Paper
if (chunk.newChunk) {
chunk.world.timings.syncChunkLoadPopulateTimer.startTiming(); // Paper
BlockSand.instaFall = true;
@@ -74,7 +74,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
BlockSand.instaFall = false;
chunk.world.getServer().getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(chunk.bukkitChunk));
chunk.world.timings.syncChunkLoadPopulateTimer.stopTiming(); // Paper
- } }); // Paper
+ }// }); // Paper
// CraftBukkit end
return chunk1;
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 27a5b61eba..e595905ad1 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -43,7 +43,7 @@ public class ChunkProviderServer implements IChunkProvider {
private final ChunkTaskScheduler chunkScheduler;
private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler;
public final WorldServer world;
- final IAsyncTaskHandler asyncTaskHandler; // Paper
+ private final IAsyncTaskHandler asyncTaskHandler;
final PaperAsyncChunkProvider asyncChunkProvider; // Paper
public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator<?> chunkgenerator, IAsyncTaskHandler iasynctaskhandler) {
@@ -53,7 +53,7 @@ public class ChunkProviderServer implements IChunkProvider {
this.asyncTaskHandler = iasynctaskhandler;
this.chunkScheduler = new ChunkTaskScheduler(0, worldserver, chunkgenerator, ichunkloader, iasynctaskhandler); // CraftBukkit - very buggy, broken in lots of __subtle__ ways. Same goes for async chunk loading. Also Bukkit API / plugins can't handle async events at all anyway.
this.batchScheduler = new SchedulerBatch(this.chunkScheduler);
- this.asyncChunkProvider = new PaperAsyncChunkProvider(world, this, asyncTaskHandler, chunkLoader, this.batchScheduler); // Paper - Async Chunks
+ this.asyncChunkProvider = new PaperAsyncChunkProvider(this, asyncTaskHandler, chunkLoader, this.batchScheduler); // Paper - Async Chunks
}
public Collection<Chunk> a() {
diff --git a/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
index 285d633c3b..5076b23393 100644
--- a/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
+++ b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
@@ -34,7 +34,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@SuppressWarnings("unused")
@@ -47,52 +46,86 @@ class PaperAsyncChunkProvider {
);
private final ExecutorService chunkGenerationExecutor ;
private final Long2ObjectMap<PendingChunk> pendingChunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
- private final IAsyncTaskHandler asyncTaskHandler;
- private final ChunkProviderServer server;
+ private final IAsyncTaskHandler asyncHandler;
+ private final ChunkProviderServer provider;
private final WorldServer world;
private final IChunkLoader chunkLoader;
- private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> schedulerBatch;
-
- PaperAsyncChunkProvider(WorldServer world, ChunkProviderServer server, IAsyncTaskHandler asynctaskhandler, IChunkLoader chunkloader, SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> schedulerBatch) {
- this.server = server;
- this.world = world;
- this.asyncTaskHandler = asynctaskhandler;
- this.chunkLoader = chunkloader;
- this.schedulerBatch = schedulerBatch;
- this.chunkGenerationExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "Paper Generation Executor - " + world.getWorld().getName()));
+ private final MinecraftServer server;
+ private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> scheduler;
+
+ PaperAsyncChunkProvider(ChunkProviderServer provider, IAsyncTaskHandler asyncTaskHandler, IChunkLoader chunkLoader, SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> scheduler) {
+ this.provider = provider;
+ this.server = provider.world.getMinecraftServer();
+ this.world = provider.world;
+ this.asyncHandler = asyncTaskHandler;
+ this.chunkLoader = chunkLoader;
+ this.scheduler = scheduler;
+ String worldName = this.world.getWorld().getName();
+ this.chunkGenerationExecutor = Executors.newSingleThreadExecutor(r -> {
+ return new Thread(r, "Paper Generation Executor - " + worldName);
+ });
}
Chunk loadOrGenerateChunk(int x, int z, boolean gen, Consumer<Chunk> consumer) {
- long key = ChunkCoordIntPair.asLong(x, z);
- AtomicBoolean hadPending = new AtomicBoolean(false);
-
+ final long key = ChunkCoordIntPair.asLong(x, z);
final CompletableFuture<Chunk> future = new CompletableFuture<>();
- final PendingChunk pendingChunk = pendingChunks.compute(key, (prevKey, pending) -> {
- if (pending == null) {
+
+ boolean hadPending;
+ final PendingChunk pending;
+ final boolean isFirstMainThreadRequest;
+ synchronized (pendingChunks) {
+ PendingChunk pendingChunk = pendingChunks.get(key);
+ // DO NOT CALL ANY METHODS ON PENDING CHUNK IN THIS BLOCK - WILL DEADLOCK
+ if (pendingChunk == null) {
pending = new PendingChunk(x, z, key, gen);
+ pendingChunks.put(key, pending);
+ hadPending = false;
+ } else if (pendingChunk.hasFinished && gen && !pendingChunk.canGenerate && pendingChunk.chunk == null) {
+ // need to overwrite the old
+ pending = new PendingChunk(x, z, key, gen);
+ pendingChunks.put(key, pending);
+ hadPending = false;
} else {
- hadPending.set(true);
+ pending = pendingChunk;
+ hadPending = true;
}
-
+ }
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (pending) {
if (!gen && pending.generating) {
future.complete(null);
} else {
pending.addListener(future, gen);
}
- return pending;
- });
-
+ // This makes sure this is the first future to be resolved to set the chunk map/entities
+ if (consumer == null && pending.mainThreadRequest == null && server.isMainThread()) {
+ isFirstMainThreadRequest = true;
+ pending.mainThreadRequest = future;
+ } else {
+ isFirstMainThreadRequest = false;
+ }
+ }
- if (!hadPending.get()) {
- EXECUTOR.submit(pendingChunk);
+ // A new main thread request may of come in after completion waiting for the next tick loop
+ // We need to go ahead and complete it now!
+ if (isFirstMainThreadRequest && pending.hasFinished) {
+ return pending.postChunk(pending.chunk);
+ } else if (!hadPending) {
+ EXECUTOR.submit(pending);
}
if (consumer == null) {
+ Chunk chunk;
try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) {
- return future.join();
+ chunk = future.join();
+ }
+
+ if (isFirstMainThreadRequest) {
+ return pending.postChunk(chunk);
}
+ return chunk;
} else {
- future.thenAccept((c) -> this.asyncTaskHandler.postToMainThread(() -> consumer.accept(c)));
+ future.thenAccept((c) -> this.asyncHandler.postToMainThread(() -> consumer.accept(c)));
}
return null;
}
@@ -110,9 +143,9 @@ class PaperAsyncChunkProvider {
try (co.aikar.timings.Timing timing = world.timings.chunkGeneration.startTiming()) {
CompletableFuture<ProtoChunk> pending = new CompletableFuture<>();
chunkGenerationExecutor.submit(() -> {
- this.schedulerBatch.startBatch();
- this.schedulerBatch.add(new ChunkCoordIntPair(x, z));
- this.schedulerBatch.executeBatch()
+ this.scheduler.startBatch();
+ this.scheduler.add(new ChunkCoordIntPair(x, z));
+ this.scheduler.executeBatch()
.handle((protoChunk, throwable) -> {
if (throwable != null) {
pending.completeExceptionally(throwable);
@@ -124,7 +157,7 @@ class PaperAsyncChunkProvider {
});
return new Chunk(this.world, pending.join(), x, z);
} catch (RuntimeException runtimeexception) {
- throw this.server.generateChunkError(x, z, runtimeexception);
+ throw this.provider.generateChunkError(x, z, runtimeexception);
}
}
@@ -136,15 +169,11 @@ class PaperAsyncChunkProvider {
}
}
- private void addEntities(Chunk chunk) {
- asyncTaskHandler.postToMainThread(chunk::addEntities);
- }
-
private Chunk checkAddChunk(long key, Chunk chunk) {
- synchronized (server.chunks) {
- final Chunk other = server.chunks.get(key);
+ synchronized (provider.chunks) {
+ final Chunk other = provider.chunks.get(key);
if (other == null && chunk != null) {
- server.chunks.put(key, chunk);
+ provider.chunks.put(key, chunk);
}
return other;
}
@@ -157,8 +186,12 @@ class PaperAsyncChunkProvider {
private final int z;
private final long key;
+ volatile CompletableFuture<Chunk> mainThreadRequest;
volatile boolean generating;
volatile boolean canGenerate;
+ volatile boolean hasPosted = false;
+ volatile boolean hasFinished;
+ volatile Chunk chunk;
final CompletableFuture<Chunk> loadOnly = new CompletableFuture<>();
final CompletableFuture<Chunk> generate = new CompletableFuture<>();
@@ -170,57 +203,98 @@ class PaperAsyncChunkProvider {
this.canGenerate = canGenerate;
}
- boolean loadFinished(Chunk chunk) {
- Chunk other = checkAddChunk(key, chunk);
- boolean shouldGenerate;
+ synchronized boolean loadFinished(Chunk chunk) {
+ if (chunk != null) {
+ postChunkToMain(chunk);
+ return false;
+ }
synchronized (pendingChunks) {
- shouldGenerate = chunk == null && other == null && canGenerate;
- if (!shouldGenerate) {
+ if (!canGenerate) {
pendingChunks.remove(key);
} else {
generating = true;
}
}
- if (other != null) {
- loadOnly.complete(other);
- generate.complete(other);
- return false;
- } else if (chunk != null) {
- chunk.setLastSaved(chunk.world.getTime());
- loadOnly.complete(chunk);
- generate.complete(chunk);
- addEntities(chunk);
+
+ // this is nullable for anyone who doesn't need generation
+ if (!canGenerate) {
+ if (mainThreadRequest != null) {
+ mainThreadRequest.complete(null);
+ }
+ generate.complete(null);
+ this.chunk = null;
+ this.hasFinished = true;
+ }
+ loadOnly.complete(null);
+
+ return canGenerate;
+ }
+
+ synchronized void generateFinished(Chunk chunk) {
+ this.chunk = chunk;
+ this.hasFinished = true;
+ if (chunk != null) {
+ postChunkToMain(chunk);
} else {
- // this is nullable for anyone who doesn't need generation
- loadOnly.complete(null);
- if (!shouldGenerate) {
- generate.complete(null);
+ synchronized (pendingChunks) {
+ pendingChunks.remove(key);
+ }
+ if (mainThreadRequest != null) {
+ mainThreadRequest.complete(null);
}
+ // failure
+ generate.complete(null);
+
}
+ }
- return shouldGenerate;
+ synchronized private void completeFutures(Chunk other) {
+ loadOnly.complete(other);
+ generate.complete(other);
}
- void generateFinished(Chunk chunk) {
- Chunk other = checkAddChunk(key, chunk);
- synchronized (pendingChunks) {
- pendingChunks.remove(key);
+ synchronized private void postChunkToMain(Chunk chunk) {
+ // ensure anything that comes in post this
+ this.chunk = chunk;
+ this.hasFinished = true;
+ if (mainThreadRequest == null) {
+ asyncHandler.postToMainThread(() -> postChunk(chunk));
+ } else {
+ // something else will handle this
+ mainThreadRequest.complete(chunk);
+ }
+ }
+
+ synchronized Chunk postChunk(Chunk chunk) {
+ if (!server.isMainThread()) {
+ throw new IllegalStateException("Must post from main");
+ }
+ if (hasPosted) {
+ return chunk;
}
+ hasPosted = true;
+ pendingChunks.remove(key);
+ Chunk other = checkAddChunk(key, chunk);
if (other != null) {
- chunk = other;
- } else if (chunk != null) {
- addEntities(chunk);
+ return other;
}
- generate.complete(chunk);
+
+ if (chunk != null) {
+ chunk.setLastSaved(chunk.world.getTime());
+ chunk.addEntities();
+ }
+ completeFutures(chunk);
+ return chunk;
}
- void addListener(CompletableFuture<Chunk> future, boolean gen) {
+ synchronized void addListener(CompletableFuture<Chunk> future, boolean gen) {
if (gen) {
canGenerate = true;
generate.thenAccept(future::complete);
} else {
loadOnly.thenAccept(future::complete);
}
+
}
@Override
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment