2019-02-07 06:04:29 +01:00
From 1e485c2ab878e0d28e6b70eb9ad3ace0c424dfee Mon Sep 17 00:00:00 2001
2018-09-27 00:21:44 +02:00
From: Aikar <aikar@aikar.co>
2018-09-27 04:35:42 +02:00
Date: Sat, 21 Jul 2018 16:55:04 -0400
2018-09-27 00:21:44 +02:00
Subject: [PATCH] Async Chunk Loading and Generation
This brings back parity to 1.12 and older versions in that any
chunk requested as part of the PlayerChunkMap can be loaded
asynchronously, since the chunk isn't needed "immediately".
The previous system used by CraftBukkit has been completely abandoned, as
mojang has put more concurrency checks into the process.
The new process is no longer lock free, but tries to maintain locks as
short as possible.
But with 1.13, we now have Chunk Conversions too. A main issue about
keeping just loading parity to 1.12 is that standard loads now
are treated as generation level events, to run the converter on
another thread.
However mojangs code was pretty bad here and doesn't actually provide
any concurrency...
Mojangs code is still not thread safe, and can only operate on
one world per thread safely, but this is still a major improvement
to get world generation off of the main thread for exploration.
This change brings Chunk Requests triggered by the Chunk Map to be
lazily loaded asynchronously.
Standard chunk loads can load in parallel across a shared executor.
However, chunk conversions and generations must only run one per world
at a time, so we have a single thread executor for those operations
per world, that all of those requests get scheduled to.
getChunkAt method is now thread safe, but has not been tested in
use by other threads for generations, but should be safe to do.
However, we are not encouraging plugins to go getting chunks async,
as while looking the chunk up may be safe, absolutely nothing about
reading or writing to the chunk will be safe, so plugins still
should not be touching chunks asynchronously!
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
2019-01-31 05:49:52 +01:00
index 359c538f3..942520088 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
2018-12-15 02:17:27 +01:00
@@ -376,4 +376,57 @@ public class PaperConfig {
2018-09-27 00:21:44 +02:00
}
}
}
+
+ public static boolean asyncChunks = false;
2018-10-03 05:42:29 +02:00
+ public static boolean asyncChunkGeneration = true;
+ public static boolean asyncChunkGenThreadPerWorld = true;
+ public static int asyncChunkLoadThreads = -1;
+ private static void asyncChunks() {
+ if (version < 15) {
+ boolean enabled = config.getBoolean("settings.async-chunks", true);
+ ConfigurationSection section = config.createSection("settings.async-chunks");
+ section.set("enable", enabled);
+ section.set("load-threads", -1);
+ section.set("generation", true);
+ section.set("thread-per-world-generation", true);
+ }
+
+ asyncChunks = getBoolean("settings.async-chunks.enable", true);
+ asyncChunkGeneration = getBoolean("settings.async-chunks.generation", true);
+ asyncChunkGenThreadPerWorld = getBoolean("settings.async-chunks.thread-per-world-generation", true);
+ asyncChunkLoadThreads = getInt("settings.async-chunks.load-threads", -1);
+ if (asyncChunkLoadThreads <= 0) {
2018-11-03 03:48:33 +01:00
+ asyncChunkLoadThreads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Runtime.getRuntime().availableProcessors() * 1.5);
2018-10-03 05:42:29 +02:00
+ }
+
+ // Let Shared Host set some limits
+ String sharedHostEnvGen = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_GEN");
+ String sharedHostEnvLoad = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_LOAD");
+ if ("1".equals(sharedHostEnvGen)) {
+ log("Async Chunks - Generation: Your host has requested to use a single thread world generation");
+ asyncChunkGenThreadPerWorld = false;
+ } else if ("2".equals(sharedHostEnvGen)) {
+ log("Async Chunks - Generation: Your host has disabled async world generation - You will experience lag from world generation");
+ asyncChunkGeneration = false;
+ }
+
+ if (sharedHostEnvLoad != null) {
+ try {
+ asyncChunkLoadThreads = Math.max(1, Math.min(asyncChunkLoadThreads, Integer.parseInt(sharedHostEnvLoad)));
+ } catch (NumberFormatException ignored) {}
+ }
+
2018-09-27 00:21:44 +02:00
+ if (!asyncChunks) {
+ log("Async Chunks: Disabled - Chunks will be managed synchronosuly, and will cause tremendous lag.");
+ } else {
2018-10-03 05:42:29 +02:00
+ log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
+ if (!asyncChunkGeneration) {
+ log("Async Chunks - Generation: Disabled - Chunks will be generated synchronosuly, and will cause tremendous lag.");
+ } else if (asyncChunkGenThreadPerWorld) {
+ log("Async Chunks - Generation: Enabled - Chunks will be generated much faster, without lag.");
+ } else {
+ log("Async Chunks - Generation: Enabled (Single Thread) - Chunks will be generated much faster, without lag.");
+ }
2018-09-27 00:21:44 +02:00
+ }
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
new file mode 100644
2019-01-31 05:49:52 +01:00
index 000000000..8f18c2869
2018-09-27 00:21:44 +02:00
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
2018-11-03 03:48:33 +01:00
@@ -0,0 +1,347 @@
2018-09-27 00:21:44 +02:00
+package com.destroystokyo.paper.util;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+/**
+ * Implements an Executor Service that allows specifying Task Priority
+ * and bumping of task priority.
+ *
2018-11-03 03:48:33 +01:00
+ * This is a non blocking executor with 3 priority levels.
+ *
+ * URGENT: Rarely used, something that is critical to take action now.
+ * HIGH: Something with more importance than the base tasks
+ *
+ * @author Daniel Ennis <aikar@aikar.co>
2018-09-27 00:21:44 +02:00
+ */
+@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
+public class PriorityQueuedExecutor extends AbstractExecutorService {
2018-11-03 03:48:33 +01:00
+
2018-10-28 15:40:30 +01:00
+ private final ConcurrentLinkedQueue<Runnable> urgent = new ConcurrentLinkedQueue<>();
2018-09-27 00:21:44 +02:00
+ private final ConcurrentLinkedQueue<Runnable> high = new ConcurrentLinkedQueue<>();
+ private final ConcurrentLinkedQueue<Runnable> normal = new ConcurrentLinkedQueue<>();
2018-11-03 03:48:33 +01:00
+ private final List<Thread> threads = new ArrayList<>();
2018-09-27 00:21:44 +02:00
+ private final RejectionHandler handler;
2018-11-03 03:48:33 +01:00
+
2018-09-27 00:21:44 +02:00
+ private volatile boolean shuttingDown = false;
+ private volatile boolean shuttingDownNow = false;
+
+ public PriorityQueuedExecutor(String name) {
2018-11-03 03:48:33 +01:00
+ this(name, Math.max(1, Runtime.getRuntime().availableProcessors() - 1));
2018-09-27 00:21:44 +02:00
+ }
+
+ public PriorityQueuedExecutor(String name, int threads) {
2018-11-03 03:48:33 +01:00
+ this(name, threads, Thread.NORM_PRIORITY, null);
+ }
+
+ public PriorityQueuedExecutor(String name, int threads, int threadPriority) {
+ this(name, threads, threadPriority, null);
2018-09-27 00:21:44 +02:00
+ }
+
+ public PriorityQueuedExecutor(String name, int threads, RejectionHandler handler) {
2018-11-03 03:48:33 +01:00
+ this(name, threads, Thread.NORM_PRIORITY, handler);
+ }
+
+ public PriorityQueuedExecutor(String name, int threads, int threadPriority, RejectionHandler handler) {
2018-09-27 00:21:44 +02:00
+ for (int i = 0; i < threads; i++) {
2018-11-03 03:48:33 +01:00
+ ExecutorThread thread = new ExecutorThread(this::processQueues);
+ thread.setDaemon(true);
+ thread.setName(threads == 1 ? name : name + "-" + (i + 1));
+ thread.setPriority(threadPriority);
2018-09-27 00:21:44 +02:00
+ thread.start();
+ this.threads.add(thread);
+ }
+ if (handler == null) {
+ handler = ABORT_POLICY;
+ }
+ this.handler = handler;
+ }
+
2018-11-03 03:48:33 +01:00
+ /**
+ * If the Current thread belongs to a PriorityQueuedExecutor, return that Executro
+ * @return The executor that controls this thread
+ */
+ public static PriorityQueuedExecutor getExecutor() {
+ if (!(Thread.currentThread() instanceof ExecutorThread)) {
+ return null;
+ }
+ return ((ExecutorThread) Thread.currentThread()).getExecutor();
+ }
+
2018-09-27 00:21:44 +02:00
+ public void shutdown() {
+ shuttingDown = true;
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ @Nonnull
+ @Override
+ public List<Runnable> shutdownNow() {
+ shuttingDown = true;
+ shuttingDownNow = true;
+ List<Runnable> tasks = new ArrayList<>(high.size() + normal.size());
+ Runnable run;
+ while ((run = getTask()) != null) {
+ tasks.add(run);
+ }
+
+ return tasks;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return shuttingDown;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ if (!shuttingDown) {
+ return false;
+ }
+ return high.isEmpty() && normal.isEmpty();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) {
+ synchronized (this) {
+ this.notifyAll();
+ }
+ final long wait = unit.toNanos(timeout);
+ final long max = System.nanoTime() + wait;
+ for (;!threads.isEmpty() && System.nanoTime() < max;) {
+ threads.removeIf(thread -> !thread.isAlive());
+ }
+ return isTerminated();
+ }
+
+
+ public PendingTask<Void> createPendingTask(Runnable task) {
+ return createPendingTask(task, Priority.NORMAL);
+ }
+ public PendingTask<Void> createPendingTask(Runnable task, Priority priority) {
+ return createPendingTask(() -> {
+ task.run();
+ return null;
+ }, priority);
+ }
+
+ public <T> PendingTask<T> createPendingTask(Supplier<T> task) {
+ return createPendingTask(task, Priority.NORMAL);
+ }
+
+ public <T> PendingTask<T> createPendingTask(Supplier<T> task, Priority priority) {
+ return new PendingTask<>(task, priority);
+ }
+
+ public PendingTask<Void> submitTask(Runnable run) {
2018-11-03 03:48:33 +01:00
+ return createPendingTask(run).submit();
2018-09-27 00:21:44 +02:00
+ }
+
+ public PendingTask<Void> submitTask(Runnable run, Priority priority) {
2018-11-03 03:48:33 +01:00
+ return createPendingTask(run, priority).submit();
2018-09-27 00:21:44 +02:00
+ }
+
+ public <T> PendingTask<T> submitTask(Supplier<T> run) {
2018-11-03 03:48:33 +01:00
+ return createPendingTask(run).submit();
2018-09-27 00:21:44 +02:00
+ }
+
+ public <T> PendingTask<T> submitTask(Supplier<T> run, Priority priority) {
2018-11-03 03:48:33 +01:00
+ PendingTask<T> task = createPendingTask(run, priority);
+ return task.submit();
2018-11-01 04:57:03 +01:00
+ }
+
2018-11-02 14:58:16 +01:00
+ @Override
+ public void execute(@Nonnull Runnable command) {
+ submitTask(command);
2018-11-01 04:57:03 +01:00
+ }
+
2018-11-03 03:48:33 +01:00
+ public boolean isCurrentThread() {
+ final Thread thread = Thread.currentThread();
+ if (!(thread instanceof ExecutorThread)) {
+ return false;
+ }
+ return ((ExecutorThread) thread).getExecutor() == this;
+ }
+
+ public Runnable getUrgentTask() {
+ return urgent.poll();
+ }
+
+ public Runnable getTask() {
2018-10-28 15:40:30 +01:00
+ Runnable run = urgent.poll();
+ if (run != null) {
+ return run;
+ }
+ run = high.poll();
2018-09-27 00:21:44 +02:00
+ if (run != null) {
+ return run;
+ }
+ return normal.poll();
+ }
+
+ private void processQueues() {
+ Runnable run = null;
+ while (true) {
+ if (run != null) {
+ run.run();
+ }
+ if (shuttingDownNow) {
+ return;
+ }
+ if ((run = getTask()) != null) {
+ continue;
+ }
+ synchronized (PriorityQueuedExecutor.this) {
+ if ((run = getTask()) != null) {
+ continue;
+ }
+
+ if (shuttingDown || shuttingDownNow) {
+ return;
+ }
+ try {
+ PriorityQueuedExecutor.this.wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ }
+
2018-11-03 03:48:33 +01:00
+ public boolean processUrgentTasks() {
+ Runnable run;
+ boolean hadTask = false;
+ while ((run = getUrgentTask()) != null) {
+ run.run();
+ hadTask = true;
+ }
+ return hadTask;
+ }
+
2018-09-27 00:21:44 +02:00
+ public enum Priority {
2018-10-28 15:40:30 +01:00
+ NORMAL, HIGH, URGENT
2018-09-27 00:21:44 +02:00
+ }
+
2018-11-03 03:48:33 +01:00
+ public class ExecutorThread extends Thread {
+ public ExecutorThread(Runnable runnable) {
+ super(runnable);
+ }
+
+ public PriorityQueuedExecutor getExecutor() {
+ return PriorityQueuedExecutor.this;
+ }
+ }
+
2018-09-27 00:21:44 +02:00
+ public class PendingTask <T> implements Runnable {
+
+ private final AtomicBoolean hasRan = new AtomicBoolean();
+ private final AtomicInteger submitted = new AtomicInteger(-1);
+ private final AtomicInteger priority;
+ private final Supplier<T> run;
+ private final CompletableFuture<T> future = new CompletableFuture<>();
+ private volatile PriorityQueuedExecutor executor;
+
+ public PendingTask(Supplier<T> run) {
+ this(run, Priority.NORMAL);
+ }
+
+ public PendingTask(Supplier<T> run, Priority priority) {
+ this.priority = new AtomicInteger(priority.ordinal());
+ this.run = run;
+ }
+
2018-10-06 06:02:09 +02:00
+ public boolean cancel() {
+ return hasRan.compareAndSet(false, true);
+ }
+
2018-09-27 00:21:44 +02:00
+ @Override
+ public void run() {
+ if (!hasRan.compareAndSet(false, true)) {
+ return;
+ }
+
+ try {
+ future.complete(run.get());
+ } catch (Throwable e) {
+ future.completeExceptionally(e);
+ }
+ }
+
+ public void bumpPriority() {
2018-10-28 15:40:30 +01:00
+ bumpPriority(Priority.HIGH);
+ }
+
+ public void bumpPriority(Priority newPriority) {
+ for (;;) {
+ int current = this.priority.get();
2018-11-03 03:48:33 +01:00
+ int ordinal = newPriority.ordinal();
+ if (current >= ordinal || priority.compareAndSet(current, ordinal)) {
2018-10-28 15:40:30 +01:00
+ break;
+ }
2018-09-27 00:21:44 +02:00
+ }
+
2018-11-03 03:48:33 +01:00
+
+ if (this.submitted.get() == -1 || this.hasRan.get()) {
2018-09-27 00:21:44 +02:00
+ return;
+ }
2018-11-03 03:48:33 +01:00
+
+ // Only resubmit if it hasnt ran yet and has been submitted
+ submit();
2018-09-27 00:21:44 +02:00
+ }
+
+ public CompletableFuture<T> onDone() {
+ return future;
+ }
+
2018-11-03 03:48:33 +01:00
+ public PendingTask<T> submit() {
+ if (shuttingDown) {
+ handler.onRejection(this, PriorityQueuedExecutor.this);
+ return this;
+ }
2018-09-27 00:21:44 +02:00
+ for (;;) {
+ final int submitted = this.submitted.get();
+ final int priority = this.priority.get();
+ if (submitted == priority) {
2018-11-03 03:48:33 +01:00
+ return this;
2018-09-27 00:21:44 +02:00
+ }
+ if (this.submitted.compareAndSet(submitted, priority)) {
2018-10-28 15:40:30 +01:00
+ if (priority == Priority.URGENT.ordinal()) {
+ urgent.add(this);
+ } else if (priority == Priority.HIGH.ordinal()) {
2018-09-27 00:21:44 +02:00
+ high.add(this);
+ } else {
+ normal.add(this);
+ }
+
+ break;
+ }
+ }
+
2018-11-03 03:48:33 +01:00
+ synchronized (PriorityQueuedExecutor.this) {
2018-09-27 00:21:44 +02:00
+ // Wake up a thread to take this work
2018-11-03 03:48:33 +01:00
+ PriorityQueuedExecutor.this.notify();
2018-09-27 00:21:44 +02:00
+ }
2018-11-03 03:48:33 +01:00
+ return this;
2018-09-27 00:21:44 +02:00
+ }
+ }
+ public interface RejectionHandler {
+ void onRejection(Runnable run, PriorityQueuedExecutor executor);
+ }
+
+ public static final RejectionHandler ABORT_POLICY = (run, executor) -> {
+ throw new RejectedExecutionException("Executor has been shutdown");
+ };
+ public static final RejectionHandler CALLER_RUNS_POLICY = (run, executor) -> {
+ run.run();
+ };
+
+}
2018-10-03 04:29:31 +02:00
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
2019-01-31 05:49:52 +01:00
index 71d865b76..6d3f5b651 100644
2018-10-03 04:29:31 +02:00
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
2019-01-01 04:15:55 +01:00
@@ -190,6 +190,7 @@ public class Chunk implements IChunkAccess {
2018-10-03 04:29:31 +02:00
for (k = 0; k < this.sections.length; ++k) {
this.sections[k] = protochunk.getSections()[k];
+ if (this.sections[k] != null) this.sections[k].disableLocks(); // Paper - Async Chunks - disable locks used during world gen
}
Iterator iterator = protochunk.s().iterator();
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
2019-01-31 05:49:52 +01:00
index 2021c0d02..154ab09e0 100644
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
--- a/src/main/java/net/minecraft/server/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/ChunkMap.java
@@ -14,9 +14,17 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
}
2018-12-17 06:18:06 +01:00
public Chunk put(long i, Chunk chunk) {
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
+ org.spigotmc.AsyncCatcher.catchOp("Async Chunk put"); // Paper
chunk.world.timings.syncChunkLoadPostTimer.startTiming(); // Paper
lastChunkByPos = chunk; // Paper
- Chunk chunk1 = (Chunk) super.put(i, chunk);
+ // Paper start
+ Chunk chunk1;
+ synchronized (this) {
+ // synchronize so any async gets are safe
+ chunk1 = (Chunk) super.put(i, chunk);
+ }
+ if (chunk1 == null) { // Paper - we should never be overwriting chunks
+ // Paper end
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
@@ -47,7 +55,11 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
chunk.setNeighborLoaded(x, z);
}
}
+ // Paper start
+ } } else {
+ a.error("Overwrote existing chunk! (" + chunk.world.getWorld().getName() + ":" + chunk.locX+"," + chunk.locZ + ")", new IllegalStateException());
}
+ // Paper end
// Paper start - if this is a spare chunk (not part of any players view distance), go ahead and queue it for unload.
if (!((WorldServer)chunk.world).getPlayerChunkMap().isChunkInUse(chunk.locX, chunk.locZ)) {
if (chunk.world.paperConfig.delayChunkUnloadsBy > 0) {
@@ -64,11 +76,19 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
}
2018-12-17 06:18:06 +01:00
public Chunk put(Long olong, Chunk chunk) {
- return this.put(olong, chunk);
+ return MCUtil.ensureMain("Chunk Put", () -> this.put(olong.longValue(), chunk)); // Paper
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
}
2018-12-17 06:18:06 +01:00
public Chunk remove(long i) {
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
- Chunk chunk = (Chunk) super.remove(i);
+ // Paper start
+ org.spigotmc.AsyncCatcher.catchOp("Async Chunk remove");
+ Chunk chunk;
+ synchronized (this) {
+ // synchronize so any async gets are safe
+ chunk = super.remove(i);
+ }
+ if (chunk != null) { // Paper - don't decrement if we didn't remove anything
+ // Paper end
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
@@ -84,6 +104,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
}
// Paper start
+ } // close if (chunk != null)
if (lastChunkByPos != null && i == lastChunkByPos.chunkKey) {
lastChunkByPos = null;
}
@@ -93,15 +114,22 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
@Override
public Chunk get(long l) {
- if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
- return lastChunkByPos;
+ if (MCUtil.isMainThread()) {
+ if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
+ return lastChunkByPos;
+ }
+ final Chunk chunk = super.get(l);
+ return chunk != null ? (lastChunkByPos = chunk) : null;
+ } else {
+ synchronized (this) {
+ return super.get(l);
+ }
}
- return lastChunkByPos = super.get(l);
}
// Paper end
2018-12-17 06:18:06 +01:00
public Chunk remove(Object object) {
- return this.remove((Long) object);
+ return MCUtil.ensureMain("Chunk Remove", () -> this.remove(((Long) object).longValue())); // Paper
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
}
public void putAll(Map<? extends Long, ? extends Chunk> map) {
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
2019-01-31 05:49:52 +01:00
index 186cfda7e..9cdeb3bfc 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
2019-01-01 04:15:55 +01:00
@@ -33,12 +33,12 @@ public class ChunkProviderServer implements IChunkProvider {
Remove MOST Synchronization from Chunk Map
This will provide quite a major performance boost by avoiding
synchronizing on EVERY chunk lookup.
Synchronize, even without contention, incurs processor cache flushes.
Considering this is the 2nd hottest method in the code base, lets
avoid doing that...
Additionally, chunk conversion operations were occuring while
under synchronization which lead to deadlocks.
Now the conversion will occur outside of the lock, and fix
that issue, resolving #1586
Note, that the chunk map is still thread safe for get operations!
The chunk map was never intended to be modified async with our
changes, as we post to main to modify the map, however
we do still synchronize for write operations (put, remove)
We also synchronize for async get operations, ensuring that
async gets are safe.
We do not need to synchronize main thread gets as the processor
cache will be insync since the map is only updated on the main thread.
However, if someone does try to delete or put concurrently, we
will force their operation back to the main thread.
2018-10-19 04:41:23 +02:00
private long lastProcessedSaves = 0L; // Paper
private long lastSaveStatPrinted = System.currentTimeMillis();
// Paper end
- public final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new ChunkMap(8192));
+ public final Long2ObjectMap<Chunk> chunks = new ChunkMap(8192); // Paper - remove synchronize - we keep everything on main for manip
2018-09-27 00:21:44 +02:00
private Chunk lastChunk;
private final ChunkTaskScheduler chunkScheduler;
- private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler;
+ final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler; // Paper
public final WorldServer world;
- private final IAsyncTaskHandler asyncTaskHandler;
+ final IAsyncTaskHandler asyncTaskHandler; // Paper
public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator<?> chunkgenerator, IAsyncTaskHandler iasynctaskhandler) {
this.world = worldserver;
2019-01-01 04:15:55 +01:00
@@ -75,10 +75,76 @@ public class ChunkProviderServer implements IChunkProvider {
2018-09-27 00:21:44 +02:00
this.unloadQueue.remove(ChunkCoordIntPair.a(i, j));
}
+ // Paper start - defaults if Async Chunks is not enabled
+ boolean chunkGoingToExists(int x, int z) {
+ final long k = ChunkCoordIntPair.asLong(x, z);
+ return chunkScheduler.progressCache.containsKey(k);
+ }
+ public void bumpPriority(ChunkCoordIntPair coords) {
+ // do nothing, override in async
+ }
+
+ public List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
+ List<ChunkCoordIntPair> list = com.google.common.collect.Lists.newArrayList();
+
+ for (int r = 1; r <= radius; r++) {
+ int x = -r;
+ int z = r;
+ list.add(new ChunkCoordIntPair(blockposition.getX(), blockposition.getZ()));
+ // Iterates the edge of half of the box; then negates for other half.
+ while (x <= r && z > -r) {
+ list.add(new ChunkCoordIntPair(blockposition.getX() + x, blockposition.getZ() + z));
+ list.add(new ChunkCoordIntPair(blockposition.getX() - x, blockposition.getZ() - z));
+
+ if (x < r) {
+ x++;
+ } else {
+ z--;
+ }
+ }
+ }
+ return list;
+ }
+
+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, Consumer<Chunk> consumer) {
+ return getChunkAt(x, z, load, gen, false, consumer);
+ }
+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
+ Chunk chunk = getChunkAt(x, z, load, gen);
+ if (consumer != null) {
+ consumer.accept(chunk);
+ }
+ return chunk;
+ }
2018-10-06 06:02:09 +02:00
+
+ PaperAsyncChunkProvider.CancellableChunkRequest requestChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
+ Chunk chunk = getChunkAt(x, z, gen, priority, consumer);
+ return new PaperAsyncChunkProvider.CancellableChunkRequest() {
+ @Override
+ public void cancel() {
+
+ }
+
+ @Override
+ public Chunk getChunk() {
+ return chunk;
+ }
+ };
+ }
2018-09-27 00:21:44 +02:00
+ // Paper end
+
@Nullable
public Chunk getChunkAt(int i, int j, boolean flag, boolean flag1) {
IChunkLoader ichunkloader = this.chunkLoader;
Chunk chunk;
+ // Paper start - do already loaded checks before synchronize
+ long k = ChunkCoordIntPair.a(i, j);
+ chunk = (Chunk) this.chunks.get(k);
+ if (chunk != null) {
+ //this.lastChunk = chunk; // Paper remove vanilla lastChunk
+ return chunk;
+ }
+ // Paper end
synchronized (this.chunkLoader) {
// Paper start - remove vanilla lastChunk, we do it more accurately
2019-01-01 04:15:55 +01:00
@@ -86,13 +152,15 @@ public class ChunkProviderServer implements IChunkProvider {
2018-09-27 00:21:44 +02:00
return this.lastChunk;
}*/ // Paper end
- long k = ChunkCoordIntPair.a(i, j);
+ // Paper start - move up
+ //long k = ChunkCoordIntPair.a(i, j);
- chunk = (Chunk) this.chunks.get(k);
+ /*chunk = (Chunk) this.chunks.get(k);
if (chunk != null) {
//this.lastChunk = chunk; // Paper remove vanilla lastChunk
return chunk;
- }
+ }*/
+ // Paper end
if (flag) {
try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) { // Paper
2019-01-04 20:19:36 +01:00
@@ -150,7 +218,8 @@ public class ChunkProviderServer implements IChunkProvider {
2018-09-27 00:21:44 +02:00
return (IChunkAccess) (chunk != null ? chunk : (IChunkAccess) this.chunkScheduler.b(new ChunkCoordIntPair(i, j), flag));
}
- public CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { return a(iterable, consumer).thenCompose(protoChunk -> null); } // Paper - overriden in async chunk provider
+ private CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { // Paper - mark private, use above method
this.batchScheduler.b();
Iterator iterator = iterable.iterator();
2019-01-04 20:19:36 +01:00
@@ -168,6 +237,7 @@ public class ChunkProviderServer implements IChunkProvider {
2018-09-27 00:21:44 +02:00
return this.batchScheduler.c();
}
+ 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");
2019-01-04 20:19:36 +01:00
@@ -289,11 +359,13 @@ public class ChunkProviderServer implements IChunkProvider {
2018-09-27 00:21:44 +02:00
}
public void close() {
- try {
+ // Paper start - we do not need to wait for chunk generations to finish on close
+ /*try {
this.batchScheduler.a();
} catch (InterruptedException interruptedexception) {
2018-12-08 11:09:55 +01:00
ChunkProviderServer.a.error("Couldn't stop taskManager", interruptedexception);
2018-09-27 00:21:44 +02:00
- }
+ }*/
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
2019-01-31 05:49:52 +01:00
index d938eb374..51df075b4 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
2019-01-04 20:19:36 +01:00
@@ -119,7 +119,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
2018-09-27 00:21:44 +02:00
// 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;
}
2019-01-04 20:19:36 +01:00
@@ -385,11 +385,12 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
2018-09-27 00:21:44 +02:00
}
};
} else {
+ /* // Paper start - we will never invoke this in an unsafe way
NBTTagCompound nbttagcompound2 = this.a(world, chunkcoordintpair.x, chunkcoordintpair.z);
if (nbttagcompound2 != null && this.a(nbttagcompound2) == ChunkStatus.Type.LEVELCHUNK) {
return;
- }
+ }*/ // Paper end
completion = new Supplier<NBTTagCompound>() {
public NBTTagCompound get() {
2018-10-03 04:29:31 +02:00
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
2019-01-31 05:49:52 +01:00
index 2af07ae59..9c6844d44 100644
2018-10-03 04:29:31 +02:00
--- a/src/main/java/net/minecraft/server/ChunkSection.java
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
2019-01-01 04:15:55 +01:00
@@ -25,7 +25,17 @@ public class ChunkSection {
2018-10-03 04:29:31 +02:00
this.skyLight = new NibbleArray();
}
+ // Paper start - Async Chunks - Lock during world gen
+ if (chunk instanceof ProtoChunk) {
+ this.blockIds.enableLocks();
+ } else {
+ this.blockIds.disableLocks();
+ }
+ }
+ void disableLocks() {
+ this.blockIds.disableLocks();
}
+ // Paper end
public IBlockData getType(int i, int j, int k) {
2019-01-01 04:15:55 +01:00
return (IBlockData) this.blockIds.a(i, j, k);
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
2019-01-31 05:49:52 +01:00
index d3898599f..8f061f5ca 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
+++ b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
2019-01-01 04:15:55 +01:00
@@ -17,13 +17,14 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
2018-09-27 00:21:44 +02:00
private final ChunkGenerator<?> d;
private final IChunkLoader e;
private final IAsyncTaskHandler f;
2019-01-01 04:15:55 +01:00
- private final Long2ObjectMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a> progressCache = new ExpiringMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a>(8192, 5000) {
+ protected final Long2ObjectMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a> progressCache = new ExpiringMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a>(8192, 5000) { // Paper - protected
protected boolean a(Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a scheduler_a) {
2018-09-27 00:21:44 +02:00
ProtoChunk protochunk = (ProtoChunk) scheduler_a.a();
2018-10-08 06:45:04 +02:00
return !protochunk.ab_() /*&& !protochunk.h()*/; // Paper
}
};
+ private final Long2ObjectMap<java.util.concurrent.CompletableFuture<Scheduler.a>> pendingSchedulers = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); // Paper
public ChunkTaskScheduler(int i, World world, ChunkGenerator<?> chunkgenerator, IChunkLoader ichunkloader, IAsyncTaskHandler iasynctaskhandler) {
super("WorldGen", i, ChunkStatus.FINALIZED, () -> {
2019-01-01 04:15:55 +01:00
@@ -47,8 +48,28 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
protected Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
2018-09-27 00:21:44 +02:00
IChunkLoader ichunkloader = this.e;
- synchronized (this.e) {
2018-10-08 06:45:04 +02:00
- return flag ? (Scheduler.a) this.progressCache.computeIfAbsent(chunkcoordintpair.a(), (i) -> {
+ // Paper start - refactor a lot of this - avoid generating a chunk while holding lock on expiring map
+ java.util.concurrent.CompletableFuture<Scheduler.a> pending = null;
+ boolean created = false;
+ long key = chunkcoordintpair.a();
+ synchronized (pendingSchedulers) {
+ Scheduler.a existing = this.progressCache.get(key);
+ if (existing != null) {
+ return existing;
+ }
+ pending = this.pendingSchedulers.get(key);
+ if (pending == null) {
+ if (!flag) {
+ return null;
+ }
+ created = true;
+ pending = new java.util.concurrent.CompletableFuture<>();
+ pendingSchedulers.put(key, pending);
+ }
+ }
+ if (created) {
+ java.util.function.Function<Long, Scheduler.a> get = (i) -> {
+ // Paper end
2018-09-27 00:21:44 +02:00
ProtoChunk protochunk;
2018-10-08 06:45:04 +02:00
try {
2019-01-01 04:15:55 +01:00
@@ -67,8 +88,18 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
2018-10-08 06:45:04 +02:00
} else {
return new Scheduler.a(chunkcoordintpair, new ProtoChunk(chunkcoordintpair, ChunkConverter.a, this.getWorld()), ChunkStatus.EMPTY); // Paper - Anti-Xray
}
- }) : (Scheduler.a) this.progressCache.get(chunkcoordintpair.a());
+ // Paper start
+ };
+ Scheduler.a scheduler = get.apply(key);
+ progressCache.put(key, scheduler);
+ pending.complete(scheduler);
+ synchronized (pendingSchedulers) {
+ pendingSchedulers.remove(key);
+ }
+ return scheduler;
}
+ return pending.join();
+ // Paper end
}
protected ProtoChunk a(ChunkCoordIntPair chunkcoordintpair, ChunkStatus chunkstatus, Map<ChunkCoordIntPair, ProtoChunk> map) {
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
2019-01-31 05:49:52 +01:00
index 454903a0e..dcbcb655c 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
@@ -3,7 +3,7 @@ package net.minecraft.server;
import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray
import java.util.Arrays;
import java.util.Objects;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.stream.Collectors;
2019-01-01 04:15:55 +01:00
@@ -21,26 +21,43 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
2018-09-27 00:21:44 +02:00
protected DataBits a; protected DataBits getDataBits() { return this.a; } // Paper - OBFHELPER
private DataPalette<T> h; private DataPalette<T> getDataPalette() { return this.h; } // Paper - OBFHELPER
private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER
- private final ReentrantLock j = new ReentrantLock();
2018-10-03 04:29:31 +02:00
+ // Paper start - use read write locks only during generation, disable once back on main thread
+ private static final NoopLock NOOP_LOCK = new NoopLock();
+ private java.util.concurrent.locks.Lock readLock = NOOP_LOCK;
+ private java.util.concurrent.locks.Lock writeLock = NOOP_LOCK;
+
+ private static class NoopLock extends ReentrantReadWriteLock.WriteLock {
+ private NoopLock() {
+ super(new ReentrantReadWriteLock());
+ }
+
+ @Override
+ public final void lock() {
+ }
+
+ @Override
+ public final void unlock() {
2018-09-27 00:21:44 +02:00
2018-10-03 04:29:31 +02:00
- private void b() {
2018-09-27 00:21:44 +02:00
- if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) {
2019-01-01 04:15:55 +01:00
- String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> {
- return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat "));
2018-09-27 00:21:44 +02:00
- }).collect(Collectors.joining("\n"));
- CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException());
- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps");
2019-01-01 04:15:55 +01:00
-
- crashreportsystemdetails.a("Thread dumps", (Object) s);
2018-09-27 00:21:44 +02:00
- throw new ReportedException(crashreport);
- } else {
- this.j.lock();
2018-10-03 04:29:31 +02:00
}
2018-09-27 00:21:44 +02:00
}
2018-10-03 04:29:31 +02:00
+ synchronized void enableLocks() {
+ ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+ readLock = lock.readLock();
+ writeLock = lock.writeLock();
+ }
+ synchronized void disableLocks() {
+ readLock = NOOP_LOCK;
+ writeLock = NOOP_LOCK;
+ }
2019-01-01 04:15:55 +01:00
+
2018-10-03 04:29:31 +02:00
+ private void b() {
+ writeLock.lock();
+ }
2018-09-27 00:21:44 +02:00
private void c() {
- this.j.unlock();
2018-10-03 04:29:31 +02:00
+ writeLock.unlock();
2018-09-27 00:21:44 +02:00
}
+ // Paper end
2019-01-01 04:15:55 +01:00
public DataPaletteBlock(DataPalette<T> datapalette, RegistryBlockID<T> registryblockid, Function<NBTTagCompound, T> function, Function<T, NBTTagCompound> function1, T t0) {
2018-09-27 00:21:44 +02:00
// Paper start - Anti-Xray - Support default constructor
2019-01-01 04:15:55 +01:00
@@ -155,9 +172,13 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
2018-09-27 00:21:44 +02:00
}
2019-01-01 04:15:55 +01:00
protected T a(int i) {
- T t0 = this.h.a(this.a.a(i));
-
- return t0 == null ? this.g : t0;
2018-10-03 04:29:31 +02:00
+ try { // Paper start - read lock
+ readLock.lock();
2019-01-01 04:15:55 +01:00
+ T object = this.h.a(this.a.a(i)); // Paper - decompile fix
2018-09-27 00:21:44 +02:00
+ return (T)(object == null ? this.g : object);
+ } finally {
2018-10-03 04:29:31 +02:00
+ readLock.unlock();
+ } // Paper end
2018-09-27 00:21:44 +02:00
}
// Paper start - Anti-Xray - Support default methods
diff --git a/src/main/java/net/minecraft/server/DefinedStructureManager.java b/src/main/java/net/minecraft/server/DefinedStructureManager.java
2019-01-31 05:49:52 +01:00
index f5a6387f2..f45685099 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/DefinedStructureManager.java
+++ b/src/main/java/net/minecraft/server/DefinedStructureManager.java
2019-01-01 04:15:55 +01:00
@@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger;
2018-09-27 00:21:44 +02:00
public class DefinedStructureManager implements IResourcePackListener {
2019-01-01 04:15:55 +01:00
2018-09-27 00:21:44 +02:00
private static final Logger a = LogManager.getLogger();
- private final Map<MinecraftKey, DefinedStructure> b = Maps.newHashMap();
+ private final Map<MinecraftKey, DefinedStructure> b = Maps.newConcurrentMap(); // Paper
private final DataFixer c;
private final MinecraftServer d;
private final java.nio.file.Path e;
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
2019-01-31 05:49:52 +01:00
index a4c5b0b5b..b572e535a 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
2019-01-01 04:15:55 +01:00
@@ -207,7 +207,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
2018-09-27 00:21:44 +02:00
this.random = SHARED_RANDOM; // Paper
this.fireTicks = -this.getMaxFireTicks();
this.justCreated = true;
- this.uniqueID = MathHelper.a(this.random);
+ this.uniqueID = MathHelper.a(java.util.concurrent.ThreadLocalRandom.current()); // Paper
this.au = this.uniqueID.toString();
this.aJ = Sets.newHashSet();
this.aL = new double[] { 0.0D, 0.0D, 0.0D};
diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java
2019-01-31 05:49:52 +01:00
index 4698ee99f..dfb45cc4e 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/IChunkLoader.java
+++ b/src/main/java/net/minecraft/server/IChunkLoader.java
@@ -6,6 +6,8 @@ import javax.annotation.Nullable;
public interface IChunkLoader {
+ void loadEntities(NBTTagCompound nbttagcompound, Chunk chunk); // Paper - Async Chunks
+ Object[] loadChunk(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException; // Paper - Async Chunks
@Nullable
Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException;
diff --git a/src/main/java/net/minecraft/server/MathHelper.java b/src/main/java/net/minecraft/server/MathHelper.java
2019-01-31 05:49:52 +01:00
index 8bb2593aa..67bb28954 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/MathHelper.java
+++ b/src/main/java/net/minecraft/server/MathHelper.java
2019-01-01 04:15:55 +01:00
@@ -7,7 +7,7 @@ import java.util.function.IntPredicate;
public class MathHelper {
public static final float a = c(2.0F);
- private static final float[] b = (float[]) SystemUtils.a((Object) (new float[65536]), (afloat) -> {
+ private static final float[] b = (float[]) SystemUtils.a((new float[65536]), (afloat) -> { // Paper - Decompile fix
for (int i = 0; i < afloat.length; ++i) {
afloat[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D);
}
@@ -136,6 +136,7 @@ public class MathHelper {
2018-09-27 00:21:44 +02:00
return Math.floorMod(i, j);
}
2018-09-29 01:31:59 +02:00
+ public static float normalizeYaw(float fx) { return g(fx); } // Paper - OBFHELPER
2019-01-01 04:15:55 +01:00
public static float g(float f) {
f %= 360.0F;
if (f >= 180.0F) {
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
2019-01-31 05:49:52 +01:00
index 68743a6b7..1dbb03a9a 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
2019-01-06 18:15:21 +01:00
@@ -499,6 +499,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
2018-09-27 00:21:44 +02:00
// CraftBukkit start - fire WorldLoadEvent and handle whether or not to keep the spawn in memory
Stopwatch stopwatch = Stopwatch.createStarted();
+ boolean waitForChunks = Boolean.getBoolean("paper.waitforchunks"); // Paper
for (WorldServer worldserver : this.getWorlds()) {
MinecraftServer.LOGGER.info("Preparing start region for level " + worldserver.dimension + " (Seed: " + worldserver.getSeed() + ")");
if (!worldserver.getWorld().getKeepSpawnInMemory()) {
2019-01-06 18:15:21 +01:00
@@ -506,29 +507,24 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
2018-09-27 00:21:44 +02:00
}
BlockPosition blockposition = worldserver.getSpawn();
2019-01-01 04:15:55 +01:00
- List<ChunkCoordIntPair> list = Lists.newArrayList();
+ List<ChunkCoordIntPair> list = worldserver.getChunkProvider().getSpiralOutChunks(blockposition, worldserver.paperConfig.keepLoadedRange >> 4); // Paper
Set<ChunkCoordIntPair> set = Sets.newConcurrentHashSet();
2018-09-27 00:21:44 +02:00
- // Paper start
- short radius = worldserver.paperConfig.keepLoadedRange;
- for (int i = -radius; i <= radius && this.isRunning(); i += 16) {
- for (int j = -radius; j <= radius && this.isRunning(); j += 16) {
- // Paper end
2019-01-01 04:15:55 +01:00
- list.add(new ChunkCoordIntPair(blockposition.getX() + i >> 4, blockposition.getZ() + j >> 4));
2018-09-27 00:21:44 +02:00
- }
- } // Paper
+ // Paper - remove arraylist creation, call spiral above
if (this.isRunning()) { // Paper
2019-01-01 04:15:55 +01:00
int expected = list.size(); // Paper
2018-09-27 00:21:44 +02:00
2019-01-01 04:15:55 +01:00
-
- CompletableFuture completablefuture = worldserver.getChunkProvider().a((Iterable) list, (chunk) -> {
+ CompletableFuture completablefuture = worldserver.getChunkProvider().loadAllChunks(list, (chunk) -> { // Paper
2018-09-27 00:21:44 +02:00
set.add(chunk.getPos());
- if (set.size() < expected && set.size() % 25 == 0) this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
+ if (waitForChunks && (set.size() == expected || (set.size() < expected && set.size() % (set.size() / 10) == 0))) {
+ this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
+ }
});
- while (!completablefuture.isDone()) {
+ while (waitForChunks && !completablefuture.isDone() && isRunning()) { // Paper
try {
- completablefuture.get(1L, TimeUnit.SECONDS);
2018-11-03 03:48:33 +01:00
+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
2018-09-27 00:21:44 +02:00
+ completablefuture.get(50L, TimeUnit.MILLISECONDS); // Paper
} catch (InterruptedException interruptedexception) {
throw new RuntimeException(interruptedexception);
} catch (ExecutionException executionexception) {
2019-01-06 18:15:21 +01:00
@@ -538,11 +534,11 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
2018-09-27 00:21:44 +02:00
throw new RuntimeException(executionexception.getCause());
} catch (TimeoutException timeoutexception) {
- this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
+ //this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
}
}
- this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
+ if (waitForChunks) this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
}
}
2019-01-06 18:15:21 +01:00
@@ -646,6 +642,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
2018-09-27 00:21:44 +02:00
if (hasStopped) return;
hasStopped = true;
}
+ PaperAsyncChunkProvider.stop(this); // Paper
// CraftBukkit end
MinecraftServer.LOGGER.info("Stopping server");
MinecraftTimings.stopServer(); // Paper
2019-01-06 18:15:21 +01:00
@@ -1013,6 +1010,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
2018-09-27 00:21:44 +02:00
while ((futuretask = (FutureTask) this.f.poll()) != null) {
SystemUtils.a(futuretask, MinecraftServer.LOGGER);
}
2018-11-03 03:48:33 +01:00
+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
2018-09-27 00:21:44 +02:00
MinecraftTimings.minecraftSchedulerTimer.stopTiming(); // Paper
2018-12-17 06:18:06 +01:00
this.methodProfiler.exitEnter("commandFunctions");
2019-01-06 18:15:21 +01:00
@@ -1049,6 +1047,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
2018-09-27 00:21:44 +02:00
// CraftBukkit - dropTickTime
for (Iterator iterator = this.getWorlds().iterator(); iterator.hasNext();) {
WorldServer worldserver = (WorldServer) iterator.next();
2018-11-03 03:48:33 +01:00
+ PaperAsyncChunkProvider.processMainThreadQueue(worldserver); // Paper
2018-09-27 00:21:44 +02:00
TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
2018-12-17 06:18:06 +01:00
i = SystemUtils.getMonotonicNanos();
2018-09-27 00:21:44 +02:00
if (true || worldserver.worldProvider.getDimensionManager() == DimensionManager.OVERWORLD || this.getAllowNether()) { // CraftBukkit
diff --git a/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
new file mode 100644
2019-02-07 06:04:29 +01:00
index 000000000..896fc94a6
2018-09-27 00:21:44 +02:00
--- /dev/null
+++ b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
2019-02-07 06:04:29 +01:00
@@ -0,0 +1,656 @@
2018-09-27 00:21:44 +02:00
+/*
+ * 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.destroystokyo.paper.PaperConfig;
+import com.destroystokyo.paper.util.PriorityQueuedExecutor;
+import com.destroystokyo.paper.util.PriorityQueuedExecutor.Priority;
+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 org.bukkit.craftbukkit.generator.CustomChunkGenerator;
+import org.bukkit.craftbukkit.generator.InternalChunkGenerator;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
2018-11-03 03:48:33 +01:00
+import java.util.concurrent.ConcurrentLinkedDeque;
2018-10-06 06:02:09 +02:00
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
2018-09-27 00:21:44 +02:00
+import java.util.function.Consumer;
+
+@SuppressWarnings("unused")
+public class PaperAsyncChunkProvider extends ChunkProviderServer {
+
2018-11-03 03:48:33 +01:00
+ private static final int GEN_THREAD_PRIORITY = Integer.getInteger("paper.genThreadPriority", 3);
+ private static final int LOAD_THREAD_PRIORITY = Integer.getInteger("paper.loadThreadPriority", 4);
+ private static final PriorityQueuedExecutor EXECUTOR = new PriorityQueuedExecutor("PaperChunkLoader", PaperConfig.asyncChunks ? PaperConfig.asyncChunkLoadThreads : 0, LOAD_THREAD_PRIORITY);
+ private static final PriorityQueuedExecutor SINGLE_GEN_EXECUTOR = new PriorityQueuedExecutor("PaperChunkGenerator", PaperConfig.asyncChunks && PaperConfig.asyncChunkGeneration && !PaperConfig.asyncChunkGenThreadPerWorld ? 1 : 0, GEN_THREAD_PRIORITY);
+ private static final ConcurrentLinkedDeque<Runnable> MAIN_THREAD_QUEUE = new ConcurrentLinkedDeque<>();
2018-09-27 00:21:44 +02:00
+
+ private final PriorityQueuedExecutor generationExecutor;
+ //private static final PriorityQueuedExecutor generationExecutor = new PriorityQueuedExecutor("PaperChunkGen", 1);
+ private final Long2ObjectMap<PendingChunk> pendingChunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
+ private final IAsyncTaskHandler asyncHandler;
+
+ private final WorldServer world;
+ private final IChunkLoader chunkLoader;
+ private final MinecraftServer server;
+ private final boolean shouldGenSync;
+
+ public PaperAsyncChunkProvider(WorldServer world, IChunkLoader chunkLoader, InternalChunkGenerator generator, MinecraftServer server) {
+ super(world, chunkLoader, generator, server);
+
+ this.server = world.getMinecraftServer();
+ this.world = world;
+ this.asyncHandler = server;
+ this.chunkLoader = chunkLoader;
+ String worldName = this.world.getWorld().getName();
2018-10-03 05:42:29 +02:00
+ this.shouldGenSync = generator instanceof CustomChunkGenerator && !(((CustomChunkGenerator) generator).asyncSupported) || !PaperConfig.asyncChunkGeneration;
2018-11-03 03:48:33 +01:00
+ this.generationExecutor = PaperConfig.asyncChunkGenThreadPerWorld ? new PriorityQueuedExecutor("PaperChunkGen-" + worldName, shouldGenSync ? 0 : 1, GEN_THREAD_PRIORITY) : SINGLE_GEN_EXECUTOR;
2018-09-27 00:21:44 +02:00
+ }
+
2018-11-03 03:48:33 +01:00
+ private static Priority calculatePriority(boolean isBlockingMain, boolean priority) {
+ if (isBlockingMain) {
+ return Priority.URGENT;
2018-09-27 00:21:44 +02:00
+ }
+
2018-11-03 03:48:33 +01:00
+ if (priority) {
+ return Priority.HIGH;
2018-09-27 00:21:44 +02:00
+ }
2018-11-03 03:48:33 +01:00
+
+ return Priority.NORMAL;
2018-09-27 00:21:44 +02:00
+ }
+
+ static void stop(MinecraftServer server) {
+ for (WorldServer world : server.getWorlds()) {
2018-10-06 06:15:40 +02:00
+ world.getPlayerChunkMap().shutdown();
2018-09-27 00:21:44 +02:00
+ }
+ }
+
2018-11-03 03:48:33 +01:00
+ static void processMainThreadQueue(MinecraftServer server) {
+ for (WorldServer world : server.getWorlds()) {
+ processMainThreadQueue(world);
2018-10-28 15:40:30 +01:00
+ }
2018-11-03 03:48:33 +01:00
+ }
2018-10-28 15:40:30 +01:00
+
2018-11-03 03:48:33 +01:00
+ static void processMainThreadQueue(World world) {
+ IChunkProvider chunkProvider = world.getChunkProvider();
+ if (chunkProvider instanceof PaperAsyncChunkProvider) {
+ ((PaperAsyncChunkProvider) chunkProvider).processMainThreadQueue();
2018-10-28 15:40:30 +01:00
+ }
+ }
+
2018-11-03 03:48:33 +01:00
+ private void processMainThreadQueue() {
+ processMainThreadQueue((PendingChunk) null);
+ }
+ private boolean processMainThreadQueue(PendingChunk pending) {
2018-09-27 00:21:44 +02:00
+ Runnable run;
+ boolean hadLoad = false;
2018-10-12 16:41:15 +02:00
+ while ((run = MAIN_THREAD_QUEUE.poll()) != null) {
2018-09-27 00:21:44 +02:00
+ run.run();
+ hadLoad = true;
2018-11-03 03:48:33 +01:00
+ if (pending != null && pending.hasPosted) {
+ break;
+ }
2018-09-27 00:21:44 +02:00
+ }
+ return hadLoad;
+ }
+
+ @Override
+ public void bumpPriority(ChunkCoordIntPair coords) {
2018-11-03 03:48:33 +01:00
+ final PendingChunk pending = pendingChunks.get(coords.asLong());
2018-09-27 00:21:44 +02:00
+ if (pending != null) {
2018-10-28 15:40:30 +01:00
+ pending.bumpPriority(Priority.HIGH);
2018-09-27 00:21:44 +02:00
+ }
+ }
+
+ @Nullable
+ @Override
+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen) {
+ return getChunkAt(x, z, load, gen, null);
+ }
+
+ @Nullable
+ @Override
+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
2018-11-03 03:48:33 +01:00
+ final long key = ChunkCoordIntPair.asLong(x, z);
+ final Chunk chunk = this.chunks.get(key);
2018-09-27 00:21:44 +02:00
+ if (chunk != null || !load) { // return null if we aren't loading
2018-10-01 07:00:09 +02:00
+ if (consumer != null) {
+ consumer.accept(chunk);
+ }
2018-09-27 00:21:44 +02:00
+ return chunk;
+ }
+ return loadOrGenerateChunk(x, z, gen, priority, consumer); // Async overrides this method
+ }
+
+ private Chunk loadOrGenerateChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
2018-10-06 06:02:09 +02:00
+ return requestChunk(x, z, gen, priority, consumer).getChunk();
+ }
+
2018-11-03 03:48:33 +01:00
+ final PendingChunkRequest requestChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
+ try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) {
+ final long key = ChunkCoordIntPair.asLong(x, z);
+ final boolean isChunkThread = isChunkThread();
+ final boolean isBlockingMain = consumer == null && server.isMainThread();
+ final boolean loadOnThisThread = isChunkThread || isBlockingMain;
+ final Priority taskPriority = calculatePriority(isBlockingMain, priority);
+
+ // Obtain a PendingChunk
+ final PendingChunk pending;
+ synchronized (pendingChunks) {
+ PendingChunk pendingChunk = pendingChunks.get(key);
+ if (pendingChunk == null) {
+ pending = new PendingChunk(x, z, key, gen, taskPriority);
+ pendingChunks.put(key, pending);
+ } else if (pendingChunk.hasFinished && gen && !pendingChunk.canGenerate && pendingChunk.chunk == null) {
+ // need to overwrite the old
+ pending = new PendingChunk(x, z, key, true, taskPriority);
+ pendingChunks.put(key, pending);
+ } else {
+ pending = pendingChunk;
+ if (pending.taskPriority != taskPriority) {
+ pending.bumpPriority(taskPriority);
+ }
+ }
+ }
2018-09-27 00:21:44 +02:00
+
2018-11-03 03:48:33 +01:00
+ // Listen for when result is ready
+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
+ final PendingChunkRequest request = pending.addListener(future, gen, !loadOnThisThread);
2018-11-02 14:58:16 +01:00
+
2018-11-03 03:48:33 +01:00
+ // Chunk Generation can trigger Chunk Loading, those loads may need to convert, and could be slow
+ // Give an opportunity for urgent tasks to jump in at these times
+ if (isChunkThread) {
+ processUrgentTasks();
2018-09-27 00:21:44 +02:00
+ }
+
2018-11-03 03:48:33 +01:00
+ if (loadOnThisThread) {
+ // do loads on main if blocking, or on current if we are a load/gen thread
+ // gen threads do trigger chunk loads
+ pending.loadTask.run();
+ }
2018-11-02 14:58:16 +01:00
+
2018-11-03 03:48:33 +01:00
+ if (isBlockingMain) {
2018-09-27 00:21:44 +02:00
+ while (!future.isDone()) {
+ // We aren't done, obtain lock on queue
2018-10-12 16:41:15 +02:00
+ synchronized (MAIN_THREAD_QUEUE) {
2018-09-27 00:21:44 +02:00
+ // We may of received our request now, check it
2018-11-03 03:48:33 +01:00
+ if (processMainThreadQueue(pending)) {
2018-09-27 00:21:44 +02:00
+ // If we processed SOMETHING, don't wait
+ continue;
+ }
+ try {
+ // We got nothing from the queue, wait until something has been added
2018-10-12 16:41:15 +02:00
+ MAIN_THREAD_QUEUE.wait(1);
2018-09-27 00:21:44 +02:00
+ } catch (InterruptedException ignored) {
+ }
+ }
+ // Queue has been notified or timed out, process it
2018-11-03 03:48:33 +01:00
+ processMainThreadQueue(pending);
2018-09-27 00:21:44 +02:00
+ }
+ // We should be done AND posted into chunk map now, return it
2018-11-03 03:48:33 +01:00
+ request.initialReturnChunk = pending.postChunk();
+ } else if (consumer == null) {
+ // This is on another thread
2018-10-06 06:02:09 +02:00
+ request.initialReturnChunk = future.join();
2018-11-03 03:48:33 +01:00
+ } else {
+ future.thenAccept((c) -> this.asyncHandler.postToMainThread(() -> consumer.accept(c)));
2018-09-27 00:21:44 +02:00
+ }
2018-11-03 03:48:33 +01:00
+
+ return request;
2018-09-27 00:21:44 +02:00
+ }
2018-11-03 03:48:33 +01:00
+ }
2018-10-06 06:02:09 +02:00
+
2018-11-03 03:48:33 +01:00
+ private void processUrgentTasks() {
+ final PriorityQueuedExecutor executor = PriorityQueuedExecutor.getExecutor();
+ if (executor != null) {
+ executor.processUrgentTasks();
+ }
2018-09-27 00:21:44 +02:00
+ }
+
+ @Override
+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
2018-11-03 03:48:33 +01:00
+ final Iterator<ChunkCoordIntPair> iterator = iterable.iterator();
2018-09-27 00:21:44 +02:00
+
2018-11-03 03:48:33 +01:00
+ final List<CompletableFuture<Chunk>> all = new ArrayList<>();
2018-09-27 00:21:44 +02:00
+ while (iterator.hasNext()) {
2018-11-03 03:48:33 +01:00
+ final ChunkCoordIntPair chunkcoordintpair = iterator.next();
+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
2018-09-27 00:21:44 +02:00
+ all.add(future);
+ this.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, true, true, chunk -> {
+ future.complete(chunk);
+ if (consumer != null) {
+ consumer.accept(chunk);
+ }
+ });
+ }
+ return CompletableFuture.allOf(all.toArray(new CompletableFuture[0]));
+ }
+
+ boolean chunkGoingToExists(int x, int z) {
+ synchronized (pendingChunks) {
2018-11-03 03:48:33 +01:00
+ PendingChunk pendingChunk = pendingChunks.get(ChunkCoordIntPair.asLong(x, z));
2018-09-27 00:21:44 +02:00
+ return pendingChunk != null && pendingChunk.canGenerate;
+ }
+ }
+
+ private enum PendingStatus {
+ /**
+ * Request has just started
+ */
+ STARTED,
+ /**
+ * Chunk is attempting to be loaded from disk
+ */
+ LOADING,
+ /**
+ * Chunk must generate on main and is pending main
+ */
+ GENERATION_PENDING,
+ /**
+ * Chunk is generating
+ */
+ GENERATING,
+ /**
+ * Chunk is ready and is pending post to main
+ */
+ PENDING_MAIN,
+ /**
+ * Could not load chunk, and did not need to generat
+ */
+ FAIL,
+ /**
+ * Fully done with this request (may or may not of loaded)
+ */
2018-10-06 06:02:09 +02:00
+ DONE,
+ /**
+ * Chunk load was cancelled (no longer needed)
+ */
+ CANCELLED
+ }
+
+ public interface CancellableChunkRequest {
+ void cancel();
+ Chunk getChunk();
+ }
+
+ public static class PendingChunkRequest implements CancellableChunkRequest {
+ private final PendingChunk pending;
+ private final AtomicBoolean cancelled = new AtomicBoolean(false);
+ private volatile boolean generating;
+ private volatile Chunk initialReturnChunk;
+
+ private PendingChunkRequest(PendingChunk pending) {
+ this.pending = pending;
+ this.cancelled.set(true);
+ }
+
+ private PendingChunkRequest(PendingChunk pending, boolean gen) {
+ this.pending = pending;
+ this.generating = gen;
+ }
+
+ public void cancel() {
+ this.pending.cancel(this);
+ }
+
+ /**
+ * Will be null on asynchronous loads
+ */
+ @Override @Nullable
+ public Chunk getChunk() {
+ return initialReturnChunk;
+ }
2018-09-27 00:21:44 +02:00
+ }
+
2018-11-03 03:48:33 +01:00
+ private boolean isLoadThread() {
+ return EXECUTOR.isCurrentThread();
+ }
+
+ private boolean isGenThread() {
+ return generationExecutor.isCurrentThread();
+ }
+ private boolean isChunkThread() {
+ return isLoadThread() || isGenThread();
+ }
+
2018-09-27 00:21:44 +02:00
+ private class PendingChunk implements Runnable {
+ private final int x;
+ private final int z;
+ private final long key;
2018-11-03 03:48:33 +01:00
+ private final long started = System.currentTimeMillis();
2018-09-27 00:21:44 +02:00
+ private final CompletableFuture<Chunk> loadOnly = new CompletableFuture<>();
+ private final CompletableFuture<Chunk> generate = new CompletableFuture<>();
2018-10-06 06:02:09 +02:00
+ private final AtomicInteger requests = new AtomicInteger(0);
2018-09-27 00:21:44 +02:00
+
+ private volatile PendingStatus status = PendingStatus.STARTED;
2018-10-06 06:02:09 +02:00
+ private volatile PriorityQueuedExecutor.PendingTask<Void> loadTask;
+ private volatile PriorityQueuedExecutor.PendingTask<Chunk> genTask;
+ private volatile Priority taskPriority;
2018-09-27 00:21:44 +02:00
+ private volatile boolean generating;
+ private volatile boolean canGenerate;
+ private volatile boolean isHighPriority;
+ private volatile boolean hasPosted;
+ private volatile boolean hasFinished;
+ private volatile Chunk chunk;
+ private volatile NBTTagCompound pendingLevel;
+
+ PendingChunk(int x, int z, long key, boolean canGenerate, boolean priority) {
+ this.x = x;
+ this.z = z;
+ this.key = key;
+ this.canGenerate = canGenerate;
2018-10-06 06:02:09 +02:00
+ taskPriority = priority ? Priority.HIGH : Priority.NORMAL;
2018-09-27 00:21:44 +02:00
+ }
+
2018-10-28 15:40:30 +01:00
+ PendingChunk(int x, int z, long key, boolean canGenerate, Priority taskPriority) {
+ this.x = x;
+ this.z = z;
+ this.key = key;
+ this.canGenerate = canGenerate;
+ this.taskPriority = taskPriority;
+ }
+
2018-09-27 00:21:44 +02:00
+ private synchronized void setStatus(PendingStatus status) {
+ this.status = status;
+ }
+
+ private Chunk loadChunk(int x, int z) throws IOException {
+ setStatus(PendingStatus.LOADING);
+ Object[] data = chunkLoader.loadChunk(world, x, z, null);
+ if (data != null) {
+ // Level must be loaded on main
+ this.pendingLevel = ((NBTTagCompound) data[1]).getCompound("Level");
+ return (Chunk) data[0];
+ } else {
+ return null;
+ }
+ }
+
+ private Chunk generateChunk() {
2018-10-06 06:02:09 +02:00
+ synchronized (this) {
+ if (requests.get() <= 0) {
+ return null;
+ }
+ }
2018-09-27 00:21:44 +02:00
+ CompletableFuture<Chunk> pending = new CompletableFuture<>();
+ batchScheduler.startBatch();
+ batchScheduler.add(new ChunkCoordIntPair(x, z));
+ try {
+ ProtoChunk protoChunk = batchScheduler.executeBatch().join();
+ boolean saved = false;
+ if (!Bukkit.isPrimaryThread()) {
+ // If we are async, dispatch later
+ try {
+ chunkLoader.saveChunk(world, protoChunk, true);
+ saved = true;
+ } catch (IOException | ExceptionWorldConflict e) {
+ e.printStackTrace();
+ }
+ }
+ Chunk chunk = new Chunk(world, protoChunk, x, z);
+ if (saved) {
+ chunk.setLastSaved(world.getTime());
+ }
+ generateFinished(chunk);
+
+ return chunk;
+ } catch (Exception e) {
+ MinecraftServer.LOGGER.error("Couldn't generate chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", e);
+ generateFinished(null);
+ return null;
+ }
+ }
+
+ boolean loadFinished(Chunk chunk) {
+ if (chunk != null) {
+ postChunkToMain(chunk);
+ return false;
+ }
+ loadOnly.complete(null);
+
+ synchronized (this) {
2018-10-06 06:02:09 +02:00
+ boolean cancelled = requests.get() <= 0;
+ if (!canGenerate || cancelled) {
+ if (!cancelled) {
+ setStatus(PendingStatus.FAIL);
+ }
2018-09-27 00:21:44 +02:00
+ this.chunk = null;
+ this.hasFinished = true;
+ pendingChunks.remove(key);
+ return false;
+ } else {
+ setStatus(PendingStatus.GENERATING);
+ generating = true;
+ return true;
+ }
+ }
+ }
+
+ void generateFinished(Chunk chunk) {
+ synchronized (this) {
+ this.chunk = chunk;
+ this.hasFinished = true;
+ }
+ if (chunk != null) {
+ postChunkToMain(chunk);
+ } else {
+ synchronized (this) {
+ pendingChunks.remove(key);
+ completeFutures(null);
+ }
+ }
+ }
+
+ synchronized private void completeFutures(Chunk chunk) {
+ loadOnly.complete(chunk);
+ generate.complete(chunk);
+ }
+
+ private void postChunkToMain(Chunk chunk) {
+ synchronized (this) {
+ setStatus(PendingStatus.PENDING_MAIN);
+ this.chunk = chunk;
+ this.hasFinished = true;
+ }
2018-10-03 05:42:29 +02:00
+
2018-11-03 03:48:33 +01:00
+ if (server.isMainThread()) {
+ postChunk();
+ return;
+ }
+
2018-10-03 05:42:29 +02:00
+ // Don't post here, even if on main, it must enter the queue so we can exit any open batch
+ // schedulers, as post stage may trigger a new generation and cause errors
2018-10-12 16:41:15 +02:00
+ synchronized (MAIN_THREAD_QUEUE) {
2018-11-03 03:48:33 +01:00
+ if (this.taskPriority == Priority.URGENT) {
+ MAIN_THREAD_QUEUE.addFirst(this::postChunk);
+ } else {
+ MAIN_THREAD_QUEUE.addLast(this::postChunk);
+ }
2018-10-12 16:41:15 +02:00
+ MAIN_THREAD_QUEUE.notify();
2018-09-27 00:21:44 +02:00
+ }
+ }
+
+ Chunk postChunk() {
+ if (!server.isMainThread()) {
+ throw new IllegalStateException("Must post from main");
+ }
+ synchronized (this) {
2018-10-06 06:02:09 +02:00
+ if (hasPosted || requests.get() <= 0) { // if pending is 0, all were cancelled
2018-09-27 00:21:44 +02:00
+ return chunk;
+ }
+ hasPosted = true;
+ }
+ try {
+ if (chunk == null) {
+ chunk = chunks.get(key);
+ completeFutures(chunk);
+ return chunk;
+ }
+ if (pendingLevel != null) {
+ chunkLoader.loadEntities(pendingLevel, chunk);
+ pendingLevel = null;
+ }
+ synchronized (chunks) {
+ final Chunk other = chunks.get(key);
+ if (other != null) {
+ this.chunk = other;
+ completeFutures(other);
+ return other;
+ }
+ if (chunk != null) {
+ chunks.put(key, chunk);
+ }
+ }
+
+ chunk.addEntities();
+
+ completeFutures(chunk);
+ return chunk;
+ } finally {
+ pendingChunks.remove(key);
+ setStatus(PendingStatus.DONE);
+ }
+ }
+
2018-11-03 03:48:33 +01:00
+ synchronized PendingChunkRequest addListener(CompletableFuture<Chunk> future, boolean gen, boolean autoSubmit) {
2019-02-07 06:04:29 +01:00
+ requests.incrementAndGet();
+ if (loadTask == null) {
+ // Take care of a race condition in that a request could be cancelled after the synchronize
+ // on pendingChunks, but before a listener is added, which would erase these pending tasks.
+ genTask = generationExecutor.createPendingTask(this::generateChunk, taskPriority);
+ loadTask = EXECUTOR.createPendingTask(this, taskPriority);
+ if (autoSubmit) {
+ // We will execute it outside of the synchronized context immediately after
+ loadTask.submit();
+ }
+ }
+
2018-09-27 00:21:44 +02:00
+ if (hasFinished) {
+ future.complete(chunk);
2018-10-06 06:02:09 +02:00
+ return new PendingChunkRequest(this);
2018-09-27 00:21:44 +02:00
+ } else if (gen) {
+ canGenerate = true;
+ generate.thenAccept(future::complete);
+ } else {
+ if (generating) {
+ future.complete(null);
2018-10-06 06:02:09 +02:00
+ return new PendingChunkRequest(this);
2018-09-27 00:21:44 +02:00
+ } else {
+ loadOnly.thenAccept(future::complete);
+ }
+ }
2018-10-06 06:02:09 +02:00
+
+ return new PendingChunkRequest(this, gen);
2018-09-27 00:21:44 +02:00
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (!loadFinished(loadChunk(x, z))) {
+ return;
+ }
+ } catch (Exception ex) {
+ MinecraftServer.LOGGER.error("Couldn't load chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", ex);
2018-10-11 03:28:12 +02:00
+ if (ex instanceof IOException) {
+ generateFinished(null);
2018-09-27 00:21:44 +02:00
+ return;
+ }
+ }
+
+ if (shouldGenSync) {
+ synchronized (this) {
+ setStatus(PendingStatus.GENERATION_PENDING);
2018-11-03 03:48:33 +01:00
+ if (this.taskPriority == Priority.URGENT) {
+ MAIN_THREAD_QUEUE.addFirst(() -> generateFinished(this.generateChunk()));
+ } else {
+ MAIN_THREAD_QUEUE.addLast(() -> generateFinished(this.generateChunk()));
+ }
+
2018-09-27 00:21:44 +02:00
+ }
2018-10-12 16:41:15 +02:00
+ synchronized (MAIN_THREAD_QUEUE) {
+ MAIN_THREAD_QUEUE.notify();
2018-09-27 00:21:44 +02:00
+ }
+ } else {
2018-11-03 03:48:33 +01:00
+ if (isGenThread()) {
2018-10-11 04:48:35 +02:00
+ // ideally we should never run into 1 chunk generating another chunk...
+ // but if we do, let's apply same solution
+ genTask.run();
+ } else {
2018-11-03 03:48:33 +01:00
+ genTask.submit();
2018-10-11 04:48:35 +02:00
+ }
2018-09-27 00:21:44 +02:00
+ }
+ }
+
+ void bumpPriority() {
2018-10-28 15:40:30 +01:00
+ bumpPriority(Priority.HIGH);
+ }
+
+ void bumpPriority(Priority newPriority) {
2018-11-03 03:48:33 +01:00
+ if (taskPriority.ordinal() >= newPriority.ordinal()) {
+ return;
+ }
+
2018-10-28 15:40:30 +01:00
+ this.taskPriority = newPriority;
2018-10-06 06:02:09 +02:00
+ PriorityQueuedExecutor.PendingTask<Void> loadTask = this.loadTask;
+ PriorityQueuedExecutor.PendingTask<Chunk> genTask = this.genTask;
+ if (loadTask != null) {
2018-10-28 15:40:30 +01:00
+ loadTask.bumpPriority(newPriority);
2018-10-06 06:02:09 +02:00
+ }
+ if (genTask != null) {
2018-10-28 15:40:30 +01:00
+ genTask.bumpPriority(newPriority);
2018-10-06 06:02:09 +02:00
+ }
+ }
+
+ public synchronized boolean isCancelled() {
+ return requests.get() <= 0;
+ }
+
+ public synchronized void cancel(PendingChunkRequest request) {
+ synchronized (pendingChunks) {
+ if (!request.cancelled.compareAndSet(false, true)) {
+ return;
+ }
+
+ if (requests.decrementAndGet() > 0) {
+ return;
+ }
+
+ boolean c1 = genTask.cancel();
+ boolean c2 = loadTask.cancel();
+ loadTask = null;
+ genTask = null;
+ pendingChunks.remove(key);
+ setStatus(PendingStatus.CANCELLED);
+ }
2018-09-27 00:21:44 +02:00
+ }
+ }
+
+}
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
2019-01-31 05:49:52 +01:00
index 240f59066..e4cf8548d 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
2019-01-01 04:15:55 +01:00
@@ -22,6 +22,52 @@ public class PlayerChunk {
2018-12-17 06:18:06 +01:00
private long i;
private boolean done;
2018-09-27 00:21:44 +02:00
boolean chunkExists; // Paper
+ // Paper start
2018-12-17 06:18:06 +01:00
+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest;
2018-09-27 00:21:44 +02:00
+ private java.util.function.Consumer<Chunk> chunkLoadedConsumer = chunk -> {
2018-10-06 06:02:09 +02:00
+ chunkRequest = null;
2018-09-27 00:21:44 +02:00
+ PlayerChunk pChunk = PlayerChunk.this;
+ pChunk.chunk = chunk;
+ markChunkUsed(); // Paper - delay chunk unloads
2018-10-22 01:16:54 +02:00
+ };
2018-09-27 00:21:44 +02:00
+ private boolean markedHigh = false;
+ void checkHighPriority(EntityPlayer player) {
2018-10-01 07:00:09 +02:00
+ if (done || markedHigh || chunk != null) {
2018-09-27 00:21:44 +02:00
+ return;
+ }
+ final double dist = getDistance(player.locX, player.locZ);
+ if (dist > 8) {
+ return;
+ }
+ if (dist >= 3 && getDistance(player, 5) > 3.5) {
+ return;
+ }
+
+ markedHigh = true;
2018-12-17 06:18:06 +01:00
+ playerChunkMap.getWorld().getChunkProvider().bumpPriority(location);
2018-10-06 06:02:09 +02:00
+ if (chunkRequest == null) {
+ requestChunkIfNeeded(PlayerChunkMap.CAN_GEN_CHUNKS.test(player));
+ }
+ }
+ private void requestChunkIfNeeded(boolean flag) {
+ if (chunkRequest == null) {
2018-12-17 06:18:06 +01:00
+ chunkRequest = this.playerChunkMap.getWorld().getChunkProvider().requestChunk(this.location.x, this.location.z, flag, markedHigh, chunkLoadedConsumer);
2018-10-06 06:02:09 +02:00
+ this.chunk = chunkRequest.getChunk(); // Paper)
2018-12-17 06:18:06 +01:00
+ markChunkUsed(); // Paper - delay chunk unloads
+ }
2018-09-27 00:21:44 +02:00
+ }
+ private double getDistance(EntityPlayer player, int inFront) {
+ final float yaw = MathHelper.normalizeYaw(player.yaw);
+ final double x = player.locX + (inFront * Math.cos(Math.toRadians(yaw)));
+ final double z = player.locZ + (inFront * Math.sin(Math.toRadians(yaw)));
+ return getDistance(x, z);
+ }
+
+ private double getDistance(double blockX, double blockZ) {
+ final double x = location.x - ((int)Math.floor(blockX) >> 4);
+ final double z = location.z - ((int)Math.floor(blockZ) >> 4);
+ return Math.sqrt((x * x) + (z * z));
+ }
2018-12-17 06:18:06 +01:00
2019-01-01 04:15:55 +01:00
public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) {
2018-12-17 06:18:06 +01:00
this.playerChunkMap = playerchunkmap;
2019-01-01 04:15:55 +01:00
@@ -29,13 +75,17 @@ public class PlayerChunk {
2018-12-17 06:18:06 +01:00
ChunkProviderServer chunkproviderserver = playerchunkmap.getWorld().getChunkProvider();
2019-01-01 04:15:55 +01:00
chunkproviderserver.a(i, j);
- this.chunk = chunkproviderserver.getChunkAt(i, j, true, false);
- this.chunkExists = this.chunk != null || org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.hasQueuedChunkLoad(playerChunkMap.getWorld(), i, j); // Paper
+ this.chunk = chunkproviderserver.getChunkAt(i, j, false, false); // Paper
+ this.chunkExists = this.chunk != null || chunkproviderserver.chunkGoingToExists(i, j); // Paper
2018-12-17 06:18:06 +01:00
markChunkUsed(); // Paper - delay chunk unloads
}
// Paper start
2018-10-01 02:34:05 +02:00
private void markChunkUsed() {
2018-10-06 06:02:09 +02:00
+ if (!chunkHasPlayers && chunkRequest != null) {
+ chunkRequest.cancel();
+ chunkRequest = null;
+ }
2018-10-01 02:34:05 +02:00
if (chunk == null) {
2018-10-06 06:02:09 +02:00
return;
}
2019-01-01 04:15:55 +01:00
@@ -65,7 +115,7 @@ public class PlayerChunk {
2018-12-17 06:18:06 +01:00
this.players.add(entityplayer);
2018-09-27 00:21:44 +02:00
if (this.done) {
2018-10-01 07:00:09 +02:00
this.sendChunk(entityplayer);
- }
+ } else checkHighPriority(entityplayer); // Paper
}
}
2019-01-01 04:15:55 +01:00
@@ -90,8 +140,9 @@ public class PlayerChunk {
2018-09-27 00:21:44 +02:00
if (this.chunk != null) {
return true;
} else {
2018-12-17 06:18:06 +01:00
- this.chunk = this.playerChunkMap.getWorld().getChunkProvider().getChunkAt(this.location.x, this.location.z, true, flag);
2018-09-27 00:21:44 +02:00
- markChunkUsed(); // Paper - delay chunk unloads
+ // Paper start - async chunks
2018-10-06 06:02:09 +02:00
+ requestChunkIfNeeded(flag);
2018-09-27 00:21:44 +02:00
+ // Paper end
return this.chunk != null;
}
}
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
2019-01-31 05:49:52 +01:00
index 679488a3c..b7dda8e28 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
2019-01-01 04:15:55 +01:00
@@ -25,10 +25,10 @@ public class PlayerChunkMap {
2018-10-06 06:02:09 +02:00
};
private static final Predicate<EntityPlayer> b = (entityplayer) -> {
return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.getWorldServer().getGameRules().getBoolean("spectatorsGenerateChunks"));
- };
+ }; static final Predicate<EntityPlayer> CAN_GEN_CHUNKS = b; // Paper - OBFHELPER
private final WorldServer world;
private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
2018-10-06 06:15:40 +02:00
- private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
+ private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096); Long2ObjectMap<PlayerChunk> getChunks() { return e; } // Paper - OBFHELPER
private final Set<PlayerChunk> f = Sets.newHashSet();
private final List<PlayerChunk> g = Lists.newLinkedList();
private final List<PlayerChunk> h = Lists.newLinkedList();
2019-01-01 04:15:55 +01:00
@@ -341,7 +341,13 @@ public class PlayerChunkMap {
2018-09-27 00:21:44 +02:00
if (playerchunk != null) {
playerchunk.b(entityplayer);
}
+ } else { // Paper start
+ PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
+ if (playerchunk != null) {
+ playerchunk.checkHighPriority(entityplayer); // Paper
+ }
}
+ // Paper end
}
}
2019-01-01 04:15:55 +01:00
@@ -352,7 +358,11 @@ public class PlayerChunkMap {
2018-09-27 00:21:44 +02:00
// CraftBukkit start - send nearest chunks first
Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
for (ChunkCoordIntPair pair : chunksToLoad) {
- this.c(pair.x, pair.z).a(entityplayer);
+ // Paper start
+ PlayerChunk c = this.c(pair.x, pair.z);
+ c.checkHighPriority(entityplayer);
+ c.a(entityplayer);
+ // Paper end
}
// CraftBukkit end
}
2019-01-01 04:15:55 +01:00
@@ -424,6 +434,15 @@ public class PlayerChunkMap {
2018-10-06 06:15:40 +02:00
}
}
}
+
+ void shutdown() {
+ getChunks().values().forEach(pchunk -> {
+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest = pchunk.chunkRequest;
+ if (chunkRequest != null) {
+ chunkRequest.cancel();
+ }
+ });
+ }
// Paper end
private void e() {
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
2019-01-31 05:49:52 +01:00
index de6dd4fed..da6df06d8 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
2019-01-01 04:15:55 +01:00
@@ -34,7 +34,7 @@ public class RegionLimitedWorldAccess implements GeneratorAccess {
2018-09-27 00:21:44 +02:00
this.d = l;
this.e = i;
this.f = j;
- this.g = world;
+ this.g = world.regionLimited(this); // Paper
this.h = world.getSeed();
this.m = world.getChunkProvider().getChunkGenerator().getSettings();
this.i = world.getSeaLevel();
diff --git a/src/main/java/net/minecraft/server/SchedulerBatch.java b/src/main/java/net/minecraft/server/SchedulerBatch.java
2019-01-31 05:49:52 +01:00
index 8e909d9ca..f214a74a2 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/SchedulerBatch.java
+++ b/src/main/java/net/minecraft/server/SchedulerBatch.java
2019-01-01 04:15:55 +01:00
@@ -10,6 +10,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
2018-09-27 00:21:44 +02:00
private final Scheduler<K, T, R> b;
private boolean c;
private int d = 1000;
+ private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
public SchedulerBatch(Scheduler<K, T, R> scheduler) {
this.b = scheduler;
2019-01-01 04:15:55 +01:00
@@ -19,8 +20,10 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
2018-09-27 00:21:44 +02:00
this.b.b();
}
+ public void startBatch() { b(); } // Paper - OBFHELPER
public void b() {
2018-10-03 05:42:29 +02:00
- if (this.c) {
2018-09-27 00:21:44 +02:00
+ lock.lock(); // Paper
2018-10-03 05:42:29 +02:00
+ if (false && this.c) { // Paper
2018-09-27 00:21:44 +02:00
throw new RuntimeException("Batch already started.");
} else {
2018-10-03 05:42:29 +02:00
this.d = 1000;
2019-01-01 04:15:55 +01:00
@@ -28,6 +31,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
2018-09-27 00:21:44 +02:00
}
}
+ public CompletableFuture<R> add(K k0) { return a(k0); } // Paper - OBFHELPER
2019-01-01 04:15:55 +01:00
public CompletableFuture<R> a(K k0) {
2018-09-27 00:21:44 +02:00
if (!this.c) {
throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
2019-01-01 04:15:55 +01:00
@@ -44,8 +48,14 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
2018-09-27 00:21:44 +02:00
}
}
+ public CompletableFuture<R> executeBatch() { return c(); } // Paper - OBFHELPER
public CompletableFuture<R> c() {
2018-10-03 05:42:29 +02:00
- if (!this.c) {
2018-09-27 00:21:44 +02:00
+ // Paper start
+ if (!lock.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Current thread does not hold the write lock");
+ }
+ try {// Paper end
2018-10-03 05:42:29 +02:00
+ if (false && !this.c) { // Paper
2018-09-27 00:21:44 +02:00
throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
} else {
2018-10-03 05:42:29 +02:00
if (this.d != 1000) {
2019-01-01 04:15:55 +01:00
@@ -55,5 +65,6 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
2018-09-27 00:21:44 +02:00
this.c = false;
return this.b.c();
}
+ } finally { lock.unlock(); } // Paper
}
}
diff --git a/src/main/java/net/minecraft/server/StructurePiece.java b/src/main/java/net/minecraft/server/StructurePiece.java
2019-01-31 05:49:52 +01:00
index d9def7135..945a005e9 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/StructurePiece.java
+++ b/src/main/java/net/minecraft/server/StructurePiece.java
2019-01-01 04:15:55 +01:00
@@ -16,7 +16,7 @@ public abstract class StructurePiece {
2018-09-27 00:21:44 +02:00
private EnumBlockMirror b;
private EnumBlockRotation c;
protected int o;
- private static final Set<Block> d = ImmutableSet.builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build();
+ private static final Set<Block> d = ImmutableSet.<Block>builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build(); // Paper - decompile error
2019-01-01 04:15:55 +01:00
public StructurePiece() {}
@@ -66,9 +66,11 @@ public abstract class StructurePiece {
2018-09-27 00:21:44 +02:00
}
public static StructurePiece a(List<StructurePiece> list, StructureBoundingBox structureboundingbox) {
2019-01-01 04:15:55 +01:00
+ StructurePiece structurepiece; // Paper
+ synchronized (list) { // Paper - synchronize main structure list
Iterator iterator = list.iterator();
- StructurePiece structurepiece;
+ //StructurePiece structurepiece; // Paper - move up
do {
if (!iterator.hasNext()) {
@@ -77,7 +79,7 @@ public abstract class StructurePiece {
structurepiece = (StructurePiece) iterator.next();
} while (structurepiece.d() == null || !structurepiece.d().a(structureboundingbox));
-
+ } // Paper
return structurepiece;
2018-09-27 00:21:44 +02:00
}
2019-01-01 04:15:55 +01:00
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/StructureStart.java b/src/main/java/net/minecraft/server/StructureStart.java
2019-01-31 05:49:52 +01:00
index 284e96710..8b08efe1f 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/StructureStart.java
+++ b/src/main/java/net/minecraft/server/StructureStart.java
2019-01-01 04:15:55 +01:00
@@ -7,7 +7,7 @@ import java.util.Random;
2018-09-27 00:21:44 +02:00
public abstract class StructureStart {
2019-01-01 04:15:55 +01:00
2018-09-27 00:21:44 +02:00
- protected final List<StructurePiece> a = Lists.newArrayList();
+ protected final List<StructurePiece> a = java.util.Collections.synchronizedList(Lists.newArrayList()); // Paper
protected StructureBoundingBox b;
protected int c;
protected int d;
2019-01-01 04:15:55 +01:00
@@ -51,13 +51,14 @@ public abstract class StructureStart {
protected void a(IBlockAccess iblockaccess) {
2018-09-27 00:21:44 +02:00
this.b = StructureBoundingBox.a();
2019-01-01 04:15:55 +01:00
+ synchronized (this.a) { // Paper - synchronize
Iterator iterator = this.a.iterator();
while (iterator.hasNext()) {
StructurePiece structurepiece = (StructurePiece) iterator.next();
2018-09-27 00:21:44 +02:00
this.b.b(structurepiece.d());
- }
+ }} // Paper
}
2019-01-01 04:15:55 +01:00
@@ -126,13 +127,14 @@ public abstract class StructureStart {
2018-09-27 00:21:44 +02:00
int l = k - this.b.e;
2019-01-01 04:15:55 +01:00
2018-09-27 00:21:44 +02:00
this.b.a(0, l, 0);
2019-01-01 04:15:55 +01:00
+ synchronized (this.a) { // Paper - synchronize
Iterator iterator = this.a.iterator();
while (iterator.hasNext()) {
StructurePiece structurepiece = (StructurePiece) iterator.next();
2018-09-27 00:21:44 +02:00
structurepiece.a(0, l, 0);
- }
+ }} // Paper
}
2019-01-01 04:15:55 +01:00
@@ -149,13 +151,14 @@ public abstract class StructureStart {
2018-09-27 00:21:44 +02:00
int i1 = l - this.b.b;
2019-01-01 04:15:55 +01:00
2018-09-27 00:21:44 +02:00
this.b.a(0, i1, 0);
2019-01-01 04:15:55 +01:00
+ synchronized (this.a) {
Iterator iterator = this.a.iterator();
while (iterator.hasNext()) {
StructurePiece structurepiece = (StructurePiece) iterator.next();
2018-09-27 00:21:44 +02:00
structurepiece.a(0, i1, 0);
- }
+ }} // Paper
}
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
2019-02-03 16:34:04 +01:00
index bd0271aeb..c771e657e 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
2019-01-01 04:15:55 +01:00
@@ -41,7 +41,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
2018-09-27 00:21:44 +02:00
import org.bukkit.generator.ChunkGenerator;
// CraftBukkit end
-public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable {
+public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable, Cloneable { // Paper
protected static final Logger e = LogManager.getLogger();
private static final EnumDirection[] a = EnumDirection.values();
2019-01-01 04:15:55 +01:00
@@ -104,6 +104,24 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
2018-09-27 00:21:44 +02:00
protected PersistentVillage villages;
public final MethodProfiler methodProfiler;
public final boolean isClientSide;
+ // Paper start - yes this is hacky as shit
+ RegionLimitedWorldAccess regionLimited;
+ World originalWorld;
+ public World regionLimited(RegionLimitedWorldAccess limitedWorldAccess) {
+ try {
+ World clone = (World) super.clone();
+ clone.regionLimited = limitedWorldAccess;
+ clone.originalWorld = this;
+ return clone;
+ } catch (CloneNotSupportedException e1) {
+ }
+ return null;
+ }
+ ChunkCoordIntPair[] strongholdCoords;
+ final java.util.concurrent.atomic.AtomicBoolean
+ strongholdInit = new java.util.concurrent.atomic.AtomicBoolean
+ (false);
+ // Paper end
public boolean allowMonsters;
public boolean allowAnimals;
private boolean J;
2019-02-03 16:34:04 +01:00
@@ -744,17 +762,42 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
2018-09-27 00:21:44 +02:00
}
- public IBlockData getType(BlockPosition blockposition) {
- // CraftBukkit start - tree generation
+ // Paper - async variant
+ public java.util.concurrent.CompletableFuture<IBlockData> getTypeAsync(BlockPosition blockposition) {
+ int x = blockposition.getX();
+ int z = blockposition.getZ();
if (captureTreeGeneration) {
Iterator<CraftBlockState> it = capturedBlockStates.iterator();
while (it.hasNext()) {
CraftBlockState previous = it.next();
2018-12-23 18:04:13 +01:00
- if (previous.getPosition().equals(blockposition)) {
2018-09-27 00:21:44 +02:00
- return previous.getHandle();
+ if (previous.getX() == x && previous.getY() == blockposition.getY() && previous.getZ() == z) {
+ return java.util.concurrent.CompletableFuture.completedFuture(previous.getHandle());
}
}
}
+ if (blockposition.isInvalidYLocation()) {
+ return java.util.concurrent.CompletableFuture.completedFuture(Blocks.VOID_AIR.getBlockData());
+ } else {
+ java.util.concurrent.CompletableFuture<IBlockData> future = new java.util.concurrent.CompletableFuture<>();
+ ((ChunkProviderServer) chunkProvider).getChunkAt(x << 4, z << 4, true, true, (chunk) -> {
+ future.complete(chunk.getType(blockposition));
+ });
+ return future;
+ }
+ }
+ // Paper end
+
+ public IBlockData getType(BlockPosition blockposition) {
+ // CraftBukkit start - tree generation
+ if (captureTreeGeneration) { // If any of this logic updates, update async variant above
+ Iterator<CraftBlockState> it = capturedBlockStates.iterator();
+ while (it.hasNext()) { // If any of this logic updates, update async variant above
+ CraftBlockState previous = it.next();
+ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) {
+ return previous.getHandle(); // If any of this logic updates, update async variant above
+ }
+ } // If any of this logic updates, update async variant above
+ }
// CraftBukkit end
if (blockposition.isInvalidYLocation()) { // Paper
return Blocks.VOID_AIR.getBlockData();
2019-02-03 16:34:04 +01:00
@@ -1023,6 +1066,11 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
2018-09-27 00:21:44 +02:00
}
public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason
+ // Paper start
+ if (regionLimited != null) {
+ return regionLimited.addEntity(entity, spawnReason);
+ }
+ // Paper end
org.spigotmc.AsyncCatcher.catchOp( "entity add"); // Spigot
if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper
2018-12-17 06:18:06 +01:00
if (!CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
2018-09-27 00:21:44 +02:00
diff --git a/src/main/java/net/minecraft/server/WorldGenStronghold.java b/src/main/java/net/minecraft/server/WorldGenStronghold.java
2019-01-31 05:49:52 +01:00
index 69d8a25bd..d0eaa9e9f 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/WorldGenStronghold.java
+++ b/src/main/java/net/minecraft/server/WorldGenStronghold.java
2019-01-01 04:15:55 +01:00
@@ -10,23 +10,28 @@ import javax.annotation.Nullable;
2018-09-27 00:21:44 +02:00
public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrongholdConfiguration> {
2019-01-01 04:15:55 +01:00
2018-09-27 00:21:44 +02:00
- private boolean b;
- private ChunkCoordIntPair[] c;
- private long d;
+ // Paper start - no shared state
+ //private boolean b;
+ //private ChunkCoordIntPair[] c;
+ //private long d;
+ // Paper end
2019-01-01 04:15:55 +01:00
public WorldGenStronghold() {}
2018-09-27 00:21:44 +02:00
2019-01-01 04:15:55 +01:00
protected boolean a(ChunkGenerator<?> chunkgenerator, Random random, int i, int j) {
2018-09-27 00:21:44 +02:00
- if (this.d != chunkgenerator.getSeed()) {
+ // Paper start
+ /*if (this.d != chunkgenerator.getSeed()) {
this.c();
- }
+ }*/
- if (!this.b) {
+ synchronized (chunkgenerator.getWorld().strongholdInit) {
+ if (chunkgenerator.getWorld().strongholdInit.compareAndSet(false, true)) { // Paper
this.a(chunkgenerator);
- this.b = true;
- }
+ //this.b = true;
+ }} // Paper
+ // Paper end
2019-01-01 04:15:55 +01:00
- ChunkCoordIntPair[] achunkcoordintpair = this.c;
+ ChunkCoordIntPair[] achunkcoordintpair = chunkgenerator.getWorld().strongholdCoords; // Paper
int k = achunkcoordintpair.length;
for (int l = 0; l < k; ++l) {
@@ -41,8 +46,8 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
2018-09-27 00:21:44 +02:00
}
private void c() {
- this.b = false;
- this.c = null;
+ //this.b = false; // Paper
+ //this.c = null; // Paper
}
protected boolean a(GeneratorAccess generatoraccess) {
2019-01-01 04:15:55 +01:00
@@ -76,23 +81,30 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
2018-09-27 00:21:44 +02:00
if (!chunkgenerator.getWorldChunkManager().a(this)) {
return null;
} else {
- if (this.d != world.getSeed()) {
- this.c();
- }
+ // Paper start
+ /*if (this.d != chunkgenerator.getSeed()) {
+ this.c();
+ }*/
- if (!this.b) {
- this.a(chunkgenerator);
- this.b = true;
- }
+ synchronized (chunkgenerator.getWorld().strongholdInit) {
+ if (chunkgenerator.getWorld().strongholdInit.compareAndSet(false, true)) { // Paper
+ this.a(chunkgenerator);
+ //this.b = true;
+ }} // Paper
+ // Paper end
BlockPosition blockposition1 = null;
2019-01-01 04:15:55 +01:00
BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(0, 0, 0);
2018-09-27 00:21:44 +02:00
double d0 = Double.MAX_VALUE;
2019-01-01 04:15:55 +01:00
+ // Paper start
+ /*
ChunkCoordIntPair[] achunkcoordintpair = this.c;
int j = achunkcoordintpair.length;
for (int k = 0; k < j; ++k) {
- ChunkCoordIntPair chunkcoordintpair = achunkcoordintpair[k];
+ */
+ for (ChunkCoordIntPair chunkcoordintpair : world.strongholdCoords) {
+ // Paper end
2018-09-27 00:21:44 +02:00
2019-01-01 04:15:55 +01:00
blockposition_mutableblockposition.c((chunkcoordintpair.x << 4) + 8, 32, (chunkcoordintpair.z << 4) + 8);
double d1 = blockposition_mutableblockposition.n(blockposition);
@@ -111,7 +123,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
2018-09-27 00:21:44 +02:00
}
private void a(ChunkGenerator<?> chunkgenerator) {
- this.d = chunkgenerator.getSeed();
+ //this.d = chunkgenerator.getSeed(); // Paper
2019-01-01 04:15:55 +01:00
List<BiomeBase> list = Lists.newArrayList();
Iterator iterator = IRegistry.BIOME.iterator();
@@ -127,7 +139,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
int j = chunkgenerator.getSettings().f();
int k = chunkgenerator.getSettings().g();
- this.c = new ChunkCoordIntPair[j];
+ ChunkCoordIntPair[] strongholdCoords = chunkgenerator.getWorld().strongholdCoords = new ChunkCoordIntPair[j]; // Paper
int l = 0;
Long2ObjectMap<StructureStart> long2objectmap = chunkgenerator.getStructureStartCache(this);
@@ -137,8 +149,8 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
while (objectiterator.hasNext()) {
StructureStart structurestart = (StructureStart) objectiterator.next();
- if (l < this.c.length) {
- this.c[l++] = new ChunkCoordIntPair(structurestart.e(), structurestart.f());
+ if (l < strongholdCoords.length) { // Paper
+ strongholdCoords[l++] = new ChunkCoordIntPair(structurestart.e(), structurestart.f()); // Paper
2018-09-27 00:21:44 +02:00
}
}
}
2019-01-01 04:15:55 +01:00
@@ -149,11 +161,11 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
double d0 = random.nextDouble() * 3.141592653589793D * 2.0D;
int i1 = long2objectmap.size();
- if (i1 < this.c.length) {
+ if (i1 < strongholdCoords.length) { // Paper
int j1 = 0;
int k1 = 0;
- for (int l1 = 0; l1 < this.c.length; ++l1) {
+ for (int l1 = 0; l1 < strongholdCoords.length; ++l1) { // Paper
double d1 = (double) (4 * i + i * k1 * 6) + (random.nextDouble() - 0.5D) * (double) i * 2.5D;
int i2 = (int) Math.round(Math.cos(d0) * d1);
int j2 = (int) Math.round(Math.sin(d0) * d1);
@@ -165,7 +177,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
2018-09-27 00:21:44 +02:00
}
2019-01-01 04:15:55 +01:00
if (l1 >= i1) {
- this.c[l1] = new ChunkCoordIntPair(i2, j2);
+ strongholdCoords[l1] = new ChunkCoordIntPair(i2, j2); // Paper
2018-09-27 00:21:44 +02:00
}
2019-01-01 04:15:55 +01:00
d0 += 6.283185307179586D / (double) k;
@@ -174,7 +186,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
++k1;
j1 = 0;
k += 2 * k / (k1 + 1);
- k = Math.min(k, this.c.length - l1);
+ k = Math.min(k, strongholdCoords.length - l1); // Paper
d0 += random.nextDouble() * 3.141592653589793D * 2.0D;
2018-09-27 00:21:44 +02:00
}
}
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
2019-01-31 05:49:52 +01:00
index a9fffa544..19ce77c4a 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
2019-01-01 04:15:55 +01:00
@@ -715,7 +715,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
2018-09-27 00:21:44 +02:00
gen = new org.bukkit.craftbukkit.generator.NormalChunkGenerator(this, this.getSeed());
}
- return new ChunkProviderServer(this, ichunkloader, gen, this.server);
+ return com.destroystokyo.paper.PaperConfig.asyncChunks ? new PaperAsyncChunkProvider(this, ichunkloader, gen, this.server) : new ChunkProviderServer(this, ichunkloader, gen, this.server); // Paper - async chunks
// CraftBukkit end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2019-02-07 06:04:29 +01:00
index 8dccf9498..75c4592c2 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2019-01-31 05:49:52 +01:00
@@ -1021,8 +1021,12 @@ public final class CraftServer implements Server {
2018-09-27 00:21:44 +02:00
if (internal.getWorld().getKeepSpawnInMemory()) {
short short1 = internal.paperConfig.keepLoadedRange; // Paper
long i = System.currentTimeMillis();
- for (int j = -short1; j <= short1; j += 16) {
- for (int k = -short1; k <= short1; k += 16) {
+ // Paper start
2018-12-17 06:18:06 +01:00
+ for (ChunkCoordIntPair coords : internal.getChunkProvider().getSpiralOutChunks(internal.getSpawn(), short1 >> 4)) {{
2018-09-27 00:21:44 +02:00
+ int j = coords.x;
+ int k = coords.z;
+ // Paper end
+
long l = System.currentTimeMillis();
if (l < i) {
2019-01-31 05:49:52 +01:00
@@ -1038,7 +1042,7 @@ public final class CraftServer implements Server {
2018-09-27 00:21:44 +02:00
}
BlockPosition chunkcoordinates = internal.getSpawn();
2018-12-17 06:18:06 +01:00
- internal.getChunkProvider().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, true, true);
+ internal.getChunkProvider().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, true, true, c -> {}); // Paper
2018-09-27 00:21:44 +02:00
}
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
2019-01-31 05:49:52 +01:00
index 95fe3a91b..b5a87cfaf 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
2019-01-01 04:15:55 +01:00
@@ -163,6 +163,16 @@ public class CraftWorld implements World {
2018-09-27 04:35:42 +02:00
}
}
+ // Paper start - Async chunk load API
+ @Override
+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(final int x, final int z, final boolean gen) {
2018-12-17 06:18:06 +01:00
+ final ChunkProviderServer cps = this.world.getChunkProvider();
2018-09-27 04:35:42 +02:00
+ java.util.concurrent.CompletableFuture<Chunk> future = new java.util.concurrent.CompletableFuture<>();
2018-09-27 00:21:44 +02:00
+ cps.getChunkAt(x, z, true, gen, chunk -> future.complete(chunk != null ? chunk.bukkitChunk : null));
2018-09-27 04:35:42 +02:00
+ return future;
+ }
+ // Paper end
+
public Chunk getChunkAt(int x, int z) {
2018-12-17 06:18:06 +01:00
return this.world.getChunkProvider().getChunkAt(x, z, true, true).bukkitChunk;
2018-09-27 00:21:44 +02:00
}
2019-01-01 04:15:55 +01:00
@@ -1466,10 +1476,13 @@ public class CraftWorld implements World {
2018-09-27 00:21:44 +02:00
int chunkCoordZ = chunkcoordinates.getZ() >> 4;
// Cycle through the 25x25 Chunks around it to load/unload the chunks.
int radius = world.paperConfig.keepLoadedRange / 16; // Paper
- for (int x = -radius; x <= radius; x++) { // Paper
- for (int z = -radius; z <= radius; z++) { // Paper
+ // Paper start
2018-12-17 06:18:06 +01:00
+ for (ChunkCoordIntPair coords : world.getChunkProvider().getSpiralOutChunks(world.getSpawn(), radius)) {{
2018-09-27 00:21:44 +02:00
+ int x = coords.x;
+ int z = coords.z;
+ // Paper end
if (keepLoaded) {
- loadChunk(chunkCoordX + x, chunkCoordZ + z);
+ getChunkAtAsync(chunkCoordX + x, chunkCoordZ + z, chunk -> {}); // Paper - Async Chunks
} else {
if (isChunkLoaded(chunkCoordX + x, chunkCoordZ + z)) {
unloadChunk(chunkCoordX + x, chunkCoordZ + z);
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
2019-01-31 05:49:52 +01:00
index 054ebc82b..2e0b4de83 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
2019-01-30 14:26:12 +01:00
@@ -82,6 +82,7 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
public static final DamageSource POISON = CraftDamageSource.copyOf(DamageSource.MAGIC);
public static org.bukkit.block.Block blockDamage; // For use in EntityDamageByBlockEvent
public static Entity entityDamage; // For use in EntityDamageByEntityEvent
+ public static boolean isWorldGen(GeneratorAccess world) { return world instanceof net.minecraft.server.RegionLimitedWorldAccess; } // Paper
// helper methods
private static boolean canBuild(CraftWorld world, Player player, int x, int z) {
2019-01-30 14:26:12 +01:00
@@ -474,6 +475,7 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
CraftServer craftServer = (CraftServer) entity.getServer();
CreatureSpawnEvent event = new CreatureSpawnEvent(entity, spawnReason);
+ if (isWorldGen(entityliving.world)) return event; // Paper - do not call during world gen
craftServer.getPluginManager().callEvent(event);
return event;
}
2019-01-30 14:26:12 +01:00
@@ -1128,6 +1130,7 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
}
BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(block.getX(), block.getY(), block.getZ()), cause, igniter);
+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
world.getServer().getPluginManager().callEvent(event);
return event;
}
2019-01-30 14:26:12 +01:00
@@ -1152,6 +1155,7 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
}
BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), cause, bukkitIgniter);
+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
world.getServer().getPluginManager().callEvent(event);
return event;
}
2019-01-30 14:26:12 +01:00
@@ -1359,7 +1363,8 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
public static BlockPhysicsEvent callBlockPhysicsEvent(GeneratorAccess world, BlockPosition blockposition) {
org.bukkit.block.Block block = CraftBlock.at(world, blockposition);
BlockPhysicsEvent event = new BlockPhysicsEvent(block, block.getBlockData());
- world.getMinecraftWorld().getMinecraftServer().server.getPluginManager().callEvent(event);
+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
+ Bukkit.getPluginManager().callEvent(event); // Paper
return event;
}
2019-01-30 14:26:12 +01:00
@@ -1395,6 +1400,7 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
}
EntityPotionEffectEvent event = new EntityPotionEffectEvent((LivingEntity) entity.getBukkitEntity(), bukkitOldEffect, bukkitNewEffect, cause, action, willOverride);
+ if (isWorldGen(entity.world)) return event; // Paper - do not call during world gen
Bukkit.getPluginManager().callEvent(event);
return event;
2019-01-30 14:26:12 +01:00
@@ -1413,6 +1419,7 @@ public class CraftEventFactory {
2018-09-27 00:21:44 +02:00
blockState.setData(block);
BlockFormEvent event = (entity == null) ? new BlockFormEvent(blockState.getBlock(), blockState) : new EntityBlockFormEvent(entity.getBukkitEntity(), blockState.getBlock(), blockState);
+ if (isWorldGen(world)) return true; // Paper - do not call during world gen
world.getServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
2019-01-31 05:49:52 +01:00
index 9c917370a..a299092a5 100644
2018-09-27 00:21:44 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
@@ -21,6 +21,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator<GeneratorSettin
private final WorldServer world;
private final long seed;
private final Random random;
+ public final boolean asyncSupported; // Paper
private final WorldChunkManager chunkManager;
private final WorldGenStronghold strongholdGen = new WorldGenStronghold();
private final GeneratorSettingsDefault settings = new GeneratorSettingsDefault();
@@ -43,6 +44,15 @@ public class CustomChunkGenerator extends InternalChunkGenerator<GeneratorSettin
this.world = (WorldServer) world;
this.generator = generator;
this.seed = seed;
+ // Paper start
+ boolean asyncSupported = false;
+ try {
+ java.lang.reflect.Field asyncSafe = generator.getClass().getDeclaredField("PAPER_ASYNC_SAFE");
+ asyncSafe.setAccessible(true);
+ asyncSupported = asyncSafe.getBoolean(generator);
+ } catch (NoSuchFieldException | IllegalAccessException ignored) {}
+ this.asyncSupported = asyncSupported;
+ // Paper end
this.random = new Random(seed);
this.chunkManager = world.worldProvider.getChunkGenerator().getWorldChunkManager();
--
2018-12-23 18:04:13 +01:00
2.20.1
2018-09-27 00:21:44 +02:00