Created
September 2, 2018 02:11
-
-
Save aikar/7306b9b87b87bda3aafb814cae6c742c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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