mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-03 23:07:40 +01:00
80de5ede6f
1) Improve frustum to look more at the near chunks and frontal chunks only instead of 1 large single look up. 2) Delay adding 33 tickets based on view distance and lower their task priority. This will slower roll out the spiral 3) Chunks behind the player have additional delay on loading, favoring chunks in front of the player. This has benefit that if faster traveling, some of the chunks will be cancelled / not loaded. This should reduce pressure on chunk loading, as well as reduce loading/unloading unnecessary chunks while moving.
953 lines
50 KiB
Diff
953 lines
50 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Sat, 11 Apr 2020 03:56:07 -0400
|
|
Subject: [PATCH] Implement Chunk Priority / Urgency System for Chunks
|
|
|
|
Mark chunks that are blocking main thread for world generation as urgent
|
|
|
|
Implements a general priority system so that chunks that are sorted in
|
|
the generator queues can prioritize certain chunks over another.
|
|
|
|
Urgent chunks will jump to the front of the line, ensuring that a
|
|
sync chunk load on an ungenerated chunk does not lag the server for
|
|
a long period of time if the servers generator queues are filled with
|
|
lots of chunks already.
|
|
|
|
This massively reduces the lag spikes from sync chunk gens.
|
|
|
|
Then we further prioritize loading order so nearby chunks have higher
|
|
priority than distant chunks, reducing the pressure a high no tick
|
|
view distance holds on you.
|
|
|
|
Chunks in front of the player have higher priority, to help with
|
|
fast traveling players keep up with their movement.
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
|
|
@@ -0,0 +0,0 @@ import com.destroystokyo.paper.io.PaperFileIOThread;
|
|
import com.destroystokyo.paper.io.IOUtil;
|
|
import com.destroystokyo.paper.io.PrioritizedTaskQueue;
|
|
import com.destroystokyo.paper.io.QueueExecutorThread;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
|
+import net.minecraft.server.ChunkCoordIntPair;
|
|
import net.minecraft.server.ChunkRegionLoader;
|
|
+import net.minecraft.server.ChunkStatus;
|
|
import net.minecraft.server.IAsyncTaskHandler;
|
|
import net.minecraft.server.IChunkAccess;
|
|
import net.minecraft.server.MinecraftServer;
|
|
@@ -0,0 +0,0 @@ public final class ChunkTaskManager {
|
|
}
|
|
|
|
static void dumpChunkInfo(Set<PlayerChunk> seenChunks, PlayerChunk chunkHolder, int x, int z) {
|
|
- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1);
|
|
+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4);
|
|
}
|
|
|
|
static void dumpChunkInfo(Set<PlayerChunk> seenChunks, PlayerChunk chunkHolder, int x, int z, int indent, int maxDepth) {
|
|
@@ -0,0 +0,0 @@ public final class ChunkTaskManager {
|
|
PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getChunkStatus().toString()));
|
|
PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + PlayerChunk.getChunkStatus(chunkHolder.getTicketLevel()));
|
|
PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString()));
|
|
+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Priority - " + chunkHolder.getCurrentPriority());
|
|
+
|
|
+ if (!chunkHolder.neighbors.isEmpty()) {
|
|
+ if (indent >= maxDepth) {
|
|
+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: (Can't show, too deeply nested)");
|
|
+ return;
|
|
+ }
|
|
+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: ");
|
|
+ for (PlayerChunk neighbor : chunkHolder.neighbors.keySet()) {
|
|
+ ChunkStatus status = neighbor.getChunkHolderStatus();
|
|
+ if (status != null && status.isAtLeastStatus(PlayerChunk.getChunkStatus(neighbor.getTicketLevel()))) {
|
|
+ continue;
|
|
+ }
|
|
+ int nx = neighbor.location.x;
|
|
+ int nz = neighbor.location.z;
|
|
+ if (seenChunks.contains(neighbor)) {
|
|
+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)");
|
|
+ continue;
|
|
+ }
|
|
+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":");
|
|
+ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth);
|
|
+ }
|
|
+ }
|
|
+
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
@@ -0,0 +0,0 @@ public class ChunkCoordIntPair {
|
|
return "[" + this.x + ", " + this.z + "]";
|
|
}
|
|
|
|
+ public final BlockPosition asPosition() { return l(); } // Paper - OBFHELPER
|
|
public BlockPosition l() {
|
|
return new BlockPosition(this.x << 4, 0, this.z << 4);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
|
|
@@ -0,0 +0,0 @@ import java.util.concurrent.Executor;
|
|
import javax.annotation.Nullable;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
+import org.spigotmc.AsyncCatcher; // Paper
|
|
|
|
public abstract class ChunkMapDistance {
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
}
|
|
|
|
private static int a(ArraySetSorted<Ticket<?>> arraysetsorted) {
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::getHighestTicketLevel"); // Paper
|
|
return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.b()).b() : PlayerChunkMap.GOLDEN_TICKET + 1;
|
|
}
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
|
|
public boolean a(PlayerChunkMap playerchunkmap) {
|
|
//this.f.a(); // Paper - no longer used
|
|
+ AsyncCatcher.catchOp("DistanceManagerTick");
|
|
this.g.a();
|
|
int i = Integer.MAX_VALUE - this.e.a(Integer.MAX_VALUE);
|
|
boolean flag = i != 0;
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
|
|
// Paper start
|
|
if (!this.pendingChunkUpdates.isEmpty()) {
|
|
+ this.pollingPendingChunkUpdates = true;
|
|
while(!this.pendingChunkUpdates.isEmpty()) {
|
|
PlayerChunk remove = this.pendingChunkUpdates.remove();
|
|
remove.isUpdateQueued = false;
|
|
remove.a(playerchunkmap);
|
|
}
|
|
+ this.pollingPendingChunkUpdates = false;
|
|
// Paper end
|
|
return true;
|
|
} else {
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
return flag;
|
|
}
|
|
}
|
|
+ boolean pollingPendingChunkUpdates = false; // Paper
|
|
|
|
private boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper
|
|
ArraySetSorted<Ticket<?>> arraysetsorted = this.e(i);
|
|
int j = a(arraysetsorted);
|
|
Ticket<?> ticket1 = (Ticket) arraysetsorted.a(ticket); // CraftBukkit - decompile error
|
|
|
|
ticket1.a(this.currentTick);
|
|
- if (ticket.b() < j) {
|
|
+ if (ticket.getTicketLevel() < j) {
|
|
this.e.b(i, ticket.b(), true);
|
|
}
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
}
|
|
|
|
private boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper
|
|
ArraySetSorted<Ticket<?>> arraysetsorted = this.e(i);
|
|
|
|
boolean removed = false; // CraftBukkit
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public static final int PRIORITY_TICKET_LEVEL = 33;
|
|
+ public boolean markUrgent(ChunkCoordIntPair coords) {
|
|
+ return addPriorityTicket(coords, TicketType.URGENT, 30);
|
|
+ }
|
|
+ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) {
|
|
+ priority = Math.min(28, Math.max(1, priority));
|
|
+ return addPriorityTicket(coords, TicketType.PRIORITY, priority);
|
|
+ }
|
|
+
|
|
+ private boolean addPriorityTicket(ChunkCoordIntPair coords, TicketType<ChunkCoordIntPair> ticketType, int priority) {
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket");
|
|
+ long pair = coords.pair();
|
|
+ PlayerChunk updatingChunk = chunkMap.getUpdatingChunk(pair);
|
|
+ if (updatingChunk != null && updatingChunk.priorityBoost >= priority) {
|
|
+ return true;
|
|
+ }
|
|
+ boolean success;
|
|
+ if (!(success = updatePriorityTicket(coords, ticketType, priority))) {
|
|
+ Ticket<ChunkCoordIntPair> ticket = new Ticket<ChunkCoordIntPair>(ticketType, PRIORITY_TICKET_LEVEL, coords);
|
|
+ ticket.priority = priority;
|
|
+ success = this.addTicket(pair, ticket);
|
|
+ }
|
|
+
|
|
+ chunkMap.world.getChunkProvider().tickDistanceManager();
|
|
+ if (updatingChunk == null) {
|
|
+ updatingChunk = chunkMap.getUpdatingChunk(pair);
|
|
+ }
|
|
+ if (updatingChunk != null && updatingChunk.priorityBoost < priority) {
|
|
+ // May not be enqueued, enqueue it if not and tick distance manager
|
|
+ chunkMap.queueHolderUpdate(updatingChunk);
|
|
+ chunkMap.world.getChunkProvider().tickDistanceManager();
|
|
+ }
|
|
+ return success;
|
|
+ }
|
|
+
|
|
+ private boolean updatePriorityTicket(ChunkCoordIntPair coords, TicketType<ChunkCoordIntPair> type, int priority) {
|
|
+ ArraySetSorted<Ticket<?>> tickets = this.tickets.get(coords.pair());
|
|
+ if (tickets == null) {
|
|
+ return false;
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getTicketType() == type) {
|
|
+ // We only support increasing, not decreasing, too complicated
|
|
+ ticket.priority = Math.max(ticket.priority, priority);
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public int getChunkPriority(ChunkCoordIntPair coords) {
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority");
|
|
+ ArraySetSorted<Ticket<?>> tickets = this.tickets.get(coords.pair());
|
|
+ if (tickets == null) {
|
|
+ return 0;
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getTicketType() == TicketType.URGENT) {
|
|
+ return 30;
|
|
+ }
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getTicketType() == TicketType.PRIORITY && ticket.priority > 0) {
|
|
+ return ticket.priority;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ public void clearPriorityTickets(ChunkCoordIntPair coords) {
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::clearPriority");
|
|
+ this.removeTicket(coords.pair(), new Ticket<ChunkCoordIntPair>(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords));
|
|
+ }
|
|
+
|
|
+ public void clearUrgent(ChunkCoordIntPair coords) {
|
|
+ AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent");
|
|
+ this.removeTicket(coords.pair(), new Ticket<ChunkCoordIntPair>(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords));
|
|
+ }
|
|
+ // Paper end
|
|
public <T> boolean addTicketAtLevel(TicketType<T> ticketType, ChunkCoordIntPair chunkcoordintpair, int level, T identifier) {
|
|
return this.addTicket(chunkcoordintpair.pair(), new Ticket<>(ticketType, level, identifier));
|
|
// CraftBukkit end
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
|
|
if (flag1) {
|
|
ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error
|
|
- ChunkMapDistance.this.m.execute(() -> {
|
|
+ // Paper start - smarter ticket delay based on frustum and distance
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersNearby = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i);
|
|
+ // delay ticket add based on distance if > 4, and penalize > 8 more
|
|
+ int delay = j < 4 ? 0 : (j > 8 ? 20 + j * 2 : j);
|
|
+ if (playersNearby != null && j < 4) {
|
|
+ Object[] backingSet = playersNearby.getBackingSet();
|
|
+ BlockPosition chunkPos = new ChunkCoordIntPair(i).asPosition();
|
|
+ double minDist = Double.MAX_VALUE;
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ if (!(backingSet[index] instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ EntityPlayer player = (EntityPlayer) backingSet[index];
|
|
+ BlockPosition pointInFront = player.getPointInFront(3 * 16);
|
|
+
|
|
+ double dist = MCUtil.distanceSq(pointInFront, chunkPos);
|
|
+ if (dist < minDist && dist >= 4*4) {
|
|
+ minDist = dist;
|
|
+ }
|
|
+ }
|
|
+ if (minDist < Double.MAX_VALUE) {
|
|
+ delay += 10 + Math.sqrt(minDist)*3;
|
|
+ }
|
|
+ }
|
|
+ MCUtil.scheduleTask(delay, () -> {
|
|
+ // Paper end
|
|
if (this.c(this.c(i))) {
|
|
ChunkMapDistance.this.addTicket(i, ticket);
|
|
ChunkMapDistance.this.l.add(i);
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
|
|
|
|
});
|
|
}, i, () -> {
|
|
- return j;
|
|
+ int desired = j + chunkMap.getEffectiveViewDistance() >= j ? 20 : 30; // Paper - use less priority for no tick chunks
|
|
+ return Math.min(PlayerChunkMap.GOLDEN_TICKET, desired); // Paper - this is based on distance to player for priority,
|
|
+ // ensure new no tick tickets arent higher priority than high priority tickets...
|
|
}));
|
|
} else {
|
|
ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error
|
|
ChunkMapDistance.this.m.execute(() -> {
|
|
ChunkMapDistance.this.removeTicket(i, ticket);
|
|
+ ChunkMapDistance.this.clearPriorityTickets(new ChunkCoordIntPair(i)); // Paper
|
|
});
|
|
}, i, true));
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) {
|
|
this.chunkMapDistance.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier);
|
|
}
|
|
+
|
|
+ public boolean markUrgent(ChunkCoordIntPair coords) {
|
|
+ return this.chunkMapDistance.markUrgent(coords);
|
|
+ }
|
|
+
|
|
+ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) {
|
|
+ return this.chunkMapDistance.markHighPriority(coords, priority);
|
|
+ }
|
|
+
|
|
+ public void clearPriorityTickets(ChunkCoordIntPair coords) {
|
|
+ this.chunkMapDistance.clearPriorityTickets(coords);
|
|
+ }
|
|
// Paper end
|
|
|
|
@Nullable
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
|
|
if (!completablefuture.isDone()) { // Paper
|
|
// Paper start - async chunk io/loading
|
|
+ ChunkCoordIntPair pair = new ChunkCoordIntPair(x, z);
|
|
+ this.chunkMapDistance.markUrgent(pair);
|
|
this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
|
|
com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z);
|
|
// Paper end
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.serverThreadQueue.awaitTasks(completablefuture::isDone);
|
|
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
|
|
this.world.timings.syncChunkLoad.stopTiming(); // Paper
|
|
+ this.chunkMapDistance.clearPriorityTickets(pair); // Paper
|
|
+ this.chunkMapDistance.clearUrgent(pair); // Paper
|
|
} // Paper
|
|
ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
|
|
return ichunkaccess1;
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
if (flag && !currentlyUnloading) {
|
|
// CraftBukkit end
|
|
this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
|
|
+ if (isUrgent) this.chunkMapDistance.markUrgent(chunkcoordintpair); // Paper
|
|
if (this.a(playerchunk, l)) {
|
|
GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler();
|
|
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
}
|
|
}
|
|
}
|
|
-
|
|
- return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
|
|
+ // Paper start
|
|
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
|
|
+ if (isUrgent) {
|
|
+ future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair));
|
|
+ }
|
|
+ return future;
|
|
+ // Paper end
|
|
}
|
|
|
|
private boolean a(@Nullable PlayerChunk playerchunk, int i) {
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
return this.serverThreadQueue.executeNext();
|
|
}
|
|
|
|
- private boolean tickDistanceManager() {
|
|
+ public boolean tickDistanceManager() { // Paper - public
|
|
boolean flag = this.chunkMapDistance.a(this.playerChunkMap);
|
|
boolean flag1 = this.playerChunkMap.b();
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
this.maxHealthCache = this.getMaxHealth();
|
|
this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
|
|
}
|
|
+ // Paper start
|
|
+ public BlockPosition getPointInFront(double inFront) {
|
|
+ final float yaw = MCUtil.normalizeYaw(this.yaw);
|
|
+ double rads = Math.toRadians(yaw);
|
|
+ final double x = locX() + inFront * Math.cos(rads);
|
|
+ final double z = locZ() + inFront * Math.sin(rads);
|
|
+ return new BlockPosition(x, locY(), z);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
// Yes, this doesn't match Vanilla, but it's the best we can do for now.
|
|
// If this is an issue, PRs are welcome
|
|
@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
if (valid && (!this.isSpectator() || this.world.isLoaded(new BlockPosition(this)))) { // Paper - don't tick dead players that are not in the world currently (pending respawn)
|
|
super.tick();
|
|
}
|
|
+ if (valid && isAlive() && this.ticksLived % 20 == 0) ((WorldServer)world).getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); // Paper
|
|
|
|
for (int i = 0; i < this.inventory.getSize(); ++i) {
|
|
ItemStack itemstack = this.inventory.getItem(i);
|
|
diff --git a/src/main/java/net/minecraft/server/LightEngineThreaded.java b/src/main/java/net/minecraft/server/LightEngineThreaded.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/LightEngineThreaded.java
|
|
+++ b/src/main/java/net/minecraft/server/LightEngineThreaded.java
|
|
@@ -0,0 +0,0 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos();
|
|
|
|
ichunkaccess.b(false);
|
|
- this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> {
|
|
+ // Paper start
|
|
+ IntSupplier defSupplier = this.d.c(chunkcoordintpair.pair());
|
|
+ IntSupplier priority = () -> Math.max(defSupplier.getAsInt() - (flag ? 2 : 0), 1);
|
|
+ // Paper end
|
|
+ this.a(chunkcoordintpair.x, chunkcoordintpair.z, priority, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { // Paper - boost light priority
|
|
ChunkSection[] achunksection = ichunkaccess.getSections();
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
@@ -0,0 +0,0 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
super.b(chunkcoordintpair, false);
|
|
return ichunkaccess;
|
|
}, (runnable) -> {
|
|
- this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.POST_UPDATE, runnable);
|
|
+ this.a(chunkcoordintpair.x, chunkcoordintpair.z, priority, LightEngineThreaded.Update.POST_UPDATE, runnable); // Paper - boost light priority
|
|
});
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -0,0 +0,0 @@ public final class MCUtil {
|
|
chunkData.addProperty("x", playerChunk.location.x);
|
|
chunkData.addProperty("z", playerChunk.location.z);
|
|
chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
|
|
+ chunkData.addProperty("priority", playerChunk.getCurrentPriority());
|
|
chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString());
|
|
chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair()));
|
|
chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
private CompletableFuture<IChunkAccess> chunkSave;
|
|
public int oldTicketLevel;
|
|
private int ticketLevel;
|
|
- private int n;
|
|
- final ChunkCoordIntPair location; // Paper - private -> package
|
|
+ volatile int n; public final int getCurrentPriority() { return n; } // Paper - OBFHELPER - make volatile since this is concurrently accessed
|
|
+ public final ChunkCoordIntPair location; // Paper - private -> public
|
|
private final short[] dirtyBlocks;
|
|
private int dirtyCount;
|
|
private int r;
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
private boolean hasBeenLoaded;
|
|
|
|
private final PlayerChunkMap chunkMap; // Paper
|
|
+ public WorldServer getWorld() { return chunkMap.world; } // Paper
|
|
|
|
long lastAutoSaveTime; // Paper - incremental autosave
|
|
long inactiveTimeStart; // Paper - incremental autosave
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
return null;
|
|
}
|
|
// Paper end - no-tick view distance
|
|
+ // Paper start - Chunk gen/load priority system
|
|
+ volatile int neighborPriority = -1;
|
|
+ volatile int priorityBoost = 0;
|
|
+ public final java.util.concurrent.ConcurrentHashMap<PlayerChunk, ChunkStatus> neighbors = new java.util.concurrent.ConcurrentHashMap<>();
|
|
+ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Integer> neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
|
|
+
|
|
+ private int getDemandedPriority() {
|
|
+ int priority = neighborPriority; // if we have a neighbor priority, use it
|
|
+ int myPriority = getMyPriority();
|
|
+
|
|
+ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) {
|
|
+ priority = myPriority;
|
|
+ }
|
|
+
|
|
+ return Math.max(1, Math.min(PlayerChunkMap.GOLDEN_TICKET, priority));
|
|
+ }
|
|
+
|
|
+ private int getMyPriority() {
|
|
+ if (priorityBoost == 30) {
|
|
+ return 1; // Urgent - ticket level isn't always 31 so 33-30 = 3
|
|
+ }
|
|
+ int basePriority = ticketLevel - priorityBoost;
|
|
+ if (ticketLevel >= 33 && priorityBoost == 0 && (neighborPriority >= 34 || neighborPriorities.isEmpty())) {
|
|
+ basePriority += 5;
|
|
+ }
|
|
+ return basePriority;
|
|
+ }
|
|
+
|
|
+ private int getNeighborsPriority() {
|
|
+ return neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority();
|
|
+ }
|
|
+
|
|
+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) {
|
|
+ neighbor.setNeighborPriority(this, getNeighborsPriority());
|
|
+ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> {
|
|
+ if (currentWantedStatus == null || !currentWantedStatus.isAtLeastStatus(status)) {
|
|
+ //System.out.println(this + " request " + neighbor + " at " + status + " currently " + currentWantedStatus);
|
|
+ return status;
|
|
+ } else {
|
|
+ //System.out.println(this + " requested " + neighbor + " at " + status + " but thats lower than other wanted status " + currentWantedStatus);
|
|
+ return currentWantedStatus;
|
|
+ }
|
|
+ });
|
|
+
|
|
+ }
|
|
+
|
|
+ public void onNeighborDone(PlayerChunk neighbor, ChunkStatus chunkstatus, IChunkAccess chunk) {
|
|
+ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> {
|
|
+ if (wantedStatus != null && chunkstatus.isAtLeastStatus(wantedStatus)) {
|
|
+ //System.out.println(this + " neighbor done at " + neighbor + " for status " + chunkstatus + " wanted " + wantedStatus);
|
|
+ neighbor.removeNeighborPriority(this);
|
|
+ return null;
|
|
+ } else {
|
|
+ //System.out.println(this + " neighbor finished our previous request at " + neighbor + " for status " + chunkstatus + " but we now want instead " + wantedStatus);
|
|
+ return wantedStatus;
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private void removeNeighborPriority(PlayerChunk requester) {
|
|
+ synchronized (neighborPriorities) {
|
|
+ neighborPriorities.remove(requester.location.pair());
|
|
+ recalcNeighborPriority();
|
|
+ }
|
|
+ checkPriority();
|
|
+ }
|
|
+
|
|
+
|
|
+ private void setNeighborPriority(PlayerChunk requester, int priority) {
|
|
+ synchronized (neighborPriorities) {
|
|
+ neighborPriorities.put(requester.location.pair(), Integer.valueOf(priority));
|
|
+ recalcNeighborPriority();
|
|
+ }
|
|
+ checkPriority();
|
|
+ }
|
|
+
|
|
+ private void recalcNeighborPriority() {
|
|
+ neighborPriority = -1;
|
|
+ if (!neighborPriorities.isEmpty()) {
|
|
+ synchronized (neighborPriorities) {
|
|
+ for (Integer neighbor : neighborPriorities.values()) {
|
|
+ if (neighbor < neighborPriority || neighborPriority == -1) {
|
|
+ neighborPriority = neighbor;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ private void checkPriority() {
|
|
+ if (getCurrentPriority() != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this);
|
|
+ }
|
|
+
|
|
+ public final double getDistanceFromPointInFront(EntityPlayer player, int dist) {
|
|
+ int inFront = dist * 16;
|
|
+ final float yaw = MCUtil.normalizeYaw(player.yaw);
|
|
+ double rads = Math.toRadians(yaw);
|
|
+ final double x = player.locX() + inFront * Math.cos(rads);
|
|
+ final double z = player.locZ() + inFront * Math.sin(rads);
|
|
+ return getDistance(x, z);
|
|
+ }
|
|
+
|
|
+ public final double getDistance(EntityPlayer player) {
|
|
+ return getDistance(player.locX(), player.locZ());
|
|
+ }
|
|
+ public final double getDistance(double blockX, double blockZ) {
|
|
+ int cx = MCUtil.fastFloor(blockX) >> 4;
|
|
+ int cz = MCUtil.fastFloor(blockZ) >> 4;
|
|
+ final double x = location.x - cx;
|
|
+ final double z = location.z - cz;
|
|
+ return (x * x) + (z * z);
|
|
+ }
|
|
+
|
|
+ public final double getDistanceFrom(BlockPosition pos) {
|
|
+ return getDistance(pos.getX(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "PlayerChunk{" +
|
|
+ "location=" + location +
|
|
+ ", ticketLevel=" + ticketLevel + "/" + getChunkStatus(this.ticketLevel) +
|
|
+ ", chunkHolderStatus=" + getChunkHolderStatus() +
|
|
+ ", neighborPriority=" + getNeighborsPriority() +
|
|
+ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + getCurrentPriority() +
|
|
+ '}';
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
|
|
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
}
|
|
return null;
|
|
}
|
|
+ public static ChunkStatus getNextStatus(ChunkStatus status) {
|
|
+ if (status == ChunkStatus.FULL) {
|
|
+ return status;
|
|
+ }
|
|
+ return CHUNK_STATUSES.get(status.getStatusIndex() + 1);
|
|
+ }
|
|
+ public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) {
|
|
+ return MCUtil.ensureMain(getStatusFutureUnchecked(chunkstatus));
|
|
+ }
|
|
// Paper end
|
|
|
|
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
return this.n;
|
|
}
|
|
|
|
+ private void setPriority(int i) { d(i); } // Paper - OBFHELPER
|
|
private void d(int i) {
|
|
this.n = i;
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
// CraftBukkit start
|
|
// ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
|
if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
|
- this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
|
+ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main
|
|
Chunk chunk = (Chunk)either.left().orElse(null);
|
|
if (chunk != null) {
|
|
playerchunkmap.callbackExecutor.execute(() -> {
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
if (!flag2 && flag3) {
|
|
// Paper start - cache ticking ready status
|
|
int expectCreateCount = ++this.fullChunkCreateCount;
|
|
- this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> {
|
|
+ this.fullChunkFuture = playerchunkmap.b(this); MCUtil.ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main
|
|
if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
Chunk fullChunk = either.left().get();
|
|
PlayerChunk.this.isFullChunkReady = true;
|
|
fullChunk.playerChunk = PlayerChunk.this;
|
|
+ this.chunkMap.chunkDistanceManager.clearPriorityTickets(location);
|
|
|
|
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
|
|
if (!flag4 && flag5) {
|
|
// Paper start - cache ticking ready status
|
|
- this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> {
|
|
+ this.tickingFuture = playerchunkmap.a(this); MCUtil.ensureMain(this.tickingFuture).thenAccept((either) -> { // Paper - ensure main
|
|
if (either.left().isPresent()) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
Chunk tickingChunk = either.left().get();
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
}
|
|
|
|
// Paper start - cache ticking ready status
|
|
- this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> {
|
|
+ this.entityTickingFuture = playerchunkmap.b(this.location); MCUtil.ensureMain(this.entityTickingFuture).thenAccept((either) -> { // Paper ensureMain
|
|
if (either.left().isPresent()) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
Chunk entityTickingChunk = either.left().get();
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
|
|
this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
}
|
|
-
|
|
- this.w.a(this.location, this::k, this.ticketLevel, this::d);
|
|
+ // Paper start - raise IO/load priority if priority changes, use our preferred priority
|
|
+ priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location);
|
|
+ int priority = getDemandedPriority();
|
|
+ if (getCurrentPriority() > priority) {
|
|
+ int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY;
|
|
+ if (priority <= 10) {
|
|
+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
|
|
+ } else if (priority <= 20) {
|
|
+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
|
|
+ }
|
|
+ chunkMap.world.asyncChunkTaskManager.raisePriority(location.x, location.z, ioPriority);
|
|
+ }
|
|
+ if (getCurrentPriority() != priority) {
|
|
+ this.w.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority
|
|
+ int neighborsPriority = getNeighborsPriority();
|
|
+ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority));
|
|
+ }
|
|
+ // Paper end
|
|
this.oldTicketLevel = this.ticketLevel;
|
|
// CraftBukkit start
|
|
// ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
|
|
if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
|
- this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
|
+ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main
|
|
Chunk chunk = (Chunk)either.left().orElse(null);
|
|
if (chunk != null) {
|
|
playerchunkmap.callbackExecutor.execute(() -> {
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
|
|
public interface c {
|
|
|
|
+ default void changePriority(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer) { a(chunkcoordintpair, intsupplier, i, intconsumer); } // Paper - OBFHELPER
|
|
void a(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -0,0 +0,0 @@ import org.apache.commons.lang3.mutable.MutableBoolean;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
import org.bukkit.entity.Player; // CraftBukkit
|
|
+import org.spigotmc.AsyncCatcher;
|
|
|
|
public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
@Override
|
|
public void execute(Runnable runnable) {
|
|
+ AsyncCatcher.catchOp("Callback Executor execute");
|
|
if (queued == null) {
|
|
queued = new java.util.ArrayDeque<>();
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
@Override
|
|
public void run() {
|
|
+ AsyncCatcher.catchOp("Callback Executor run");
|
|
if (queued == null) {
|
|
return;
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
|
(EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ checkHighPriorityChunks(player);
|
|
if (newState.size() != 1) {
|
|
return;
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
|
|
PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
|
|
- });
|
|
+ PlayerChunkMap.this.world.getChunkProvider().clearPriorityTickets(chunkPos);
|
|
+ }, (player, prevPos, newPos) -> checkHighPriorityChunks(player));
|
|
this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
|
|
this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
|
(EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
});
|
|
// Paper end - no-tick view distance
|
|
}
|
|
+ // Paper start - Chunk Prioritization
|
|
+ private static final int[][] neighborMatrix = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};
|
|
+ public void queueHolderUpdate(PlayerChunk playerchunk) {
|
|
+ Runnable runnable = () -> {
|
|
+ if (isUnloading(playerchunk)) {
|
|
+ return; // unloaded
|
|
+ }
|
|
+ chunkDistanceManager.pendingChunkUpdates.add(playerchunk);
|
|
+ if (!chunkDistanceManager.pollingPendingChunkUpdates) {
|
|
+ world.getChunkProvider().tickDistanceManager();
|
|
+ }
|
|
+ };
|
|
+ if (MCUtil.isMainThread()) {
|
|
+ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks...
|
|
+ runnable.run();
|
|
+ } else {
|
|
+ executor.execute(runnable);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean isUnloading(PlayerChunk playerchunk) {
|
|
+ return playerchunk == null || MCUtil.getChunkStatus(playerchunk) == null || unloadQueue.contains(playerchunk.location.pair());
|
|
+ }
|
|
+
|
|
+ public void checkHighPriorityChunks(EntityPlayer player) {
|
|
+ BlockPosition front2 = player.getPointInFront(16*2);
|
|
+ BlockPosition front4 = player.getPointInFront(16*4);
|
|
+ BlockPosition front6 = player.getPointInFront(16*6);
|
|
+ int viewDistance = getLoadViewDistance();
|
|
+ int maxDistSq = (viewDistance * 16) * (viewDistance * 16);
|
|
+
|
|
+ // Prioritize Frustum near 2
|
|
+ int dist3Sq = 3 * 3;
|
|
+ MCUtil.getSpiralOutChunks(front2, Math.min(5, viewDistance)).forEach(coord -> {
|
|
+ PlayerChunk chunk = getUpdatingChunk(coord.pair());
|
|
+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return;
|
|
+
|
|
+ double dist = chunk.getDistanceFrom(front2);
|
|
+ if (dist <= dist3Sq) {
|
|
+ chunkDistanceManager.markHighPriority(coord, 26);
|
|
+ } else {
|
|
+ chunkDistanceManager.markHighPriority(coord, 24);
|
|
+ }
|
|
+ });
|
|
+ // Prioritize Frustum near 4
|
|
+ if (viewDistance > 4) {
|
|
+ MCUtil.getSpiralOutChunks(front4, Math.min(4, viewDistance)).forEach(coord -> {
|
|
+ PlayerChunk chunk = getUpdatingChunk(coord.pair());
|
|
+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return;
|
|
+
|
|
+ double dist = chunk.getDistanceFrom(front4);
|
|
+ if (dist <= dist3Sq) {
|
|
+ chunkDistanceManager.markHighPriority(coord, 22);
|
|
+ } else {
|
|
+ chunkDistanceManager.markHighPriority(coord, 20);
|
|
+ }
|
|
+
|
|
+ });
|
|
+ }
|
|
+
|
|
+ // Prioritize Frustum far 6
|
|
+ if (viewDistance > 6) {
|
|
+ MCUtil.getSpiralOutChunks(front6, Math.min(4, viewDistance)).forEach(coord -> {
|
|
+ PlayerChunk chunk = getUpdatingChunk(coord.pair());
|
|
+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return;
|
|
+
|
|
+ double dist = chunk.getDistanceFrom(front6);
|
|
+ if (dist <= dist3Sq) {
|
|
+ chunkDistanceManager.markHighPriority(coord, 15);
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ // Prioritize circular near
|
|
+ MCUtil.getSpiralOutChunks(new BlockPosition(player), Math.min(5, viewDistance)).forEach(coord -> {
|
|
+ PlayerChunk chunk = getUpdatingChunk(coord.pair());
|
|
+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return;
|
|
+ double dist = chunk.getDistance(player);
|
|
+
|
|
+ // Prioritize immediate
|
|
+ if (dist <= dist3Sq) {
|
|
+ chunkDistanceManager.markHighPriority(coord, (int) (28 - dist));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Prioritize nearby chunks
|
|
+ if (dist <= (5*5)) {
|
|
+ chunkDistanceManager.markHighPriority(coord, (int) (16 - Math.sqrt(dist*(4D/5D))));
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private boolean shouldSkipPrioritization(ChunkCoordIntPair coord, PlayerChunk chunk, EntityPlayer player, int viewDistance) {
|
|
+ return chunk == null || chunk.isFullChunkReady() || !world.getWorldBorder().isInBounds(coord)
|
|
+ || isUnloading(chunk) || chunk.getDistance(player) > viewDistance;
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public void updatePlayerMobTypeMap(Entity entity) {
|
|
if (!this.world.paperConfig.perPlayerMobSpawns) {
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
List<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> list = Lists.newArrayList();
|
|
int j = chunkcoordintpair.x;
|
|
int k = chunkcoordintpair.z;
|
|
+ PlayerChunk requestingNeighbor = getUpdatingChunk(chunkcoordintpair.pair()); // Paper
|
|
|
|
for (int l = -i; l <= i; ++l) {
|
|
for (int i1 = -i; i1 <= i; ++i1) {
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1);
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this);
|
|
+ // Paper start
|
|
+ if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) {
|
|
+ requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus);
|
|
+ completablefuture.thenAccept(either -> {
|
|
+ requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null));
|
|
+ });
|
|
+ }
|
|
+ // Paper end
|
|
|
|
list.add(completablefuture);
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
};
|
|
|
|
CompletableFuture<NBTTagCompound> chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z);
|
|
+ PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair());
|
|
+ int chunkPriority = playerChunk != null ? playerChunk.getCurrentPriority() : 33;
|
|
+ int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY;
|
|
+
|
|
+ if (chunkPriority <= 10) {
|
|
+ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
|
|
+ } else if (chunkPriority <= 20) {
|
|
+ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
|
|
+ }
|
|
+ boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
|
|
if (chunkSaveFuture != null) {
|
|
- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z,
|
|
- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture);
|
|
- this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY);
|
|
+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture);
|
|
} else {
|
|
- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z,
|
|
- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false);
|
|
+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority);
|
|
}
|
|
+ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority);
|
|
return ret;
|
|
// Paper end
|
|
}
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
long i = playerchunk.i().pair();
|
|
|
|
playerchunk.getClass();
|
|
- mailbox.a(ChunkTaskQueueSorter.a(runnable, i, playerchunk::getTicketLevel)); // CraftBukkit - decompile error
|
|
+ mailbox.a(ChunkTaskQueueSorter.a(runnable, i, () -> 1)); // CraftBukkit - decompile error // Paper - final loads are always urgent!
|
|
});
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/Ticket.java
|
|
+++ b/src/main/java/net/minecraft/server/Ticket.java
|
|
@@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
|
|
private final int b;
|
|
public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER
|
|
private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER
|
|
+ public int priority = 0; // Paper
|
|
|
|
protected Ticket(TicketType<T> tickettype, int i, T t0) {
|
|
this.a = tickettype;
|
|
diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/TicketType.java
|
|
+++ b/src/main/java/net/minecraft/server/TicketType.java
|
|
@@ -0,0 +0,0 @@ public class TicketType<T> {
|
|
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
|
|
public static final TicketType<Long> FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper
|
|
public static final TicketType<Long> ASYNC_LOAD = a("async_load", Long::compareTo); // Paper
|
|
+ public static final TicketType<ChunkCoordIntPair> PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
|
|
+ public static final TicketType<ChunkCoordIntPair> URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
|
|
|
|
public static <T> TicketType<T> a(String s, Comparator<T> comparator) {
|
|
return new TicketType<>(s, comparator, 0L);
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
|
|
return future;
|
|
}
|
|
|
|
+ if (!urgent) {
|
|
+ // if not urgent, at least use a slightly boosted priority
|
|
+ world.getChunkProvider().markHighPriority(new ChunkCoordIntPair(x, z), 1);
|
|
+ }
|
|
return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
|
|
net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null);
|
|
return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk());
|