Created
August 31, 2018 03:19
-
-
Save aikar/66d632096d7cd1fea7a74d13e96076c0 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
commit b2a65ead8b8c7aa5728568a5944131e7bfd135bd | |
Author: Aikar <[email protected]> | |
Date: Thu Aug 30 22:38:31 2018 -0400 | |
Async Chunk Loading and Generation | |
diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java | |
index b941676829..76a449b1fe 100644 | |
--- a/src/main/java/net/minecraft/server/ChunkMap.java | |
+++ b/src/main/java/net/minecraft/server/ChunkMap.java | |
@@ -17,6 +17,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> { | |
chunk.world.timings.syncChunkLoadPostTimer.startTiming(); // Paper | |
lastChunkByPos = chunk; // Paper | |
Chunk chunk1 = (Chunk) super.put(i, chunk); | |
+ | |
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i); | |
for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) { | |
@@ -49,7 +50,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> { | |
} | |
} | |
chunk.world.timings.syncChunkLoadPostTimer.stopTiming(); // Paper | |
- | |
+ ((ChunkProviderServer) chunk.world.getChunkProvider()).asyncTaskHandler.postToMainThread(() -> {// Paper | |
if (chunk.newChunk) { | |
chunk.world.timings.syncChunkLoadPopulateTimer.startTiming(); // Paper | |
BlockSand.instaFall = true; | |
@@ -73,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 | |
// 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 c0ec896eea..93b61c1274 100644 | |
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java | |
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java | |
@@ -43,15 +43,17 @@ public class ChunkProviderServer implements IChunkProvider { | |
private final ChunkTaskScheduler chunkScheduler; | |
private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler; | |
public final WorldServer world; | |
- private final IAsyncTaskHandler asyncTaskHandler; | |
+ final IAsyncTaskHandler asyncTaskHandler; // Paper | |
+ private final PaperAsyncChunkLoader asyncChunkLoader; // Paper | |
public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator<?> chunkgenerator, IAsyncTaskHandler iasynctaskhandler) { | |
this.world = worldserver; | |
this.chunkLoader = ichunkloader; | |
this.chunkGenerator = chunkgenerator; | |
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.chunkScheduler = new ChunkTaskScheduler(4, worldserver, chunkgenerator, ichunkloader, iasynctaskhandler); // Paper - bump to 4 | |
this.batchScheduler = new SchedulerBatch(this.chunkScheduler); | |
+ this.asyncChunkLoader = new PaperAsyncChunkLoader(world, this, asyncTaskHandler, chunkLoader, batchScheduler); // Paper - Async Chunks | |
} | |
public Collection<Chunk> a() { | |
@@ -81,7 +83,24 @@ public class ChunkProviderServer implements IChunkProvider { | |
} | |
@Nullable | |
- public Chunk getChunkAt(int i, int j, boolean flag, boolean flag1) { | |
+ public final Chunk getChunkAt(int i, int j, boolean flag, boolean flag1) { | |
+ // Paper start - Async Chunks | |
+ return getChunkAt(i, j, flag, flag1, null); | |
+ } | |
+ | |
+ public final Chunk getChunkAt(int x, int z, boolean load, boolean gen, Consumer<Chunk> consumer) { | |
+ long key = ChunkCoordIntPair.a(x, z); | |
+ Chunk chunk = this.chunks.get(key); | |
+ System.out.println("GET" + x + "," + z + " - " + load + ":" + gen); | |
+ if (chunk != null || !load) { // return null if we aren't loading | |
+ System.out.println("RETURN" + x + "," + z + " : " + (chunk != null ? "has" : "do not")); | |
+ return chunk; | |
+ } | |
+ return asyncChunkLoader.loadOrGenerateChunk(x, z, gen, consumer); | |
+ } | |
+ @Nullable | |
+ public Chunk getChunkAtOriginal(int i, int j, boolean flag, boolean flag1) { // Paper - rename to disable | |
+ // Paper end - Async Chunks | |
IChunkLoader ichunkloader = this.chunkLoader; | |
Chunk chunk; | |
@@ -134,6 +153,7 @@ public class ChunkProviderServer implements IChunkProvider { | |
// CraftBukkit start | |
public Chunk generateChunk(int x, int z) { | |
try { | |
+ synchronized (this.batchScheduler) { // Paper - synchronized | |
this.batchScheduler.b(); | |
ChunkCoordIntPair pos = new ChunkCoordIntPair(x, z); | |
this.chunkScheduler.forcePolluteCache(pos); | |
@@ -141,6 +161,7 @@ public class ChunkProviderServer implements IChunkProvider { | |
CompletableFuture<ProtoChunk> completablefuture = this.batchScheduler.c(); | |
return (Chunk) completablefuture.thenApply(this::a).join(); | |
+ } // Paper - synchronized | |
} catch (RuntimeException runtimeexception) { | |
throw this.a(x, z, (Throwable) runtimeexception); | |
} | |
@@ -154,6 +175,7 @@ public class ChunkProviderServer implements IChunkProvider { | |
} | |
public CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { | |
+ synchronized (this.batchScheduler) { // Paper - synchronized | |
this.batchScheduler.b(); | |
Iterator iterator = iterable.iterator(); | |
@@ -169,8 +191,10 @@ public class ChunkProviderServer implements IChunkProvider { | |
} | |
return this.batchScheduler.c(); | |
+ } // Paper - synchronized | |
} | |
+ ReportedException generateChunkError(int i, int j, Throwable throwable) { return a(i, j, throwable); } // Paper - OBFHELPER | |
private ReportedException a(int i, int j, Throwable throwable) { | |
CrashReport crashreport = CrashReport.a(throwable, "Exception generating new chunk"); | |
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated"); | |
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java | |
index 06968974c5..0f216c39bd 100644 | |
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java | |
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java | |
@@ -110,7 +110,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { | |
// CraftBukkit start | |
private boolean check(ChunkProviderServer cps, int x, int z) throws IOException { | |
if (cps != null) { | |
- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); | |
+ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this is safe | |
if (cps.isLoaded(x, z)) { | |
return true; | |
} | |
diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java | |
index 4698ee99f8..33b30bb7e3 100644 | |
--- a/src/main/java/net/minecraft/server/IChunkLoader.java | |
+++ b/src/main/java/net/minecraft/server/IChunkLoader.java | |
@@ -6,6 +6,7 @@ import javax.annotation.Nullable; | |
public interface IChunkLoader { | |
+ default Chunk loadChunkAt(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException { return a(generatoraccess, i, j, consumer); } // Paper - OBFHELPER | |
@Nullable | |
Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException; | |
diff --git a/src/main/java/net/minecraft/server/PaperAsyncChunkLoader.java b/src/main/java/net/minecraft/server/PaperAsyncChunkLoader.java | |
new file mode 100644 | |
index 0000000000..adaae7bfed | |
--- /dev/null | |
+++ b/src/main/java/net/minecraft/server/PaperAsyncChunkLoader.java | |
@@ -0,0 +1,249 @@ | |
+/* | |
+ * This file is licensed under the MIT License (MIT). | |
+ * | |
+ * Copyright (c) 2018 Daniel Ennis <http://aikar.co> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
+ * THE SOFTWARE. | |
+ */ | |
+package net.minecraft.server; | |
+ | |
+import com.google.common.util.concurrent.MoreExecutors; | |
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap; | |
+import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; | |
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; | |
+import org.bukkit.Bukkit; | |
+ | |
+import java.io.IOException; | |
+import java.util.concurrent.CompletableFuture; | |
+import java.util.concurrent.ExecutorService; | |
+import java.util.concurrent.Executors; | |
+import java.util.concurrent.atomic.AtomicBoolean; | |
+import java.util.concurrent.atomic.AtomicInteger; | |
+import java.util.function.Consumer; | |
+ | |
+public class PaperAsyncChunkLoader { | |
+ | |
+ private static final ExecutorService chunkExecutor = Executors.newCachedThreadPool(new NamedIncrementingThreadFactory("Paper Chunk Executor ")); | |
+ private static final ExecutorService directChunkExecutor = MoreExecutors.newDirectExecutorService(); | |
+ | |
+ private final IAsyncTaskHandler asyncTaskHandler; | |
+ private final ChunkProviderServer server; | |
+ private final WorldServer world; | |
+ private final IChunkLoader chunkLoader; | |
+ private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler; | |
+ | |
+ private final Long2ObjectMap<PendingChunk> pendingChunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); | |
+ | |
+ | |
+ PaperAsyncChunkLoader(WorldServer world, ChunkProviderServer server, IAsyncTaskHandler asynctaskhandler, IChunkLoader chunkloader, SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler) { | |
+ this.server = server; | |
+ this.world = world; | |
+ this.asyncTaskHandler = asynctaskhandler; | |
+ this.chunkLoader = chunkloader; | |
+ this.batchScheduler = batchScheduler; | |
+ } | |
+ | |
+ Chunk loadOrGenerateChunk(int x, int z, boolean gen, Consumer<Chunk> consumer) { | |
+ long key = ChunkCoordIntPair.a(x, z); | |
+ AtomicBoolean hadPending = new AtomicBoolean(false); | |
+ | |
+ final CompletableFuture<Chunk> future = new CompletableFuture<>(); | |
+ final PendingChunk pendingChunk = pendingChunks.compute(key, (prevKey, pending) -> { | |
+ if (pending == null) { | |
+ pending = new PendingChunk(x, z, key, gen); | |
+ } else { | |
+ hadPending.set(true); | |
+ } | |
+ | |
+ if (!gen && pending.generating) { | |
+ future.complete(null); | |
+ } else { | |
+ pending.addListener(future, gen); | |
+ } | |
+ return pending; | |
+ }); | |
+ | |
+ | |
+ if (!hadPending.get()) { | |
+ final boolean useDirect = !Bukkit.isPrimaryThread() || consumer == null; | |
+ System.out.println("EXECUTE" + x + "," + z + " - " + (useDirect ? "1" : "0")); | |
+ if (useDirect) { | |
+ directChunkExecutor.submit(pendingChunk); | |
+ } else { | |
+ chunkExecutor.submit(pendingChunk); | |
+ } | |
+ } | |
+ | |
+ if (consumer == null) { | |
+ System.out.println("JOINWAIT " + x +"," + z); | |
+ try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) { | |
+ return future.join(); | |
+ } | |
+ } else { | |
+ System.out.println("CONSUMEASYNC " + x +"," + z); | |
+ consumeOnMain(future, consumer); | |
+ } | |
+ System.out.println("RETNULL " + x +"," + z); | |
+ return null; | |
+ } | |
+ | |
+ private Chunk loadChunk(int x, int z) { | |
+ try { | |
+ return this.chunkLoader.loadChunkAt(this.world, x, z, this::doNothingChunkConsumer); | |
+ } catch (IOException e) { | |
+ MinecraftServer.LOGGER.error("Couldn't load chunk", e); | |
+ return null; | |
+ } | |
+ } | |
+ private Chunk generateChunk0(int x, int z) { | |
+ try (co.aikar.timings.Timing timing = world.timings.chunkGeneration.startTiming()) { | |
+ CompletableFuture<ProtoChunk> pending; | |
+ synchronized (this.batchScheduler) { | |
+ this.batchScheduler.startBatch(); | |
+ this.batchScheduler.add(new ChunkCoordIntPair(x, z)); | |
+ pending = this.batchScheduler.executeBatch(); | |
+ } | |
+ | |
+ return new Chunk(this.world, pending.join(), x, z); | |
+ } catch (RuntimeException runtimeexception) { | |
+ throw this.server.generateChunkError(x, z, runtimeexception); | |
+ } | |
+ } | |
+ | |
+ private void consumeOnMain(CompletableFuture<Chunk> future, Consumer<Chunk> consumer) { | |
+ future.thenAccept((c) -> this.asyncTaskHandler.postToMainThread(() -> consumer.accept(c))); | |
+ } | |
+ | |
+ private void doNothingChunkConsumer(Chunk chunk) {} | |
+ | |
+ | |
+ static AtomicInteger idpool = new AtomicInteger(1); | |
+ private class PendingChunk implements Runnable { | |
+ private final int x; | |
+ private final int z; | |
+ private final long key; | |
+ | |
+ volatile boolean generating; | |
+ volatile boolean canGenerate; | |
+ | |
+ final CompletableFuture<Chunk> loadOnly = new CompletableFuture<>(); | |
+ final CompletableFuture<Chunk> generate = new CompletableFuture<>(); | |
+ private String debug; | |
+ | |
+ | |
+ | |
+ PendingChunk(int x, int z, long key, boolean canGenerate) { | |
+ this.x = x; | |
+ this.z = z; | |
+ this.key = key; | |
+ this.canGenerate = canGenerate; | |
+ debug = "(" + idpool.getAndIncrement() + ") " + x + "," + z; | |
+ } | |
+ boolean loadFinished(Chunk chunk) { | |
+ Chunk other = checkAddChunk(chunk); | |
+ boolean shouldGenerate; | |
+ synchronized (pendingChunks) { | |
+ shouldGenerate = chunk == null && other == null && canGenerate; | |
+ System.out.println("COMPLETINGLOAD" + " " + debug + " - " + (chunk != null ? "1" : "0")); | |
+ if (!shouldGenerate) { | |
+ pendingChunks.remove(key); | |
+ } else { | |
+ generating = true; | |
+ } | |
+ } | |
+ if (other != null) { | |
+ System.out.println("OTHER WAS LOADED POST LOAD " + debug); | |
+ loadOnly.complete(other); | |
+ generate.complete(other); | |
+ return false; | |
+ } else if (chunk != null) { | |
+ loadOnly.complete(chunk); | |
+ generate.complete(chunk); | |
+ addEntities(chunk); | |
+ } else { | |
+ // this is nullable for anyone who doesn't need generation | |
+ loadOnly.complete(null); | |
+ if (!shouldGenerate) { | |
+ generate.complete(null); | |
+ } | |
+ } | |
+ | |
+ return shouldGenerate; | |
+ } | |
+ | |
+ private void addEntities(Chunk chunk) { | |
+ System.out.println("ADDENTITIES " + debug); | |
+ PaperAsyncChunkLoader.this.asyncTaskHandler.postToMainThread(chunk::addEntities); | |
+ } | |
+ | |
+ private Chunk checkAddChunk(Chunk chunk) { | |
+ synchronized (server.chunks) { | |
+ final Chunk other = server.chunks.get(key); | |
+ if (other == null && chunk != null) { | |
+ server.chunks.put(key, chunk); | |
+ } | |
+ return other; | |
+ } | |
+ } | |
+ | |
+ void generateFinished(Chunk chunk) { | |
+ Chunk other = checkAddChunk(chunk); | |
+ synchronized (pendingChunks) { | |
+ System.out.println("GENERATE FINISHED " + " " + debug + " - " + (chunk != null ? "1" : "0")); | |
+ pendingChunks.remove(key); | |
+ } | |
+ if (other != null) { | |
+ System.out.println("OTHER WAS LOADED POST GEN " + debug); | |
+ chunk = other; | |
+ } else if (chunk != null) { | |
+ addEntities(chunk); | |
+ } | |
+ generate.complete(chunk); | |
+ } | |
+ void addListener(CompletableFuture<Chunk> future, boolean gen) { | |
+ if (gen) { | |
+ canGenerate = true; | |
+ generate.thenAccept(future::complete); | |
+ } else { | |
+ loadOnly.thenAccept(future::complete); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void run() { | |
+ System.out.println("LOAD" + debug); | |
+ Chunk chunk = loadChunk(x, z); | |
+ System.out.println("LOAD DONE " + debug + " - " + (chunk != null ? "1" : "0")); | |
+ if (!loadFinished(chunk)) { | |
+ return; | |
+ } | |
+ | |
+ System.out.println("GENERATE" + debug); | |
+ try { | |
+ chunk = generateChunk0(x, z); | |
+ System.out.println("GENERATE DONE " + debug); | |
+ generateFinished(chunk); | |
+ } catch (Exception e) { | |
+ generateFinished(null); | |
+ } | |
+ | |
+ } | |
+ } | |
+ | |
+} | |
diff --git a/src/main/java/net/minecraft/server/SchedulerBatch.java b/src/main/java/net/minecraft/server/SchedulerBatch.java | |
index 8c88fc9c38..23965dc882 100644 | |
--- a/src/main/java/net/minecraft/server/SchedulerBatch.java | |
+++ b/src/main/java/net/minecraft/server/SchedulerBatch.java | |
@@ -19,6 +19,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> { | |
this.b.b(); | |
} | |
+ public void startBatch() { b(); } // Paper - OBFHELPER | |
public void b() { | |
if (this.c) { | |
throw new RuntimeException("Batch already started."); | |
@@ -28,6 +29,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> { | |
} | |
} | |
+ public CompletableFuture<R> add(K k0) { return a(k0); } // Paper - OBFHELPER | |
public CompletableFuture<R> a(K k0) { | |
if (!this.c) { | |
throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch."); | |
@@ -44,6 +46,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> { | |
} | |
} | |
+ public CompletableFuture<R> executeBatch() { return c(); } // Paper - OBFHELPER | |
public CompletableFuture<R> c() { | |
if (!this.c) { | |
throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch."); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment