2503 lines
107 KiB
Diff
2503 lines
107 KiB
Diff
From 3e8f931198ca4a9877415012df19ec4efc8c54b1 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Sat, 21 Jul 2018 16:55:04 -0400
|
|
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
|
|
index 07d7976d21..c25db284ff 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
@@ -376,4 +376,57 @@ public class PaperConfig {
|
|
}
|
|
}
|
|
}
|
|
+
|
|
+ public static boolean asyncChunks = false;
|
|
+ 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) {
|
|
+ asyncChunkLoadThreads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Runtime.getRuntime().availableProcessors() * 1.5);
|
|
+ }
|
|
+
|
|
+ // 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) {}
|
|
+ }
|
|
+
|
|
+ if (!asyncChunks) {
|
|
+ log("Async Chunks: Disabled - Chunks will be managed synchronosuly, and will cause tremendous lag.");
|
|
+ } else {
|
|
+ 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.");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
}
|
|
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
|
|
index 0000000000..8f18c28695
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
|
|
@@ -0,0 +1,347 @@
|
|
+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.
|
|
+ *
|
|
+ * 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>
|
|
+ */
|
|
+@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
|
|
+public class PriorityQueuedExecutor extends AbstractExecutorService {
|
|
+
|
|
+ private final ConcurrentLinkedQueue<Runnable> urgent = new ConcurrentLinkedQueue<>();
|
|
+ private final ConcurrentLinkedQueue<Runnable> high = new ConcurrentLinkedQueue<>();
|
|
+ private final ConcurrentLinkedQueue<Runnable> normal = new ConcurrentLinkedQueue<>();
|
|
+ private final List<Thread> threads = new ArrayList<>();
|
|
+ private final RejectionHandler handler;
|
|
+
|
|
+ private volatile boolean shuttingDown = false;
|
|
+ private volatile boolean shuttingDownNow = false;
|
|
+
|
|
+ public PriorityQueuedExecutor(String name) {
|
|
+ this(name, Math.max(1, Runtime.getRuntime().availableProcessors() - 1));
|
|
+ }
|
|
+
|
|
+ public PriorityQueuedExecutor(String name, int threads) {
|
|
+ this(name, threads, Thread.NORM_PRIORITY, null);
|
|
+ }
|
|
+
|
|
+ public PriorityQueuedExecutor(String name, int threads, int threadPriority) {
|
|
+ this(name, threads, threadPriority, null);
|
|
+ }
|
|
+
|
|
+ public PriorityQueuedExecutor(String name, int threads, RejectionHandler handler) {
|
|
+ this(name, threads, Thread.NORM_PRIORITY, handler);
|
|
+ }
|
|
+
|
|
+ public PriorityQueuedExecutor(String name, int threads, int threadPriority, RejectionHandler handler) {
|
|
+ for (int i = 0; i < threads; i++) {
|
|
+ ExecutorThread thread = new ExecutorThread(this::processQueues);
|
|
+ thread.setDaemon(true);
|
|
+ thread.setName(threads == 1 ? name : name + "-" + (i + 1));
|
|
+ thread.setPriority(threadPriority);
|
|
+ thread.start();
|
|
+ this.threads.add(thread);
|
|
+ }
|
|
+ if (handler == null) {
|
|
+ handler = ABORT_POLICY;
|
|
+ }
|
|
+ this.handler = handler;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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();
|
|
+ }
|
|
+
|
|
+ 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) {
|
|
+ return createPendingTask(run).submit();
|
|
+ }
|
|
+
|
|
+ public PendingTask<Void> submitTask(Runnable run, Priority priority) {
|
|
+ return createPendingTask(run, priority).submit();
|
|
+ }
|
|
+
|
|
+ public <T> PendingTask<T> submitTask(Supplier<T> run) {
|
|
+ return createPendingTask(run).submit();
|
|
+ }
|
|
+
|
|
+ public <T> PendingTask<T> submitTask(Supplier<T> run, Priority priority) {
|
|
+ PendingTask<T> task = createPendingTask(run, priority);
|
|
+ return task.submit();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void execute(@Nonnull Runnable command) {
|
|
+ submitTask(command);
|
|
+ }
|
|
+
|
|
+ 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() {
|
|
+ Runnable run = urgent.poll();
|
|
+ if (run != null) {
|
|
+ return run;
|
|
+ }
|
|
+ run = high.poll();
|
|
+ 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) {
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean processUrgentTasks() {
|
|
+ Runnable run;
|
|
+ boolean hadTask = false;
|
|
+ while ((run = getUrgentTask()) != null) {
|
|
+ run.run();
|
|
+ hadTask = true;
|
|
+ }
|
|
+ return hadTask;
|
|
+ }
|
|
+
|
|
+ public enum Priority {
|
|
+ NORMAL, HIGH, URGENT
|
|
+ }
|
|
+
|
|
+ public class ExecutorThread extends Thread {
|
|
+ public ExecutorThread(Runnable runnable) {
|
|
+ super(runnable);
|
|
+ }
|
|
+
|
|
+ public PriorityQueuedExecutor getExecutor() {
|
|
+ return PriorityQueuedExecutor.this;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ public boolean cancel() {
|
|
+ return hasRan.compareAndSet(false, true);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void run() {
|
|
+ if (!hasRan.compareAndSet(false, true)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ future.complete(run.get());
|
|
+ } catch (Throwable e) {
|
|
+ future.completeExceptionally(e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void bumpPriority() {
|
|
+ bumpPriority(Priority.HIGH);
|
|
+ }
|
|
+
|
|
+ public void bumpPriority(Priority newPriority) {
|
|
+ for (;;) {
|
|
+ int current = this.priority.get();
|
|
+ int ordinal = newPriority.ordinal();
|
|
+ if (current >= ordinal || priority.compareAndSet(current, ordinal)) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ if (this.submitted.get() == -1 || this.hasRan.get()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Only resubmit if it hasnt ran yet and has been submitted
|
|
+ submit();
|
|
+ }
|
|
+
|
|
+ public CompletableFuture<T> onDone() {
|
|
+ return future;
|
|
+ }
|
|
+
|
|
+ public PendingTask<T> submit() {
|
|
+ if (shuttingDown) {
|
|
+ handler.onRejection(this, PriorityQueuedExecutor.this);
|
|
+ return this;
|
|
+ }
|
|
+ for (;;) {
|
|
+ final int submitted = this.submitted.get();
|
|
+ final int priority = this.priority.get();
|
|
+ if (submitted == priority) {
|
|
+ return this;
|
|
+ }
|
|
+ if (this.submitted.compareAndSet(submitted, priority)) {
|
|
+ if (priority == Priority.URGENT.ordinal()) {
|
|
+ urgent.add(this);
|
|
+ } else if (priority == Priority.HIGH.ordinal()) {
|
|
+ high.add(this);
|
|
+ } else {
|
|
+ normal.add(this);
|
|
+ }
|
|
+
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ synchronized (PriorityQueuedExecutor.this) {
|
|
+ // Wake up a thread to take this work
|
|
+ PriorityQueuedExecutor.this.notify();
|
|
+ }
|
|
+ return this;
|
|
+ }
|
|
+ }
|
|
+ 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();
|
|
+ };
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
|
|
index 4ae0acbf13..4f64072a7b 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -190,6 +190,7 @@ public class Chunk implements IChunkAccess {
|
|
|
|
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();
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
|
|
index 2021c0d02e..154ab09e0c 100644
|
|
--- 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> {
|
|
}
|
|
|
|
public Chunk put(long i, Chunk chunk) {
|
|
+ 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> {
|
|
}
|
|
|
|
public Chunk put(Long olong, Chunk chunk) {
|
|
- return this.put(olong, chunk);
|
|
+ return MCUtil.ensureMain("Chunk Put", () -> this.put(olong.longValue(), chunk)); // Paper
|
|
}
|
|
|
|
public Chunk remove(long i) {
|
|
- 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
|
|
|
|
public Chunk remove(Object object) {
|
|
- return this.remove((Long) object);
|
|
+ return MCUtil.ensureMain("Chunk Remove", () -> this.remove(((Long) object).longValue())); // Paper
|
|
}
|
|
|
|
public void putAll(Map<? extends Long, ? extends Chunk> map) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 186cfda7e4..781e068770 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -33,12 +33,12 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
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
|
|
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;
|
|
@@ -75,10 +75,77 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
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();
|
|
+
|
|
+ list.add(new ChunkCoordIntPair(blockposition.getX() >> 4, blockposition.getZ() >> 4));
|
|
+ for (int r = 1; r <= radius; r++) {
|
|
+ int x = -r;
|
|
+ int z = r;
|
|
+
|
|
+ // 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 << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4));
|
|
+ list.add(new ChunkCoordIntPair((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4));
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+ // 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
|
|
@@ -86,13 +153,15 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
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
|
|
@@ -150,7 +219,8 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
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();
|
|
|
|
@@ -168,6 +238,7 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
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");
|
|
@@ -289,11 +360,13 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
}
|
|
|
|
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) {
|
|
ChunkProviderServer.a.error("Couldn't stop taskManager", interruptedexception);
|
|
- }
|
|
+ }*/
|
|
+ // Paper end
|
|
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
index d938eb3749..7734712af9 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
@@ -119,7 +119,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
|
|
// CraftBukkit start
|
|
private boolean check(ChunkProviderServer cps, int x, int z) throws IOException {
|
|
if (cps != null) {
|
|
- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
|
|
+ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this is safe
|
|
if (cps.isLoaded(x, z)) {
|
|
return true;
|
|
}
|
|
@@ -204,10 +204,13 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
|
|
}
|
|
}
|
|
|
|
+ private final Object legacyStructureLock = new Object(); // Paper
|
|
+ public void getPersistentStructureLegacy(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection) { this.a(dimensionmanager, persistentcollection); } // Paper
|
|
public void a(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection) {
|
|
if (this.e == null) {
|
|
+ synchronized (legacyStructureLock){ if (this.e == null) { // Paper
|
|
this.e = PersistentStructureLegacy.a(dimensionmanager, persistentcollection);
|
|
- }
|
|
+ } } } // Paper
|
|
|
|
}
|
|
|
|
@@ -385,11 +388,12 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
|
|
}
|
|
};
|
|
} 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() {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
index 2af07ae592..9c6844d441 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
@@ -25,7 +25,17 @@ public class ChunkSection {
|
|
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) {
|
|
return (IBlockData) this.blockIds.a(i, j, k);
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
|
|
index d3898599f8..8f061f5ca3 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
|
|
@@ -17,13 +17,14 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
|
|
private final ChunkGenerator<?> d;
|
|
private final IChunkLoader e;
|
|
private final IAsyncTaskHandler f;
|
|
- 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) {
|
|
ProtoChunk protochunk = (ProtoChunk) scheduler_a.a();
|
|
|
|
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, () -> {
|
|
@@ -47,8 +48,28 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
|
|
protected Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
|
|
IChunkLoader ichunkloader = this.e;
|
|
|
|
- synchronized (this.e) {
|
|
- 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
|
|
ProtoChunk protochunk;
|
|
|
|
try {
|
|
@@ -67,8 +88,18 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
|
|
} 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) {
|
|
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
index 454903a0e7..dcbcb655c5 100644
|
|
--- 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;
|
|
|
|
@@ -21,26 +21,43 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
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();
|
|
+ // 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() {
|
|
|
|
- private void b() {
|
|
- if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) {
|
|
- 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 "));
|
|
- }).collect(Collectors.joining("\n"));
|
|
- CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException());
|
|
- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps");
|
|
-
|
|
- crashreportsystemdetails.a("Thread dumps", (Object) s);
|
|
- throw new ReportedException(crashreport);
|
|
- } else {
|
|
- this.j.lock();
|
|
}
|
|
}
|
|
|
|
+ synchronized void enableLocks() {
|
|
+ ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
|
+ readLock = lock.readLock();
|
|
+ writeLock = lock.writeLock();
|
|
+ }
|
|
+ synchronized void disableLocks() {
|
|
+ readLock = NOOP_LOCK;
|
|
+ writeLock = NOOP_LOCK;
|
|
+ }
|
|
+
|
|
+ private void b() {
|
|
+ writeLock.lock();
|
|
+ }
|
|
private void c() {
|
|
- this.j.unlock();
|
|
+ writeLock.unlock();
|
|
}
|
|
+ // Paper end
|
|
|
|
public DataPaletteBlock(DataPalette<T> datapalette, RegistryBlockID<T> registryblockid, Function<NBTTagCompound, T> function, Function<T, NBTTagCompound> function1, T t0) {
|
|
// Paper start - Anti-Xray - Support default constructor
|
|
@@ -155,9 +172,13 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
}
|
|
|
|
protected T a(int i) {
|
|
- T t0 = this.h.a(this.a.a(i));
|
|
-
|
|
- return t0 == null ? this.g : t0;
|
|
+ try { // Paper start - read lock
|
|
+ readLock.lock();
|
|
+ T object = this.h.a(this.a.a(i)); // Paper - decompile fix
|
|
+ return (T)(object == null ? this.g : object);
|
|
+ } finally {
|
|
+ readLock.unlock();
|
|
+ } // Paper end
|
|
}
|
|
|
|
// 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
|
|
index f5a6387f27..f456850997 100644
|
|
--- a/src/main/java/net/minecraft/server/DefinedStructureManager.java
|
|
+++ b/src/main/java/net/minecraft/server/DefinedStructureManager.java
|
|
@@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger;
|
|
public class DefinedStructureManager implements IResourcePackListener {
|
|
|
|
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
|
|
index 3d90fdb642..d6d0dd6d88 100644
|
|
--- a/src/main/java/net/minecraft/server/Entity.java
|
|
+++ b/src/main/java/net/minecraft/server/Entity.java
|
|
@@ -207,7 +207,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
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
|
|
index 4698ee99f8..431f4ab189 100644
|
|
--- a/src/main/java/net/minecraft/server/IChunkLoader.java
|
|
+++ b/src/main/java/net/minecraft/server/IChunkLoader.java
|
|
@@ -6,6 +6,9 @@ import javax.annotation.Nullable;
|
|
|
|
public interface IChunkLoader {
|
|
|
|
+ void getPersistentStructureLegacy(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection); // Paper
|
|
+ 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
|
|
index 8bb2593aa9..67bb289545 100644
|
|
--- a/src/main/java/net/minecraft/server/MathHelper.java
|
|
+++ b/src/main/java/net/minecraft/server/MathHelper.java
|
|
@@ -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 {
|
|
return Math.floorMod(i, j);
|
|
}
|
|
|
|
+ public static float normalizeYaw(float fx) { return g(fx); } // Paper - OBFHELPER
|
|
public static float g(float f) {
|
|
f %= 360.0F;
|
|
if (f >= 180.0F) {
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index d2ee4e5781..236fbafeb5 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -499,6 +499,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
|
|
|
// 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()) {
|
|
@@ -506,29 +507,24 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
|
}
|
|
|
|
BlockPosition blockposition = worldserver.getSpawn();
|
|
- List<ChunkCoordIntPair> list = Lists.newArrayList();
|
|
+ List<ChunkCoordIntPair> list = worldserver.getChunkProvider().getSpiralOutChunks(blockposition, worldserver.paperConfig.keepLoadedRange >> 4); // Paper
|
|
Set<ChunkCoordIntPair> set = Sets.newConcurrentHashSet();
|
|
|
|
- // 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
|
|
- list.add(new ChunkCoordIntPair(blockposition.getX() + i >> 4, blockposition.getZ() + j >> 4));
|
|
- }
|
|
- } // Paper
|
|
+ // Paper - remove arraylist creation, call spiral above
|
|
if (this.isRunning()) { // Paper
|
|
int expected = list.size(); // Paper
|
|
|
|
-
|
|
- CompletableFuture completablefuture = worldserver.getChunkProvider().a((Iterable) list, (chunk) -> {
|
|
+ CompletableFuture completablefuture = worldserver.getChunkProvider().loadAllChunks(list, (chunk) -> { // Paper
|
|
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);
|
|
+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
|
|
+ completablefuture.get(50L, TimeUnit.MILLISECONDS); // Paper
|
|
} catch (InterruptedException interruptedexception) {
|
|
throw new RuntimeException(interruptedexception);
|
|
} catch (ExecutionException executionexception) {
|
|
@@ -538,11 +534,11 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
@@ -646,6 +642,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
|
if (hasStopped) return;
|
|
hasStopped = true;
|
|
}
|
|
+ PaperAsyncChunkProvider.stop(this); // Paper
|
|
// CraftBukkit end
|
|
MinecraftServer.LOGGER.info("Stopping server");
|
|
MinecraftTimings.stopServer(); // Paper
|
|
@@ -1013,6 +1010,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
|
while ((futuretask = (FutureTask) this.f.poll()) != null) {
|
|
SystemUtils.a(futuretask, MinecraftServer.LOGGER);
|
|
}
|
|
+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
|
|
MinecraftTimings.minecraftSchedulerTimer.stopTiming(); // Paper
|
|
|
|
this.methodProfiler.exitEnter("commandFunctions");
|
|
@@ -1049,6 +1047,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
|
// CraftBukkit - dropTickTime
|
|
for (Iterator iterator = this.getWorlds().iterator(); iterator.hasNext();) {
|
|
WorldServer worldserver = (WorldServer) iterator.next();
|
|
+ PaperAsyncChunkProvider.processMainThreadQueue(worldserver); // Paper
|
|
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
|
|
TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
|
|
i = SystemUtils.getMonotonicNanos();
|
|
diff --git a/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
|
|
new file mode 100644
|
|
index 0000000000..851756d65e
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
|
|
@@ -0,0 +1,658 @@
|
|
+/*
|
|
+ * 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;
|
|
+import java.util.concurrent.ConcurrentLinkedDeque;
|
|
+import java.util.concurrent.atomic.AtomicBoolean;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+import java.util.function.Consumer;
|
|
+
|
|
+@SuppressWarnings("unused")
|
|
+public class PaperAsyncChunkProvider extends ChunkProviderServer {
|
|
+
|
|
+ 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<>();
|
|
+
|
|
+ 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();
|
|
+ this.shouldGenSync = generator instanceof CustomChunkGenerator && !(((CustomChunkGenerator) generator).asyncSupported) || !PaperConfig.asyncChunkGeneration;
|
|
+ this.generationExecutor = PaperConfig.asyncChunkGenThreadPerWorld ? new PriorityQueuedExecutor("PaperChunkGen-" + worldName, shouldGenSync ? 0 : 1, GEN_THREAD_PRIORITY) : SINGLE_GEN_EXECUTOR;
|
|
+ }
|
|
+
|
|
+ private static Priority calculatePriority(boolean isBlockingMain, boolean priority) {
|
|
+ if (isBlockingMain) {
|
|
+ return Priority.URGENT;
|
|
+ }
|
|
+
|
|
+ if (priority) {
|
|
+ return Priority.HIGH;
|
|
+ }
|
|
+
|
|
+ return Priority.NORMAL;
|
|
+ }
|
|
+
|
|
+ static void stop(MinecraftServer server) {
|
|
+ for (WorldServer world : server.getWorlds()) {
|
|
+ world.getPlayerChunkMap().shutdown();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static void processMainThreadQueue(MinecraftServer server) {
|
|
+ for (WorldServer world : server.getWorlds()) {
|
|
+ processMainThreadQueue(world);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static void processMainThreadQueue(World world) {
|
|
+ IChunkProvider chunkProvider = world.getChunkProvider();
|
|
+ if (chunkProvider instanceof PaperAsyncChunkProvider) {
|
|
+ ((PaperAsyncChunkProvider) chunkProvider).processMainThreadQueue();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void processMainThreadQueue() {
|
|
+ processMainThreadQueue((PendingChunk) null);
|
|
+ }
|
|
+ private boolean processMainThreadQueue(PendingChunk pending) {
|
|
+ Runnable run;
|
|
+ boolean hadLoad = false;
|
|
+ while ((run = MAIN_THREAD_QUEUE.poll()) != null) {
|
|
+ run.run();
|
|
+ hadLoad = true;
|
|
+ if (pending != null && pending.hasPosted) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ return hadLoad;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void bumpPriority(ChunkCoordIntPair coords) {
|
|
+ final PendingChunk pending = pendingChunks.get(coords.asLong());
|
|
+ if (pending != null) {
|
|
+ pending.bumpPriority(Priority.HIGH);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @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) {
|
|
+ final long key = ChunkCoordIntPair.asLong(x, z);
|
|
+ final Chunk chunk = this.chunks.get(key);
|
|
+ if (chunk != null || !load) { // return null if we aren't loading
|
|
+ if (consumer != null) {
|
|
+ consumer.accept(chunk);
|
|
+ }
|
|
+ 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) {
|
|
+ return requestChunk(x, z, gen, priority, consumer).getChunk();
|
|
+ }
|
|
+
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Listen for when result is ready
|
|
+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
|
|
+ final PendingChunkRequest request = pending.addListener(future, gen, !loadOnThisThread);
|
|
+
|
|
+ // 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();
|
|
+ }
|
|
+
|
|
+ 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();
|
|
+ }
|
|
+
|
|
+ if (isBlockingMain) {
|
|
+ while (!future.isDone()) {
|
|
+ // We aren't done, obtain lock on queue
|
|
+ synchronized (MAIN_THREAD_QUEUE) {
|
|
+ // We may of received our request now, check it
|
|
+ if (processMainThreadQueue(pending)) {
|
|
+ // If we processed SOMETHING, don't wait
|
|
+ continue;
|
|
+ }
|
|
+ try {
|
|
+ // We got nothing from the queue, wait until something has been added
|
|
+ MAIN_THREAD_QUEUE.wait(1);
|
|
+ } catch (InterruptedException ignored) {
|
|
+ }
|
|
+ }
|
|
+ // Queue has been notified or timed out, process it
|
|
+ processMainThreadQueue(pending);
|
|
+ }
|
|
+ // We should be done AND posted into chunk map now, return it
|
|
+ request.initialReturnChunk = pending.postChunk();
|
|
+ } else if (consumer == null) {
|
|
+ // This is on another thread
|
|
+ request.initialReturnChunk = future.join();
|
|
+ } else {
|
|
+ future.thenAccept((c) -> this.asyncHandler.postToMainThread(() -> consumer.accept(c)));
|
|
+ }
|
|
+
|
|
+ return request;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void processUrgentTasks() {
|
|
+ final PriorityQueuedExecutor executor = PriorityQueuedExecutor.getExecutor();
|
|
+ if (executor != null) {
|
|
+ executor.processUrgentTasks();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
|
|
+ final Iterator<ChunkCoordIntPair> iterator = iterable.iterator();
|
|
+
|
|
+ final List<CompletableFuture<Chunk>> all = new ArrayList<>();
|
|
+ while (iterator.hasNext()) {
|
|
+ final ChunkCoordIntPair chunkcoordintpair = iterator.next();
|
|
+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
|
|
+ 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) {
|
|
+ PendingChunk pendingChunk = pendingChunks.get(ChunkCoordIntPair.asLong(x, z));
|
|
+ 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)
|
|
+ */
|
|
+ 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;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private boolean isLoadThread() {
|
|
+ return EXECUTOR.isCurrentThread();
|
|
+ }
|
|
+
|
|
+ private boolean isGenThread() {
|
|
+ return generationExecutor.isCurrentThread();
|
|
+ }
|
|
+ private boolean isChunkThread() {
|
|
+ return isLoadThread() || isGenThread();
|
|
+ }
|
|
+
|
|
+ private class PendingChunk implements Runnable {
|
|
+ private final int x;
|
|
+ private final int z;
|
|
+ private final long key;
|
|
+ private final long started = System.currentTimeMillis();
|
|
+ private final CompletableFuture<Chunk> loadOnly = new CompletableFuture<>();
|
|
+ private final CompletableFuture<Chunk> generate = new CompletableFuture<>();
|
|
+ private final AtomicInteger requests = new AtomicInteger(0);
|
|
+
|
|
+ private volatile PendingStatus status = PendingStatus.STARTED;
|
|
+ private volatile PriorityQueuedExecutor.PendingTask<Void> loadTask;
|
|
+ private volatile PriorityQueuedExecutor.PendingTask<Chunk> genTask;
|
|
+ private volatile Priority taskPriority;
|
|
+ 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;
|
|
+ taskPriority = priority ? Priority.HIGH : Priority.NORMAL;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ 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() {
|
|
+ synchronized (this) {
|
|
+ if (requests.get() <= 0) {
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ CompletableFuture<Chunk> pending = new CompletableFuture<>();
|
|
+ batchScheduler.startBatch();
|
|
+ batchScheduler.add(new ChunkCoordIntPair(x, z));
|
|
+
|
|
+ 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 (Throwable 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) {
|
|
+ boolean cancelled = requests.get() <= 0;
|
|
+ if (!canGenerate || cancelled) {
|
|
+ if (!cancelled) {
|
|
+ setStatus(PendingStatus.FAIL);
|
|
+ }
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ if (server.isMainThread()) {
|
|
+ postChunk();
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // 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
|
|
+ synchronized (MAIN_THREAD_QUEUE) {
|
|
+ if (this.taskPriority == Priority.URGENT) {
|
|
+ MAIN_THREAD_QUEUE.addFirst(this::postChunk);
|
|
+ } else {
|
|
+ MAIN_THREAD_QUEUE.addLast(this::postChunk);
|
|
+ }
|
|
+ MAIN_THREAD_QUEUE.notify();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ Chunk postChunk() {
|
|
+ if (!server.isMainThread()) {
|
|
+ throw new IllegalStateException("Must post from main");
|
|
+ }
|
|
+ synchronized (this) {
|
|
+ if (hasPosted || requests.get() <= 0) { // if pending is 0, all were cancelled
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ synchronized PendingChunkRequest addListener(CompletableFuture<Chunk> future, boolean gen, boolean autoSubmit) {
|
|
+ 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();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (hasFinished) {
|
|
+ future.complete(chunk);
|
|
+ return new PendingChunkRequest(this);
|
|
+ } else if (gen) {
|
|
+ canGenerate = true;
|
|
+ generate.thenAccept(future::complete);
|
|
+ } else {
|
|
+ if (generating) {
|
|
+ future.complete(null);
|
|
+ return new PendingChunkRequest(this);
|
|
+ } else {
|
|
+ loadOnly.thenAccept(future::complete);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return new PendingChunkRequest(this, gen);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void run() {
|
|
+ try {
|
|
+ if (!loadFinished(loadChunk(x, z))) {
|
|
+ return;
|
|
+ }
|
|
+ } catch (Throwable ex) {
|
|
+ MinecraftServer.LOGGER.error("Couldn't load chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", ex);
|
|
+ if (ex instanceof IOException) {
|
|
+ generateFinished(null);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (shouldGenSync) {
|
|
+ synchronized (this) {
|
|
+ setStatus(PendingStatus.GENERATION_PENDING);
|
|
+ if (this.taskPriority == Priority.URGENT) {
|
|
+ MAIN_THREAD_QUEUE.addFirst(() -> generateFinished(this.generateChunk()));
|
|
+ } else {
|
|
+ MAIN_THREAD_QUEUE.addLast(() -> generateFinished(this.generateChunk()));
|
|
+ }
|
|
+
|
|
+ }
|
|
+ synchronized (MAIN_THREAD_QUEUE) {
|
|
+ MAIN_THREAD_QUEUE.notify();
|
|
+ }
|
|
+ } else {
|
|
+ if (isGenThread()) {
|
|
+ // ideally we should never run into 1 chunk generating another chunk...
|
|
+ // but if we do, let's apply same solution
|
|
+ genTask.run();
|
|
+ } else {
|
|
+ genTask.submit();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void bumpPriority() {
|
|
+ bumpPriority(Priority.HIGH);
|
|
+ }
|
|
+
|
|
+ void bumpPriority(Priority newPriority) {
|
|
+ if (taskPriority.ordinal() >= newPriority.ordinal()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ this.taskPriority = newPriority;
|
|
+ PriorityQueuedExecutor.PendingTask<Void> loadTask = this.loadTask;
|
|
+ PriorityQueuedExecutor.PendingTask<Chunk> genTask = this.genTask;
|
|
+ if (loadTask != null) {
|
|
+ loadTask.bumpPriority(newPriority);
|
|
+ }
|
|
+ if (genTask != null) {
|
|
+ genTask.bumpPriority(newPriority);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
index 240f590666..e4cf8548d3 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
@@ -22,6 +22,52 @@ public class PlayerChunk {
|
|
private long i;
|
|
private boolean done;
|
|
boolean chunkExists; // Paper
|
|
+ // Paper start
|
|
+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest;
|
|
+ private java.util.function.Consumer<Chunk> chunkLoadedConsumer = chunk -> {
|
|
+ chunkRequest = null;
|
|
+ PlayerChunk pChunk = PlayerChunk.this;
|
|
+ pChunk.chunk = chunk;
|
|
+ markChunkUsed(); // Paper - delay chunk unloads
|
|
+ };
|
|
+ private boolean markedHigh = false;
|
|
+ void checkHighPriority(EntityPlayer player) {
|
|
+ if (done || markedHigh || chunk != null) {
|
|
+ return;
|
|
+ }
|
|
+ final double dist = getDistance(player.locX, player.locZ);
|
|
+ if (dist > 8) {
|
|
+ return;
|
|
+ }
|
|
+ if (dist >= 3 && getDistance(player, 5) > 3.5) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ markedHigh = true;
|
|
+ playerChunkMap.getWorld().getChunkProvider().bumpPriority(location);
|
|
+ if (chunkRequest == null) {
|
|
+ requestChunkIfNeeded(PlayerChunkMap.CAN_GEN_CHUNKS.test(player));
|
|
+ }
|
|
+ }
|
|
+ private void requestChunkIfNeeded(boolean flag) {
|
|
+ if (chunkRequest == null) {
|
|
+ chunkRequest = this.playerChunkMap.getWorld().getChunkProvider().requestChunk(this.location.x, this.location.z, flag, markedHigh, chunkLoadedConsumer);
|
|
+ this.chunk = chunkRequest.getChunk(); // Paper)
|
|
+ markChunkUsed(); // Paper - delay chunk unloads
|
|
+ }
|
|
+ }
|
|
+ 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));
|
|
+ }
|
|
|
|
public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) {
|
|
this.playerChunkMap = playerchunkmap;
|
|
@@ -29,13 +75,17 @@ public class PlayerChunk {
|
|
ChunkProviderServer chunkproviderserver = playerchunkmap.getWorld().getChunkProvider();
|
|
|
|
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
|
|
markChunkUsed(); // Paper - delay chunk unloads
|
|
}
|
|
|
|
// Paper start
|
|
private void markChunkUsed() {
|
|
+ if (!chunkHasPlayers && chunkRequest != null) {
|
|
+ chunkRequest.cancel();
|
|
+ chunkRequest = null;
|
|
+ }
|
|
if (chunk == null) {
|
|
return;
|
|
}
|
|
@@ -65,7 +115,7 @@ public class PlayerChunk {
|
|
this.players.add(entityplayer);
|
|
if (this.done) {
|
|
this.sendChunk(entityplayer);
|
|
- }
|
|
+ } else checkHighPriority(entityplayer); // Paper
|
|
|
|
}
|
|
}
|
|
@@ -90,8 +140,9 @@ public class PlayerChunk {
|
|
if (this.chunk != null) {
|
|
return true;
|
|
} else {
|
|
- this.chunk = this.playerChunkMap.getWorld().getChunkProvider().getChunkAt(this.location.x, this.location.z, true, flag);
|
|
- markChunkUsed(); // Paper - delay chunk unloads
|
|
+ // Paper start - async chunks
|
|
+ requestChunkIfNeeded(flag);
|
|
+ // 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
|
|
index 679488a3cf..b7dda8e282 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -25,10 +25,10 @@ public class PlayerChunkMap {
|
|
};
|
|
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();
|
|
- 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();
|
|
@@ -341,7 +341,13 @@ public class PlayerChunkMap {
|
|
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
|
|
}
|
|
}
|
|
|
|
@@ -352,7 +358,11 @@ public class PlayerChunkMap {
|
|
// 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
|
|
}
|
|
@@ -424,6 +434,15 @@ public class PlayerChunkMap {
|
|
}
|
|
}
|
|
}
|
|
+
|
|
+ void shutdown() {
|
|
+ getChunks().values().forEach(pchunk -> {
|
|
+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest = pchunk.chunkRequest;
|
|
+ if (chunkRequest != null) {
|
|
+ chunkRequest.cancel();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
// Paper end
|
|
|
|
private void e() {
|
|
diff --git a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
index de6dd4fed7..da6df06d84 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
@@ -34,7 +34,7 @@ public class RegionLimitedWorldAccess implements GeneratorAccess {
|
|
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
|
|
index 8e909d9caf..f214a74a29 100644
|
|
--- a/src/main/java/net/minecraft/server/SchedulerBatch.java
|
|
+++ b/src/main/java/net/minecraft/server/SchedulerBatch.java
|
|
@@ -10,6 +10,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
|
|
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;
|
|
@@ -19,8 +20,10 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
|
|
this.b.b();
|
|
}
|
|
|
|
+ public void startBatch() { b(); } // Paper - OBFHELPER
|
|
public void b() {
|
|
- if (this.c) {
|
|
+ lock.lock(); // Paper
|
|
+ if (false && this.c) { // Paper
|
|
throw new RuntimeException("Batch already started.");
|
|
} else {
|
|
this.d = 1000;
|
|
@@ -28,6 +31,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
|
|
}
|
|
}
|
|
|
|
+ public CompletableFuture<R> add(K k0) { return a(k0); } // Paper - OBFHELPER
|
|
public CompletableFuture<R> a(K k0) {
|
|
if (!this.c) {
|
|
throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
|
|
@@ -44,8 +48,14 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
|
|
}
|
|
}
|
|
|
|
+ public CompletableFuture<R> executeBatch() { return c(); } // Paper - OBFHELPER
|
|
public CompletableFuture<R> c() {
|
|
- if (!this.c) {
|
|
+ // Paper start
|
|
+ if (!lock.isHeldByCurrentThread()) {
|
|
+ throw new IllegalStateException("Current thread does not hold the write lock");
|
|
+ }
|
|
+ try {// Paper end
|
|
+ if (false && !this.c) { // Paper
|
|
throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
|
|
} else {
|
|
if (this.d != 1000) {
|
|
@@ -55,5 +65,6 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
|
|
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
|
|
index d9def71353..945a005e90 100644
|
|
--- a/src/main/java/net/minecraft/server/StructurePiece.java
|
|
+++ b/src/main/java/net/minecraft/server/StructurePiece.java
|
|
@@ -16,7 +16,7 @@ public abstract class StructurePiece {
|
|
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
|
|
|
|
public StructurePiece() {}
|
|
|
|
@@ -66,9 +66,11 @@ public abstract class StructurePiece {
|
|
}
|
|
|
|
public static StructurePiece a(List<StructurePiece> list, StructureBoundingBox structureboundingbox) {
|
|
+ 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;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/StructureStart.java b/src/main/java/net/minecraft/server/StructureStart.java
|
|
index 284e96710a..8b08efe1f4 100644
|
|
--- a/src/main/java/net/minecraft/server/StructureStart.java
|
|
+++ b/src/main/java/net/minecraft/server/StructureStart.java
|
|
@@ -7,7 +7,7 @@ import java.util.Random;
|
|
|
|
public abstract class StructureStart {
|
|
|
|
- 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;
|
|
@@ -51,13 +51,14 @@ public abstract class StructureStart {
|
|
|
|
protected void a(IBlockAccess iblockaccess) {
|
|
this.b = StructureBoundingBox.a();
|
|
+ synchronized (this.a) { // Paper - synchronize
|
|
Iterator iterator = this.a.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
StructurePiece structurepiece = (StructurePiece) iterator.next();
|
|
|
|
this.b.b(structurepiece.d());
|
|
- }
|
|
+ }} // Paper
|
|
|
|
}
|
|
|
|
@@ -126,13 +127,14 @@ public abstract class StructureStart {
|
|
int l = k - this.b.e;
|
|
|
|
this.b.a(0, l, 0);
|
|
+ synchronized (this.a) { // Paper - synchronize
|
|
Iterator iterator = this.a.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
StructurePiece structurepiece = (StructurePiece) iterator.next();
|
|
|
|
structurepiece.a(0, l, 0);
|
|
- }
|
|
+ }} // Paper
|
|
|
|
}
|
|
|
|
@@ -149,13 +151,14 @@ public abstract class StructureStart {
|
|
int i1 = l - this.b.b;
|
|
|
|
this.b.a(0, i1, 0);
|
|
+ synchronized (this.a) {
|
|
Iterator iterator = this.a.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
StructurePiece structurepiece = (StructurePiece) iterator.next();
|
|
|
|
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
|
|
index 0d45e21573..97a0fbd55c 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -41,7 +41,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
|
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();
|
|
@@ -104,6 +104,24 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
|
|
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;
|
|
@@ -744,17 +762,42 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
|
|
|
|
}
|
|
|
|
- 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();
|
|
- if (previous.getPosition().equals(blockposition)) {
|
|
- 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();
|
|
@@ -1023,6 +1066,11 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
|
|
}
|
|
|
|
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
|
|
if (!CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
|
|
diff --git a/src/main/java/net/minecraft/server/WorldGenStronghold.java b/src/main/java/net/minecraft/server/WorldGenStronghold.java
|
|
index 69d8a25bdf..d0eaa9e9f8 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldGenStronghold.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldGenStronghold.java
|
|
@@ -10,23 +10,28 @@ import javax.annotation.Nullable;
|
|
|
|
public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrongholdConfiguration> {
|
|
|
|
- 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
|
|
|
|
public WorldGenStronghold() {}
|
|
|
|
protected boolean a(ChunkGenerator<?> chunkgenerator, Random random, int i, int j) {
|
|
- 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
|
|
|
|
- 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
|
|
}
|
|
|
|
private void c() {
|
|
- this.b = false;
|
|
- this.c = null;
|
|
+ //this.b = false; // Paper
|
|
+ //this.c = null; // Paper
|
|
}
|
|
|
|
protected boolean a(GeneratorAccess generatoraccess) {
|
|
@@ -76,23 +81,30 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
|
|
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;
|
|
BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(0, 0, 0);
|
|
double d0 = Double.MAX_VALUE;
|
|
+ // 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
|
|
|
|
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
|
|
}
|
|
|
|
private void a(ChunkGenerator<?> chunkgenerator) {
|
|
- this.d = chunkgenerator.getSeed();
|
|
+ //this.d = chunkgenerator.getSeed(); // Paper
|
|
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
|
|
}
|
|
}
|
|
}
|
|
@@ -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
|
|
}
|
|
|
|
if (l1 >= i1) {
|
|
- this.c[l1] = new ChunkCoordIntPair(i2, j2);
|
|
+ strongholdCoords[l1] = new ChunkCoordIntPair(i2, j2); // Paper
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index 0ff3fe03dd..e71a405807 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -80,6 +80,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
|
|
this.P();
|
|
this.Q();
|
|
this.getWorldBorder().a(minecraftserver.au());
|
|
+ MCUtil.scheduleAsyncTask(() -> this.getChunkProvider().chunkLoader.getPersistentStructureLegacy(dimensionmanager, worldMaps)); // Paper
|
|
}
|
|
|
|
public WorldServer i_() {
|
|
@@ -714,7 +715,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
|
|
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
|
|
index d4d8fbb556..af065bd705 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -1022,8 +1022,12 @@ public final class CraftServer implements Server {
|
|
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
|
|
+ for (ChunkCoordIntPair coords : internal.getChunkProvider().getSpiralOutChunks(internal.getSpawn(), short1 >> 4)) {{
|
|
+ int j = coords.x;
|
|
+ int k = coords.z;
|
|
+ // Paper end
|
|
+
|
|
long l = System.currentTimeMillis();
|
|
|
|
if (l < i) {
|
|
@@ -1039,7 +1043,7 @@ public final class CraftServer implements Server {
|
|
}
|
|
|
|
BlockPosition chunkcoordinates = internal.getSpawn();
|
|
- 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
|
|
}
|
|
}
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 0e4455d66e..eacecccfdb 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -163,6 +163,16 @@ public class CraftWorld implements World {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - Async chunk load API
|
|
+ @Override
|
|
+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(final int x, final int z, final boolean gen) {
|
|
+ final ChunkProviderServer cps = this.world.getChunkProvider();
|
|
+ java.util.concurrent.CompletableFuture<Chunk> future = new java.util.concurrent.CompletableFuture<>();
|
|
+ cps.getChunkAt(x, z, true, gen, chunk -> future.complete(chunk != null ? chunk.bukkitChunk : null));
|
|
+ return future;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public Chunk getChunkAt(int x, int z) {
|
|
return this.world.getChunkProvider().getChunkAt(x, z, true, true).bukkitChunk;
|
|
}
|
|
@@ -1466,10 +1476,13 @@ public class CraftWorld implements World {
|
|
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
|
|
+ for (ChunkCoordIntPair coords : world.getChunkProvider().getSpiralOutChunks(world.getSpawn(), radius)) {{
|
|
+ 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
|
|
index 68e30185a2..7905420cac 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
@@ -82,6 +82,7 @@ public class CraftEventFactory {
|
|
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) {
|
|
@@ -474,6 +475,7 @@ public class CraftEventFactory {
|
|
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;
|
|
}
|
|
@@ -1128,6 +1130,7 @@ public class CraftEventFactory {
|
|
}
|
|
|
|
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;
|
|
}
|
|
@@ -1152,6 +1155,7 @@ public class CraftEventFactory {
|
|
}
|
|
|
|
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;
|
|
}
|
|
@@ -1359,7 +1363,8 @@ public class CraftEventFactory {
|
|
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;
|
|
}
|
|
|
|
@@ -1395,6 +1400,7 @@ public class CraftEventFactory {
|
|
}
|
|
|
|
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;
|
|
@@ -1413,6 +1419,7 @@ public class CraftEventFactory {
|
|
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
|
|
index 9e553866eb..04c0adf7c7 100644
|
|
--- 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();
|
|
--
|
|
2.21.0
|
|
|