13
0
geforkt von Mirrors/Paper

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.

This commit also improves single core cpu scenarios in that we will
now automatically disable Async Chunks as well as Minecrafts thread
pool.

It is never recommended to use async chunks on a single CPU as context
switching will be slower than just running it all on main.

This also bumps the number of server worker threads by default too.
Mojang does not utilize the workers in an effecient manner, resulting
in them using barely any sustained CPU.

So give it more workers so more chunks can be processed concurrently

This change also improves urgent chunk loading, so players flying into
unloaded chunks will hurt a little bit less (but still hurt)

Ping #3395 #3363 (Not marking as closed, we need to make prevent moving work)
Dieser Commit ist enthalten in:
Aikar 2020-05-19 04:01:53 -04:00
Ursprung b4f1f6fc59
Commit e14f7e171f
8 geänderte Dateien mit 593 neuen und 320 gelöschten Zeilen

Datei anzeigen

@ -202,8 +202,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ asyncChunks = getBoolean("settings.async-chunks.enable", true);
+ int threads = getInt("settings.async-chunks.threads", -1);
+ int cpus = Runtime.getRuntime().availableProcessors();
+ if (threads <= 0) {
+ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, Runtime.getRuntime().availableProcessors() - 1));
+ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, cpus - 1));
+ }
+ if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) {
+ asyncChunks = false;
+ }
+
+ // Let Shared Host set some limits
@ -3845,6 +3849,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/server/SystemUtils.java b/src/main/java/net/minecraft/server/SystemUtils.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/SystemUtils.java
+++ b/src/main/java/net/minecraft/server/SystemUtils.java
@@ -0,0 +0,0 @@ public class SystemUtils {
i = Integer.getInteger("Paper.WorkerThreadCount", i); // Paper - allow overriding
Object object;
- if (i <= 0) {
+ if (i <= 0 || (Runtime.getRuntime().availableProcessors() == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore"))) { // Paper - disable server worker queue if single core system
object = MoreExecutors.newDirectExecutorService();
} else {
object = new ForkJoinPool(i, (forkjoinpool) -> {
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

Datei anzeigen

@ -0,0 +1,524 @@
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/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 @@ public abstract class ChunkMapDistance {
Ticket<?> ticket1 = (Ticket) arraysetsorted.a(ticket); // CraftBukkit - decompile error
ticket1.a(this.currentTick);
- if (ticket.b() < j) {
+ if (ticket.b() < j || (ticket.getTicketType() == TicketType.PRIORITY && ((Ticket<Integer>) ticket).getObjectReason() < j)) { // Paper - check priority tickets too
this.e.b(i, ticket.b(), true);
}
@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance {
this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0);
}
+ // Paper start
+ public boolean markUrgent(ChunkCoordIntPair coords) {
+ return this.markHighPriority(coords, 30);
+ }
+ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) {
+ priority = Math.min(30, Math.max(1, priority));
+ Ticket<Integer> ticket = new Ticket<Integer>(TicketType.PRIORITY, 31, priority);
+ return this.addTicket(coords.pair(), ticket);
+ }
+ public int getChunkPriority(ChunkCoordIntPair coords) {
+ int priority = 0;
+ ArraySetSorted<Ticket<?>> tickets = this.tickets.get(coords.pair());
+ if (tickets == null) {
+ return priority;
+ }
+ for (Ticket<?> ticket : tickets) {
+ if (ticket.getTicketType() != TicketType.PRIORITY) {
+ continue;
+ }
+ //noinspection unchecked
+ Ticket<Integer> prioTicket = (Ticket<Integer>) ticket;
+ if (prioTicket.getObjectReason() > priority) {
+ priority = prioTicket.getObjectReason();
+ }
+ }
+ return priority;
+ }
+ public void clearPriorityTickets(ChunkCoordIntPair coords) {
+ ArraySetSorted<Ticket<?>> tickets = this.tickets.get(coords.pair());
+ java.util.List<Ticket<?>> toRemove = new java.util.ArrayList<>();
+ if (tickets == null) return;
+ for (Ticket<?> ticket : tickets) {
+ if (ticket.getTicketType() == TicketType.PRIORITY) {
+ toRemove.add(ticket);
+ }
+ }
+ for (Ticket<?> ticket : toRemove) {
+ this.removeTicket(coords.pair(), ticket);
+ }
+
+ }
+ // 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 {
});
}, i, () -> {
- return j;
+ PlayerChunk chunk = chunkMap.getUpdatingChunk(i); // Paper
+ return chunk != null && chunk.getCurrentPriority() < j ? chunk.getCurrentPriority() : j; // Paper
}));
} else {
ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error
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 chunkMapDistance.markUrgent(coords);
+ }
+ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) {
+ return 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.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.clearPriorityTickets(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.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.clearPriorityTickets(chunkcoordintpair));
+ }
+ return future;
+ // Paper end
}
private boolean a(@Nullable PlayerChunk playerchunk, int i) {
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 {
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/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;
+ private int n; public final int getCurrentPriority() { return n; } // Paper - OBFHELPER
final ChunkCoordIntPair location; // Paper - private -> package
private final short[] dirtyBlocks;
private int dirtyCount;
@@ -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;
+ final java.util.concurrent.ConcurrentHashMap<PlayerChunk, Integer> neighbors = new java.util.concurrent.ConcurrentHashMap<>();
+ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Integer> neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
+
+ public int getPreferredPriority() {
+ int priority = neighborPriority; // if we have a neighbor priority, use it
+ int priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location);
+ int basePriority = ticketLevel - priorityBoost;
+
+ if (priority == -1 || priority > basePriority) {
+ if (priorityBoost > 0) {
+ //System.out.println(location + " boost " + (basePriority) + " = " + ticketLevel + " - " + priorityBoost);
+ }
+ priority = basePriority;
+ if (ticketLevel >= 34 && priorityBoost == 0) {
+ priority += 5;
+ }
+ }
+
+
+ return Math.max(1, Math.min(PlayerChunkMap.GOLDEN_TICKET, priority));
+ }
+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) {
+ int currentPriority = getCurrentPriority();
+ if (!neighborPriorities.containsKey(neighbor.location.pair()) && (neighbor.neighborPriority == -1 || neighbor.neighborPriority > currentPriority)) {
+ this.neighbors.put(neighbor, currentPriority);
+ neighbor.setNeighborPriority(this, Math.max(1, currentPriority));
+ }
+ }
+
+ private void setNeighborPriority(PlayerChunk requester, int priority) {
+ if (priority < neighborPriority || neighborPriority == -1) {
+ synchronized (neighborPriorities) {
+ if (priority < neighborPriority || neighborPriority == -1) {
+ neighborPriority = priority;
+ neighborPriorities.put(requester.location.pair(), Integer.valueOf(priority));
+ }
+ }
+ }
+ }
+
+ public void onNeighborsDone() {
+ java.util.List<PlayerChunk> neighbors = new java.util.ArrayList<>(this.neighbors.keySet());
+ this.neighbors.clear();
+ for (PlayerChunk neighbor : neighbors) {
+ synchronized (neighbor.neighborPriorities) {
+ neighbor.neighborPriorities.remove(location.pair());
+ neighbor.recalcNeighborPriority();
+ }
+ }
+ }
+
+ private void recalcNeighborPriority() {
+ neighborPriority = -1;
+ if (!neighborPriorities.isEmpty()) {
+ synchronized (neighborPriorities) {
+ for (Integer neighbor : neighborPriorities.values()) {
+ if (neighbor < neighborPriority || neighborPriority == -1) {
+ neighborPriority = neighbor;
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ // 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);
+ }
// 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) {
+ if (i == n) return; // Paper
this.n = i;
+ // Paper start
+ this.neighbors.keySet().forEach(neighbor -> {
+ if (neighbor.getCurrentPriority() > i) {
+ neighbor.setNeighborPriority(this, i);
+ this.w.changePriority(neighbor.location, neighbor::getCurrentPriority, neighbor.getCurrentPriority(), neighbor::setPriority);
+ }
+ });
+ // Paper end
}
public void a(int i) {
@@ -0,0 +0,0 @@ public class PlayerChunk {
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 {
this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
}
- this.w.a(this.location, this::k, this.ticketLevel, this::d);
+ this.w.a(this.location, this::k, getPreferredPriority(), this::d); // Paper - preferred priority
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.
@@ -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 @@ 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 checkHighPriorityChunks(EntityPlayer player) {
+ MCUtil.getSpiralOutChunks(new BlockPosition(player), Math.min(7, getLoadViewDistance())).forEach(coord -> {
+ PlayerChunk chunk = getUpdatingChunk(coord.pair());
+ if (chunk == null || chunk.isFullChunkReady() || chunk.getTicketLevel() >= 34 ||
+ !world.getWorldBorder().isInBounds(coord)
+ ) {
+ return;
+ }
+
+ double dist = chunk.getDistance(player);
+ // Prioritize immediate
+ if (dist <= 5) {
+ chunkDistanceManager.markHighPriority(coord, (int) (29 - dist));
+ return;
+ }
+ boolean hasNeighbor = false;
+ for (int[] matrix : neighborMatrix) {
+ long neighborKey = MCUtil.getCoordinateKey(coord.x + matrix[0], coord.x + matrix[1]);
+ PlayerChunk neighbor = getUpdatingChunk(neighborKey);
+ if (neighbor != null && neighbor.isFullChunkReady()) {
+ hasNeighbor = true;
+ break;
+ }
+ }
+ if (!hasNeighbor) {
+ return;
+ }
+ // Prioritize Frustum near
+ double distFront1 = chunk.getDistanceFromPointInFront(player, 2);
+ if (distFront1 <= (4*4)) {
+ if (distFront1 <= (2 * 2)) {
+ chunkDistanceManager.markHighPriority(coord, 24);
+ } else {
+ chunkDistanceManager.markHighPriority(coord, 22);
+ }
+ return;
+ }
+ // Prioritize Frustum far
+ double distFront2 = chunk.getDistanceFromPointInFront(player, 4);
+ if (distFront2 <= (3*3)) {
+ if (distFront2 <= (2 * 2)) {
+ chunkDistanceManager.markHighPriority(coord, 23);
+ } else {
+ chunkDistanceManager.markHighPriority(coord, 20);
+ }
+ return;
+ }
+ // Prioritize nearby chunks
+ if (dist <= (5*5)) {
+ chunkDistanceManager.markHighPriority(coord, (int) (16 - Math.sqrt(dist*(4D/5D))));
+ }
+ });
+ }
+ // 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);
+ if (requestingNeighbor != null && requestingNeighbor != playerchunk) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this);
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 {
return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
});
}, (runnable) -> {
+ playerchunk.onNeighborsDone(); // Paper
this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error
});
}
@@ -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, playerchunk::getCurrentPriority)); // CraftBukkit - decompile error // Paper - use priority not ticket level....
});
}
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<Integer> PRIORITY = a("priority", Integer::compareTo, 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 this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
+ CompletableFuture<Chunk> future = 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());
}, MinecraftServer.getServer());
+ if (urgent) {
+ world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
+ }
+ return future;
+
}
// Paper end

Datei anzeigen

@ -1,312 +0,0 @@
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 World Gen
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.
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 {
if (!completablefuture.isDone()) { // Paper
// Paper start - async chunk io/loading
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z));
+ if (playerChunk != null) {
+ playerChunk.markChunkUrgent(chunkstatus);
+ }
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 {
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
this.world.timings.syncChunkLoad.stopTiming(); // Paper
} // Paper
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z));
+ if (playerChunk != null) {
+ playerChunk.clearChunkUrgent();
+ }
ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
return ichunkaccess1;
}, (playerchunk_failure) -> {
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
}
}
}
+ // Paper start
+ if (playerchunk != null && isUrgent) {
+ playerchunk.markChunkUrgent(chunkstatus);
+ }
+ // Paper end
return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
}
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 {
long lastAutoSaveTime; // Paper - incremental autosave
long inactiveTimeStart; // Paper - incremental autosave
+ // Paper start - Chunk gen/load priority system
+ volatile int chunkPriority = 0;
+ volatile boolean isUrgent = false;
+ final java.util.List<PlayerChunk> urgentNeighbors = new java.util.ArrayList<>();
+ volatile PlayerChunk rootUrgentOriginator;
+ volatile PlayerChunk urgentOriginator;
+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) {
+ if (isUrgent && !neighbor.isUrgent && !java.util.Objects.equals(neighbor, rootUrgentOriginator) && !java.util.Objects.equals(neighbor, urgentOriginator)) {
+ synchronized (this.urgentNeighbors) {
+ if (!neighbor.isUrgent) {
+ neighbor.markChunkUrgent(status, this.rootUrgentOriginator, this);
+ this.urgentNeighbors.add(neighbor);
+ }
+ }
+ }
+ }
+
+ public void onNeighborsDone() {
+ List<PlayerChunk> urgentNeighbors;
+ synchronized (this.urgentNeighbors) {
+ urgentNeighbors = new java.util.ArrayList<>(this.urgentNeighbors);
+ this.urgentNeighbors.clear();
+ }
+ for (PlayerChunk urgentNeighbor : urgentNeighbors) {
+ if (urgentNeighbor != null) {
+ urgentNeighbor.clearChunkUrgent(this);
+ }
+ }
+ }
+
+ public void clearChunkUrgent() {
+ clearChunkUrgent(this);
+ }
+ public void clearChunkUrgent(PlayerChunk requester) {
+ if (this.isUrgent && java.util.Objects.equals(requester, this.urgentOriginator)) {
+ this.isUrgent = false;
+ this.urgentOriginator = null;
+ this.rootUrgentOriginator = null;
+ this.onNeighborsDone();
+ }
+ }
+
+ public void markChunkUrgent(ChunkStatus targetStatus) {
+ this.markChunkUrgent(targetStatus, this , this);
+ }
+ public void markChunkUrgent(ChunkStatus targetStatus, PlayerChunk rootUrgentOriginator, PlayerChunk urgentOriginator) {
+ if (!this.isUrgent) {
+ this.rootUrgentOriginator = rootUrgentOriginator;
+ this.urgentOriginator = urgentOriginator;
+ this.isUrgent = true;
+ int x = location.x;
+ int z = location.z;
+ IChunkAccess chunk = getAvailableChunkNow();
+ final ChunkStatus chunkCurrentStatus = chunk == null ? null : chunk.getChunkStatus();
+ final ChunkStatus completedStatus = this.getChunkHolderStatus();
+ final ChunkStatus nextStatus = getNextStatus(completedStatus != null ? completedStatus : ChunkStatus.EMPTY);
+
+ if (chunkCurrentStatus == null || completedStatus == null) {
+ this.chunkMap.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
+ // next status is empty, empty has no neighbours needing loading
+ return;
+ }
+
+ if (!targetStatus.isAtLeastStatus(nextStatus)) {
+ // we don't want a status greater-than the one we already have, don't prioritise these loads - they will get in the way
+ return;
+ }
+
+ // at this point we want a chunk that has a status higher than the one we have already completed
+
+ // does the next status need neighbours at all?
+ final int requiredNeighbours = nextStatus.getNeighborRadius();
+ if (requiredNeighbours <= 0) {
+ // no it doesn't, we're done here. we've already prioritised this chunk, no neighbours need prioritising
+ return;
+ }
+
+ // even though we might want a higher status than targetFinalStatus, we cannot queue neighbours for it - we
+ // instead use the current chunk status in progress (nextCompletedStatus) to ensure we aren't waiting on
+ // unprioritised logic for the next status to complete
+
+ for (int cx = -requiredNeighbours; cx <= requiredNeighbours; ++cx) {
+ for (int cz = -requiredNeighbours; cz <= requiredNeighbours; ++cz) {
+ if (cx == 0 && cz == 0) {
+ continue;
+ }
+ PlayerChunk neighbor = this.chunkMap.getUpdatingChunk(ChunkCoordIntPair.asLong(x + cz, z + cx));
+ if (neighbor == null) {
+ continue;
+ }
+
+ IChunkAccess neighborChunk = neighbor.getAvailableChunkNow();
+ ChunkStatus neededStatus = this.chunkMap.getNeededStatusByRadius(nextStatus, Math.max(Math.abs(cx), Math.abs(cz)));
+ ChunkStatus neighborCurrentStatus = neighborChunk != null ? neighborChunk.getChunkStatus() : ChunkStatus.EMPTY;
+ if (nextStatus == ChunkStatus.LIGHT || !neighborCurrentStatus.isAtLeastStatus(neededStatus)) {
+ // we don't need to gen neighbours if our current chunk's status has already gone through the gen
+ // light is always an exception, no matter what if we go through light we need its neighbours - the light engine requires them
+ this.onNeighborRequest(neighbor, neededStatus);
+ }
+ }
+ }
+ }
+ }
+ // 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());
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
@@ -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);
+ }
// Paper end
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
@@ -0,0 +0,0 @@ public class PlayerChunk {
}
public int k() {
- return this.n;
+ return Math.max(1, this.n - this.chunkPriority - (isUrgent ? 20 : 0)); // Paper - allow modifying priority, subtracts 20 if urgent
}
private void d(int i) {
@@ -0,0 +0,0 @@ public class PlayerChunk {
Chunk fullChunk = either.left().get();
PlayerChunk.this.isFullChunkReady = true;
fullChunk.playerChunk = PlayerChunk.this;
+ this.clearChunkUrgent();
}
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 @@ 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 = this.requestingNeighbor; // 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);
+ if (requestingNeighbor != null) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this);
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());
+ boolean isBlockingMain = playerChunk != null && playerChunk.isUrgent;
+ int priority = isBlockingMain ? com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY : com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_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, isBlockingMain, 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, isBlockingMain);
}
+ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority);
return ret;
// Paper end
}
+ private PlayerChunk requestingNeighbor; // Paper
private CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> b(PlayerChunk playerchunk, ChunkStatus chunkstatus) {
ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
+ PlayerChunk prevNeighbor = requestingNeighbor; // Paper
+ this.requestingNeighbor = playerchunk; // Paper
CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, chunkstatus.f(), (i) -> {
return this.a(chunkstatus, i);
});
+ this.requestingNeighbor = prevNeighbor; // Paper
this.world.getMethodProfiler().c(() -> {
return "chunkGenerate " + chunkstatus.d();
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
});
}, (runnable) -> {
+ playerchunk.onNeighborsDone(); // Paper
this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error
});
}
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}));
}
+ public ChunkStatus getNeededStatusByRadius(ChunkStatus chunkstatus, int i) { return a(chunkstatus, i); } // Paper - OBFHELPER
private ChunkStatus a(ChunkStatus chunkstatus, int i) {
ChunkStatus chunkstatus1;
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> a(PlayerChunk playerchunk) {
ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
+ PlayerChunk prevNeighbor = this.requestingNeighbor; // Paper
+ this.requestingNeighbor = playerchunk; // Paper
CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, 1, (i) -> {
return ChunkStatus.FULL;
});
+ this.requestingNeighbor = prevNeighbor; // Paper
CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture1 = completablefuture.thenApplyAsync((either) -> {
return either.flatMap((list) -> {
Chunk chunk = (Chunk) list.get(list.size() / 2);
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 @@
package org.bukkit.craftbukkit;
+import com.destroystokyo.paper.io.PrioritizedTaskQueue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -0,0 +0,0 @@ public class CraftWorld implements World {
}
}
- return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
+ CompletableFuture<Chunk> future = 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());
}, MinecraftServer.getServer());
+ if (urgent) {
+ world.asyncChunkTaskManager.raisePriority(x, z, PrioritizedTaskQueue.HIGHEST_PRIORITY);
+ }
+ return future;
+
}
// Paper end

Datei anzeigen

@ -1132,6 +1132,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ protected final ChangeCallback<E> addCallback;
+ protected final ChangeCallback<E> removeCallback;
+ protected final ChangeSourceCallback<E> changeSourceCallback;
+
+ public AreaMap() {
+ this(new PooledLinkedHashSets<>());
@ -1143,9 +1144,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback) {
+ this(pooledHashSets, addCallback, removeCallback, null);
+ }
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback, final ChangeSourceCallback<E> changeSourceCallback) {
+ this.pooledHashSets = pooledHashSets;
+ this.addCallback = addCallback;
+ this.removeCallback = removeCallback;
+ this.changeSourceCallback = changeSourceCallback;
+ }
+
+ @Nullable
@ -1208,7 +1213,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+
+ // called after the distance map updates
+ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {}
+ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {
+ if (newPosition != oldPosition && this.changeSourceCallback != null) {
+ this.changeSourceCallback.accept(Object, oldPosition, newPosition);
+ }
+ }
+
+ public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
+ final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance);
@ -1537,6 +1546,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> newState);
+
+ }
+
+ @FunctionalInterface
+ public static interface ChangeSourceCallback<E> {
+ void accept(final E object, final long prevPos, final long newPos);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java
new file mode 100644
@ -1744,7 +1758,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
+ final ChangeCallback<EntityPlayer> removeCallback) {
+ super(pooledHashSets, addCallback, removeCallback);
+ this(pooledHashSets, addCallback, removeCallback, null);
+ }
+
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
+ final ChangeCallback<EntityPlayer> removeCallback, final ChangeSourceCallback<EntityPlayer> changeSourceCallback) {
+ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback);
+ }
+
+ @Override
@ -3467,6 +3486,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return x < (double)truncated ? truncated - 1 : truncated;
+ }
+
+ public static float normalizeYaw(float f) {
+ float f1 = f % 360.0F;
+
+ if (f1 >= 180.0F) {
+ f1 -= 360.0F;
+ }
+
+ if (f1 < -180.0F) {
+ f1 += 360.0F;
+ }
+
+ return f1;
+ }
+
+ public static int fastFloor(float x) {
+ int truncated = (int)x;
+ return x < (double)truncated ? truncated - 1 : truncated;

Datei anzeigen

@ -116,7 +116,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ //noinspection StatementWithEmptyBody
+ while (pollChunkLoadTasks()) {}
+
+ if (System.nanoTime() - lastMidTickChunkTask < 1000000) {
+ if (System.nanoTime() - lastMidTickChunkTask < 200000) {
+ return;
+ }
+
@ -163,7 +163,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ public int midTickChunksTasksRan = 0;
+ private long midTickLastRan = 0;
+ public void midTickLoadChunks() {
+ if (!isMainThread() || System.nanoTime() - midTickLastRan < 250000) {
+ if (!isMainThread() || System.nanoTime() - midTickLastRan < 200000) {
+ // only check once per 0.25ms incase this code is called in a hot method
+ return;
+ }

Datei anzeigen

@ -118,6 +118,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- 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 {
// cached here to avoid a map lookup
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInMobSpawnRange;
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInChunkTickRange;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInTickingRange;
void updateRanges() {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.location);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ this.playersInTickingRange = this.chunkMap.playerViewDistanceTickMap.getObjectsInRange(key);
}
// Paper end - optimise isOutsideOfRange

Datei anzeigen

@ -165,8 +165,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- 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 {
}
// Paper end
long lastAutoSaveTime; // Paper - incremental autosave
long inactiveTimeStart; // Paper - incremental autosave
+ // Paper start - optimise isOutsideOfRange
+ // cached here to avoid a map lookup

Datei anzeigen

@ -13,7 +13,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private static ExecutorService k() {
- int i = MathHelper.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, 7);
+ int i = Math.min(6, Math.max(Runtime.getRuntime().availableProcessors() - 2, 2)); // Paper - use more reasonable default - 2 is hard minimum to avoid using unlimited threads
+ int i = Math.min(8, Math.max(Runtime.getRuntime().availableProcessors() - 2, 3)); // Paper - use more reasonable default - 2 is hard minimum to avoid using unlimited threads
+ i = Integer.getInteger("Paper.WorkerThreadCount", i); // Paper - allow overriding
Object object;
if (i <= 0) {