4d40e87b33
Had to drop some hunks modifying getEntities, as those methods were rewritten by Mojang in 1.17
1192 lines
64 KiB
Diff
1192 lines
64 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.
|
|
|
|
1.17 update note: very big diff skipping for now, still needs to be updated
|
|
|
|
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 18ae2e2b339d357fbe0f6f2b18bc14c0dfe4c222..782301661f739192798ca6ef501b184bea990c5a 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
|
|
@@ -108,7 +108,7 @@ public final class ChunkTaskManager {
|
|
}
|
|
|
|
static void dumpChunkInfo(Set<ChunkHolder> seenChunks, ChunkHolder chunkHolder, int x, int z) {
|
|
- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1);
|
|
+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4);
|
|
}
|
|
|
|
static void dumpChunkInfo(Set<ChunkHolder> seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) {
|
|
@@ -129,6 +129,30 @@ public final class ChunkTaskManager {
|
|
PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString()));
|
|
PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(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.getQueueLevel());
|
|
+
|
|
+ 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 (ChunkHolder neighbor : chunkHolder.neighbors.keySet()) {
|
|
+ ChunkStatus status = neighbor.getChunkHolderStatus();
|
|
+ if (status != null && status.isOrAfter(ChunkHolder.getStatus(neighbor.getTicketLevel()))) {
|
|
+ continue;
|
|
+ }
|
|
+ int nx = neighbor.pos.x;
|
|
+ int nz = neighbor.pos.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/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
index c1db5cc45dbc7dd24a1ef4dbf88a8efb6c7f2d57..7a87258d1725348fe0c343540f6423885a97a560 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -1,5 +1,6 @@
|
|
package net.minecraft.server.level;
|
|
|
|
+import com.destroystokyo.paper.io.PrioritizedTaskQueue;
|
|
import com.mojang.datafixers.util.Either;
|
|
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
|
|
import it.unimi.dsi.fastutil.shorts.ShortSet;
|
|
@@ -60,7 +61,7 @@ public class ChunkHolder {
|
|
private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory;
|
|
public int oldTicketLevel;
|
|
private int ticketLevel;
|
|
- private int queueLevel;
|
|
+ volatile int queueLevel; // Paper - make volatile since this is concurrently accessed
|
|
public final ChunkPos pos; // Paper - package->public
|
|
private boolean hasChangedSections;
|
|
private final ShortSet[] changedBlocksPerSection;
|
|
@@ -75,6 +76,7 @@ public class ChunkHolder {
|
|
|
|
boolean isUpdateQueued = false; // Paper
|
|
private final ChunkMap chunkMap; // Paper
|
|
+ public ServerLevel getWorld() { return chunkMap.level; } // Paper
|
|
// Paper start - no-tick view distance
|
|
public final LevelChunk getSendingChunk() {
|
|
// it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
|
|
@@ -113,7 +115,120 @@ public class ChunkHolder {
|
|
// Paper end - optimise isOutsideOfRange
|
|
long lastAutoSaveTime; // Paper - incremental autosave
|
|
long inactiveTimeStart; // Paper - incremental autosave
|
|
+ // Paper start - Chunk gen/load priority system
|
|
+ volatile int neighborPriority = -1;
|
|
+ volatile int priorityBoost = 0;
|
|
+ public final java.util.concurrent.ConcurrentHashMap<ChunkHolder, 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(Math.max(ticketLevel, ChunkMap.MAX_CHUNK_DISTANCE), priority));
|
|
+ }
|
|
+
|
|
+ private int getMyPriority() {
|
|
+ if (priorityBoost == DistanceManager.URGENT_PRIORITY) {
|
|
+ return 2; // Urgent - ticket level isn't always 31 so 33-30 = 3, but allow 1 more tasks to go below this for dependents
|
|
+ }
|
|
+ return ticketLevel - priorityBoost;
|
|
+ }
|
|
+
|
|
+ private int getNeighborsPriority() {
|
|
+ return (neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority()) + 1;
|
|
+ }
|
|
+
|
|
+ public void onNeighborRequest(ChunkHolder neighbor, ChunkStatus status) {
|
|
+ neighbor.setNeighborPriority(this, getNeighborsPriority());
|
|
+ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> {
|
|
+ if (currentWantedStatus == null || !currentWantedStatus.isOrAfter(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(ChunkHolder neighbor, ChunkStatus chunkstatus, ChunkAccess chunk) {
|
|
+ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> {
|
|
+ if (wantedStatus != null && chunkstatus.isOrAfter(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(ChunkHolder requester) {
|
|
+ synchronized (neighborPriorities) {
|
|
+ neighborPriorities.remove(requester.pos.toLong());
|
|
+ recalcNeighborPriority();
|
|
+ }
|
|
+ checkPriority();
|
|
+ }
|
|
+
|
|
+
|
|
+ private void setNeighborPriority(ChunkHolder requester, int priority) {
|
|
+ synchronized (neighborPriorities) {
|
|
+ neighborPriorities.put(requester.pos.toLong(), 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 (getQueueLevel() != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this);
|
|
+ }
|
|
+
|
|
+ public final double getDistance(ServerPlayer player) {
|
|
+ return getDistance(player.getX(), player.getZ());
|
|
+ }
|
|
+ public final double getDistance(double blockX, double blockZ) {
|
|
+ int cx = net.minecraft.server.MCUtil.fastFloor(blockX) >> 4;
|
|
+ int cz = net.minecraft.server.MCUtil.fastFloor(blockZ) >> 4;
|
|
+ final double x = pos.x - cx;
|
|
+ final double z = pos.z - cz;
|
|
+ return (x * x) + (z * z);
|
|
+ }
|
|
+
|
|
+ public final double getDistanceFrom(BlockPos pos) {
|
|
+ return getDistance(pos.getX(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "PlayerChunk{" +
|
|
+ "location=" + pos +
|
|
+ ", ticketLevel=" + ticketLevel + "/" + getStatus(this.ticketLevel) +
|
|
+ ", chunkHolderStatus=" + getChunkHolderStatus() +
|
|
+ ", neighborPriority=" + getNeighborsPriority() +
|
|
+ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + getQueueLevel() +
|
|
+ '}';
|
|
+ }
|
|
+ // Paper end
|
|
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
|
|
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
|
|
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
@@ -164,7 +279,18 @@ public class ChunkHolder {
|
|
return null;
|
|
}
|
|
// CraftBukkit end
|
|
-
|
|
+ public static ChunkStatus getNextStatus(ChunkStatus status) {
|
|
+ if (status == ChunkStatus.FULL) {
|
|
+ return status;
|
|
+ }
|
|
+ return CHUNK_STATUSES.get(status.getIndex() + 1);
|
|
+ }
|
|
+ public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) {
|
|
+ return ensureMain(getFutureIfPresentUnchecked(chunkstatus));
|
|
+ }
|
|
+ public <T> CompletableFuture<T> ensureMain(CompletableFuture<T> future) {
|
|
+ return future.thenApplyAsync(r -> r, chunkMap.mainInvokingExecutor);
|
|
+ }
|
|
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus leastStatus) {
|
|
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(leastStatus.getIndex());
|
|
|
|
@@ -488,7 +614,7 @@ public class ChunkHolder {
|
|
// CraftBukkit start
|
|
// ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
|
if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
|
+ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main
|
|
LevelChunk chunk = (LevelChunk)either.left().orElse(null);
|
|
if (chunk != null) {
|
|
chunkStorage.callbackExecutor.execute(() -> {
|
|
@@ -553,13 +679,14 @@ public class ChunkHolder {
|
|
this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this);
|
|
this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER);
|
|
// Paper start - cache ticking ready status
|
|
- this.fullChunkFuture.thenAccept(either -> {
|
|
+ ensureMain(this.fullChunkFuture).thenAccept(either -> { // Paper ensureMain
|
|
final Optional<LevelChunk> left = either.left();
|
|
if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
LevelChunk fullChunk = either.left().get();
|
|
ChunkHolder.this.isFullChunkReady = true;
|
|
fullChunk.playerChunk = ChunkHolder.this;
|
|
+ this.chunkMap.getDistanceManager().clearPriorityTickets(pos);
|
|
}
|
|
});
|
|
this.updateChunkToSave(this.fullChunkFuture, "full");
|
|
@@ -583,11 +710,12 @@ public class ChunkHolder {
|
|
this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this);
|
|
this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING);
|
|
// Paper start - cache ticking ready status
|
|
- this.tickingChunkFuture.thenAccept(either -> {
|
|
+ ensureMain(this.tickingChunkFuture).thenAccept(either -> {// Paper - ensureMain
|
|
either.ifLeft(chunk -> {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
- ChunkHolder.this.isTickingReady = true;
|
|
-
|
|
+ LevelChunk fullChunk = either.left().get();
|
|
+ ChunkHolder.this.isFullChunkReady = true;
|
|
+ fullChunk.playerChunk = ChunkHolder.this;
|
|
// Paper start - rewrite ticklistserver
|
|
ChunkHolder.this.chunkMap.level.onChunkSetTicking(ChunkHolder.this.pos.x, ChunkHolder.this.pos.z);
|
|
// Paper end - rewrite ticklistserver
|
|
@@ -613,7 +741,7 @@ public class ChunkHolder {
|
|
this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this.pos);
|
|
this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING);
|
|
// Paper start - cache ticking ready status
|
|
- this.entityTickingChunkFuture.thenAccept(either -> {
|
|
+ ensureMain(this.entityTickingChunkFuture).thenAccept(either -> {// Paper - ensureMain
|
|
either.ifLeft(chunk -> {
|
|
ChunkHolder.this.isEntityTickingReady = true;
|
|
});
|
|
@@ -632,11 +760,29 @@ public class ChunkHolder {
|
|
}
|
|
|
|
this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
|
|
+ // Paper start - raise IO/load priority if priority changes, use our preferred priority
|
|
+ priorityBoost = this.chunkMap.getDistanceManager().getChunkPriority(pos);
|
|
+ int priority = getDemandedPriority();
|
|
+ if (getQueueLevel() > priority) {
|
|
+ int ioPriority = PrioritizedTaskQueue.NORMAL_PRIORITY;
|
|
+ if (priority <= 10) {
|
|
+ ioPriority = PrioritizedTaskQueue.HIGHEST_PRIORITY;
|
|
+ } else if (priority <= 20) {
|
|
+ ioPriority = PrioritizedTaskQueue.HIGH_PRIORITY;
|
|
+ }
|
|
+ chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority);
|
|
+ }
|
|
+ if (getQueueLevel() != priority) {
|
|
+ this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, priority, this::setQueueLevel); // 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.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
|
+ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> {// Paper - ensure main
|
|
LevelChunk chunk = (LevelChunk)either.left().orElse(null);
|
|
if (chunk != null) {
|
|
chunkStorage.callbackExecutor.execute(() -> {
|
|
@@ -714,6 +860,7 @@ public class ChunkHolder {
|
|
@FunctionalInterface
|
|
public interface LevelChangeListener {
|
|
|
|
+ default void changePriority(ChunkPos chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer) { onLevelChange(chunkcoordintpair, intsupplier, i, intconsumer); } // Paper - OBFHELPER
|
|
void onLevelChange(ChunkPos pos, IntSupplier levelGetter, int targetLevel, IntConsumer levelSetter);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 89f73e8a7f5b429c89d051b44ea6c9cbecb97730..08857bd5defc48eb2e91c1dc7c013a90443e9b7d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -144,6 +144,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
public final ServerLevel level;
|
|
private final ThreadedLevelLightEngine lightEngine;
|
|
private final BlockableEventLoop<Runnable> mainThreadExecutor;
|
|
+ final java.util.concurrent.Executor mainInvokingExecutor; // Paper
|
|
public final ChunkGenerator generator;
|
|
private final Supplier<DimensionDataStorage> overworldDataStorage; public final Supplier<DimensionDataStorage> getWorldPersistentDataSupplier() { return this.overworldDataStorage; } // Paper - OBFHELPER
|
|
private final PoiManager poiManager;
|
|
@@ -182,6 +183,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
@Override
|
|
public void execute(Runnable runnable) {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute"); // Paper
|
|
if (this.queue == null) {
|
|
this.queue = new java.util.ArrayDeque<>();
|
|
}
|
|
@@ -190,6 +192,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
@Override
|
|
public void run() {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor run"); // Paper
|
|
if (this.queue == null) {
|
|
return;
|
|
}
|
|
@@ -346,6 +349,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.level = world;
|
|
this.generator = chunkGenerator;
|
|
this.mainThreadExecutor = mainThreadExecutor;
|
|
+ // Paper start
|
|
+ this.mainInvokingExecutor = (run) -> {
|
|
+ if (MCUtil.isMainThread()) {
|
|
+ run.run();
|
|
+ } else {
|
|
+ mainThreadExecutor.execute(run);
|
|
+ }
|
|
+ };
|
|
+ // Paper end
|
|
ProcessorMailbox<Runnable> threadedmailbox = ProcessorMailbox.create(executor, "worldgen");
|
|
|
|
Objects.requireNonNull(mainThreadExecutor);
|
|
@@ -441,6 +453,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
|
(ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
|
+ checkHighPriorityChunks(player);
|
|
if (newState.size() != 1) {
|
|
return;
|
|
}
|
|
@@ -459,7 +472,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
|
|
ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
|
|
- });
|
|
+ ChunkMap.this.level.getChunkSource().clearPriorityTickets(chunkPos);
|
|
+ }, (player, prevPos, newPos) -> {
|
|
+ player.lastHighPriorityChecked = -1; // reset and recheck
|
|
+ checkHighPriorityChunks(player);
|
|
+ });
|
|
this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
|
|
this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
|
(ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
@@ -476,8 +493,116 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
});
|
|
// Paper end - no-tick view distance
|
|
}
|
|
+ // Paper start - Chunk Prioritization
|
|
+ public void queueHolderUpdate(ChunkHolder playerchunk) {
|
|
+ Runnable runnable = () -> {
|
|
+ if (isUnloading(playerchunk)) {
|
|
+ return; // unloaded
|
|
+ }
|
|
+ distanceManager.pendingChunkUpdates.add(playerchunk);
|
|
+ if (!distanceManager.pollingPendingChunkUpdates) {
|
|
+ level.getChunkSource().runDistanceManagerUpdates();
|
|
+ }
|
|
+ };
|
|
+ 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 {
|
|
+ mainThreadExecutor.execute(runnable);
|
|
+ }
|
|
+ }
|
|
|
|
// Paper start
|
|
+ private boolean isUnloading(ChunkHolder playerchunk) {
|
|
+ return playerchunk == null || toDrop.contains(playerchunk.pos.toLong());
|
|
+ }
|
|
+
|
|
+ private void updateChunkPriorityMap(it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap map, long chunk, int level) {
|
|
+ int prev = map.getOrDefault(chunk, -1);
|
|
+ if (level > prev) {
|
|
+ map.put(chunk, level);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void checkHighPriorityChunks(ServerPlayer player) {
|
|
+ int currentTick = MinecraftServer.currentTick;
|
|
+ if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players
|
|
+ return;
|
|
+ }
|
|
+ player.lastHighPriorityChecked = currentTick;
|
|
+ it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap priorities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap();
|
|
+
|
|
+ int viewDistance = getEffectiveNoTickViewDistance();
|
|
+ net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos();
|
|
+
|
|
+ // Prioritize circular near
|
|
+ double playerChunkX = Mth.floor(player.getX()) >> 4;
|
|
+ double playerChunkZ = Mth.floor(player.getZ()) >> 4;
|
|
+ pos.setValues(player.getX(), 0, player.getZ());
|
|
+ double twoThirdModifier = 2D / 3D;
|
|
+ MCUtil.getSpiralOutChunks(pos, Math.min(6, viewDistance)).forEach(coord -> {
|
|
+ if (shouldSkipPrioritization(coord)) return;
|
|
+
|
|
+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z);
|
|
+ // Prioritize immediate
|
|
+ if (dist <= 4) {
|
|
+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (27 - dist));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Prioritize nearby chunks
|
|
+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (20 - dist * twoThirdModifier));
|
|
+ });
|
|
+
|
|
+ // Prioritize Frustum near 3
|
|
+ ChunkPos front3 = player.getChunkInFront(3);
|
|
+ pos.setValues(front3.x << 4, 0, front3.z << 4);
|
|
+ MCUtil.getSpiralOutChunks(pos, Math.min(5, viewDistance)).forEach(coord -> {
|
|
+ if (shouldSkipPrioritization(coord)) return;
|
|
+
|
|
+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z);
|
|
+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier));
|
|
+ });
|
|
+
|
|
+ // Prioritize Frustum near 5
|
|
+ if (viewDistance > 4) {
|
|
+ ChunkPos front5 = player.getChunkInFront(5);
|
|
+ pos.setValues(front5.x << 4, 0, front5.z << 4);
|
|
+ MCUtil.getSpiralOutChunks(pos, 4).forEach(coord -> {
|
|
+ if (shouldSkipPrioritization(coord)) return;
|
|
+
|
|
+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z);
|
|
+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier));
|
|
+ });
|
|
+ }
|
|
+
|
|
+ // Prioritize Frustum far 7
|
|
+ if (viewDistance > 6) {
|
|
+ ChunkPos front7 = player.getChunkInFront(7);
|
|
+ pos.setValues(front7.x << 4, 0, front7.z << 4);
|
|
+ MCUtil.getSpiralOutChunks(pos, 3).forEach(coord -> {
|
|
+ if (shouldSkipPrioritization(coord)) {
|
|
+ return;
|
|
+ }
|
|
+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z);
|
|
+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier));
|
|
+ });
|
|
+ }
|
|
+
|
|
+ if (priorities.isEmpty()) return;
|
|
+ distanceManager.delayDistanceManagerTick = true;
|
|
+ priorities.long2IntEntrySet().fastForEach(entry -> distanceManager.markHighPriority(new ChunkPos(entry.getLongKey()), entry.getIntValue()));
|
|
+ distanceManager.delayDistanceManagerTick = false;
|
|
+ level.getChunkSource().runDistanceManagerUpdates();
|
|
+
|
|
+ }
|
|
+
|
|
+ private boolean shouldSkipPrioritization(ChunkPos coord) {
|
|
+ if (playerViewDistanceNoTickMap.getObjectsInRange(coord.toLong()) == null) return true;
|
|
+ ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong());
|
|
+ return chunk != null && (chunk.isFullChunkReady());
|
|
+ }
|
|
+ // Paper end
|
|
public void updatePlayerMobTypeMap(Entity entity) {
|
|
if (!this.level.paperConfig.perPlayerMobSpawns) {
|
|
return;
|
|
@@ -635,6 +760,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
List<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> list = Lists.newArrayList();
|
|
int j = centerChunk.x;
|
|
int k = centerChunk.z;
|
|
+ ChunkHolder requestingNeighbor = getUpdatingChunkIfPresent(centerChunk.toLong()); // Paper
|
|
|
|
for (int l = -margin; l <= margin; ++l) {
|
|
for (int i1 = -margin; i1 <= margin; ++i1) {
|
|
@@ -653,6 +779,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(j1);
|
|
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getOrScheduleFuture(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);
|
|
}
|
|
@@ -1099,14 +1233,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
};
|
|
|
|
CompletableFuture<CompoundTag> chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z);
|
|
+ ChunkHolder playerChunk = getUpdatingChunkIfPresent(pos.toLong());
|
|
+ int chunkPriority = playerChunk != null ? playerChunk.getQueueLevel() : 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.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z,
|
|
- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture);
|
|
- this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY);
|
|
+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture);
|
|
} else {
|
|
- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z,
|
|
- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false);
|
|
+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority);
|
|
}
|
|
+ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, priority);
|
|
return ret;
|
|
// Paper end
|
|
}
|
|
@@ -1237,7 +1379,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
long i = playerchunk.getPos().toLong();
|
|
|
|
Objects.requireNonNull(playerchunk);
|
|
- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, playerchunk::getTicketLevel));
|
|
+ mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, () -> 1)); // Paper - final loads are always urgent!
|
|
});
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
index d94241bcca4f2fd5e464a860bd356af504dc68b7..864c78ae0f0b3b50b8ea22b709c1f16bea0ecfea 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -98,6 +98,7 @@ public abstract class DistanceManager {
|
|
}
|
|
|
|
private static int getTicketLevelAt(SortedArraySet<Ticket<?>> arraysetsorted) {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getLowestTicketLevel"); // Paper
|
|
return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1;
|
|
}
|
|
|
|
@@ -111,6 +112,7 @@ public abstract class DistanceManager {
|
|
|
|
public boolean runAllUpdates(ChunkMap playerchunkmap) {
|
|
//this.f.a(); // Paper - no longer used
|
|
+ org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
|
|
this.playerTicketManager.runAllUpdates();
|
|
int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
|
|
boolean flag = i != 0;
|
|
@@ -121,11 +123,13 @@ public abstract class DistanceManager {
|
|
|
|
// Paper start
|
|
if (!this.pendingChunkUpdates.isEmpty()) {
|
|
+ this.pollingPendingChunkUpdates = true; try {
|
|
while(!this.pendingChunkUpdates.isEmpty()) {
|
|
ChunkHolder remove = this.pendingChunkUpdates.remove();
|
|
remove.isUpdateQueued = false;
|
|
remove.updateFutures(playerchunkmap, this.mainThreadExecutor);
|
|
}
|
|
+ } finally { this.pollingPendingChunkUpdates = false; }
|
|
// Paper end
|
|
return true;
|
|
} else {
|
|
@@ -161,8 +165,10 @@ public abstract class DistanceManager {
|
|
return flag;
|
|
}
|
|
}
|
|
+ boolean pollingPendingChunkUpdates = false; // Paper
|
|
|
|
boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper
|
|
SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
|
|
int j = DistanceManager.getTicketLevelAt(arraysetsorted);
|
|
Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); // CraftBukkit - decompile error
|
|
@@ -176,7 +182,9 @@ public abstract class DistanceManager {
|
|
}
|
|
|
|
boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper
|
|
SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
|
|
+ int oldLevel = getTicketLevelAt(arraysetsorted); // Paper
|
|
|
|
boolean removed = false; // CraftBukkit
|
|
if (arraysetsorted.remove(ticket)) {
|
|
@@ -208,7 +216,8 @@ public abstract class DistanceManager {
|
|
this.tickets.remove(i);
|
|
}
|
|
|
|
- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false);
|
|
+ int newLevel = getTicketLevelAt(arraysetsorted); // Paper
|
|
+ if (newLevel > oldLevel) this.ticketTracker.update(i, newLevel, false); // Paper
|
|
return removed; // CraftBukkit
|
|
}
|
|
|
|
@@ -217,6 +226,135 @@ public abstract class DistanceManager {
|
|
this.addTicketAtLevel(type, pos, level, argument);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public static final int PRIORITY_TICKET_LEVEL = ChunkMap.MAX_CHUNK_DISTANCE;
|
|
+ public static final int URGENT_PRIORITY = 29;
|
|
+ public boolean delayDistanceManagerTick = false;
|
|
+ public boolean markUrgent(ChunkPos coords) {
|
|
+ return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY);
|
|
+ }
|
|
+ public boolean markHighPriority(ChunkPos coords, int priority) {
|
|
+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority));
|
|
+ return addPriorityTicket(coords, TicketType.PRIORITY, priority);
|
|
+ }
|
|
+
|
|
+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) {
|
|
+ delayDistanceManagerTick = true;
|
|
+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority));
|
|
+ int finalPriority = priority;
|
|
+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> {
|
|
+ addPriorityTicket(coords, TicketType.PRIORITY, finalPriority);
|
|
+ });
|
|
+ delayDistanceManagerTick = false;
|
|
+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
|
|
+ }
|
|
+
|
|
+ public void clearAreaPriorityTickets(ChunkPos center, int radius) {
|
|
+ delayDistanceManagerTick = true;
|
|
+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> {
|
|
+ this.removeTicket(coords.toLong(), new Ticket<ChunkPos>(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords));
|
|
+ });
|
|
+ delayDistanceManagerTick = false;
|
|
+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
|
|
+ }
|
|
+
|
|
+ private boolean hasPlayerTicket(ChunkPos coords, int level) {
|
|
+ SortedArraySet<Ticket<?>> tickets = this.tickets.get(coords.toLong());
|
|
+ if (tickets == null || tickets.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getType() == TicketType.PLAYER && ticket.getTicketLevel() == level) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ private boolean addPriorityTicket(ChunkPos coords, TicketType<ChunkPos> ticketType, int priority) {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket");
|
|
+ long pair = coords.toLong();
|
|
+ ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair);
|
|
+ boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33);
|
|
+
|
|
+ if (needsTicket) {
|
|
+ Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, coords);
|
|
+ ticketsToRelease.add(pair);
|
|
+ addTicket(pair, ticket);
|
|
+ }
|
|
+ if ((chunk != null && chunk.isFullChunkReady())) {
|
|
+ if (needsTicket) {
|
|
+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
|
|
+ }
|
|
+ return needsTicket;
|
|
+ }
|
|
+
|
|
+ boolean success;
|
|
+ if (!(success = updatePriorityTicket(coords, ticketType, priority))) {
|
|
+ Ticket<ChunkPos> ticket = new Ticket<ChunkPos>(ticketType, PRIORITY_TICKET_LEVEL, coords);
|
|
+ ticket.priority = priority;
|
|
+ success = this.addTicket(pair, ticket);
|
|
+ } else {
|
|
+ if (chunk == null) {
|
|
+ chunk = chunkMap.getUpdatingChunkIfPresent(pair);
|
|
+ }
|
|
+ chunkMap.queueHolderUpdate(chunk);
|
|
+ }
|
|
+
|
|
+ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true);
|
|
+
|
|
+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
|
|
+
|
|
+ return success;
|
|
+ }
|
|
+
|
|
+ private boolean updatePriorityTicket(ChunkPos coords, TicketType<ChunkPos> type, int priority) {
|
|
+ SortedArraySet<Ticket<?>> tickets = this.tickets.get(coords.toLong());
|
|
+ if (tickets == null) {
|
|
+ return false;
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getType() == type) {
|
|
+ // We only support increasing, not decreasing, too complicated
|
|
+ ticket.setCurrentTick(this.ticketTickCounter);
|
|
+ ticket.priority = Math.max(ticket.priority, priority);
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public int getChunkPriority(ChunkPos coords) {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority");
|
|
+ SortedArraySet<Ticket<?>> tickets = this.tickets.get(coords.toLong());
|
|
+ if (tickets == null) {
|
|
+ return 0;
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getType() == TicketType.URGENT) {
|
|
+ return URGENT_PRIORITY;
|
|
+ }
|
|
+ }
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getType() == TicketType.PRIORITY && ticket.priority > 0) {
|
|
+ return ticket.priority;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ public void clearPriorityTickets(ChunkPos coords) {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearPriority");
|
|
+ this.removeTicket(coords.toLong(), new Ticket<ChunkPos>(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords));
|
|
+ }
|
|
+
|
|
+ public void clearUrgent(ChunkPos coords) {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent");
|
|
+ this.removeTicket(coords.toLong(), new Ticket<ChunkPos>(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords));
|
|
+ }
|
|
+ // Paper end
|
|
public <T> boolean addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkcoordintpair, int level, T identifier) {
|
|
return this.addTicket(chunkcoordintpair.toLong(), new Ticket<>(ticketType, level, identifier));
|
|
// CraftBukkit end
|
|
@@ -516,41 +654,68 @@ public abstract class DistanceManager {
|
|
|
|
public void updateViewDistance(int watchDistance) {
|
|
ObjectIterator objectiterator = this.chunks.long2ByteEntrySet().iterator();
|
|
+ // Paper start - set the view distance before scheduling chunk loads/unloads
|
|
+ int lastViewDistance = viewDistance;
|
|
+ this.viewDistance = watchDistance;
|
|
+ // Paper end
|
|
|
|
while (objectiterator.hasNext()) {
|
|
it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry) objectiterator.next();
|
|
byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue();
|
|
long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey();
|
|
|
|
- this.onLevelChange(j, b0, this.haveTicketFor(b0), b0 <= watchDistance - 2);
|
|
+ this.onLevelChange(j, b0, b0 <= lastViewDistance - 2, this.haveTicketFor(b0)); // Paper
|
|
}
|
|
|
|
- this.viewDistance = watchDistance;
|
|
+ //this.e = i; // Paper - view distance is now set further up
|
|
}
|
|
|
|
private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) {
|
|
if (oldWithinViewDistance != withinViewDistance) {
|
|
- Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance
|
|
+ ChunkPos coords = new ChunkPos(pos); // Paper
|
|
+ Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, coords); // Paper - no-tick view distance
|
|
|
|
if (withinViewDistance) {
|
|
- DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> {
|
|
+ scheduleChunkLoad(pos, net.minecraft.server.MinecraftServer.currentTick, distance, (priority) -> { // Paper - smarter ticket delay based on frustum and distance
|
|
+ // Paper start - recheck its still valid if not cancel
|
|
+ if (!isChunkInRange(pos)) {
|
|
+ DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
|
|
+ DistanceManager.this.mainThreadExecutor.execute(() -> {
|
|
+ DistanceManager.this.removeTicket(pos, ticket);
|
|
+ DistanceManager.this.clearPriorityTickets(coords);
|
|
+ });
|
|
+ }, pos, false));
|
|
+ return;
|
|
+ }
|
|
+ // abort early if we got a ticket already
|
|
+ if (hasPlayerTicket(coords, 33)) return;
|
|
+ // skip player ticket throttle for near chunks
|
|
+ if (priority <= 3) {
|
|
+ DistanceManager.this.addTicket(pos, ticket);
|
|
+ DistanceManager.this.ticketsToRelease.add(pos);
|
|
+ return;
|
|
+ }
|
|
+ // Paper end
|
|
+ DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> { // CraftBukkit - decompile error
|
|
DistanceManager.this.mainThreadExecutor.execute(() -> {
|
|
- if (this.haveTicketFor(this.getLevel(pos))) {
|
|
+ if (isChunkInRange(pos)) { if (!hasPlayerTicket(coords, 33)) { // Paper - high priority might of already added it
|
|
DistanceManager.this.addTicket(pos, ticket);
|
|
DistanceManager.this.ticketsToRelease.add(pos);
|
|
- } else {
|
|
- DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
|
|
+ }} else { // Paper
|
|
+ DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { // CraftBukkit - decompile error
|
|
}, pos, false));
|
|
}
|
|
|
|
});
|
|
}, pos, () -> {
|
|
- return distance;
|
|
+ return Math.min(ChunkMap.MAX_CHUNK_DISTANCE, priority); // Paper
|
|
}));
|
|
+ }); // Paper
|
|
} else {
|
|
DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
|
|
DistanceManager.this.mainThreadExecutor.execute(() -> {
|
|
DistanceManager.this.removeTicket(pos, ticket);
|
|
+ DistanceManager.this.clearPriorityTickets(coords); // Paper
|
|
});
|
|
}, pos, true));
|
|
}
|
|
@@ -558,6 +723,101 @@ public abstract class DistanceManager {
|
|
|
|
}
|
|
|
|
+ // Paper start - smart scheduling of player tickets
|
|
+ private boolean isChunkInRange(long i) {
|
|
+ return this.haveTicketFor(this.getLevel(i));
|
|
+ }
|
|
+ public void scheduleChunkLoad(long i, long startTick, int initialDistance, java.util.function.Consumer<Integer> task) {
|
|
+ long elapsed = net.minecraft.server.MinecraftServer.currentTick - startTick;
|
|
+ ChunkPos chunkPos = new ChunkPos(i);
|
|
+ ChunkHolder updatingChunk = chunkMap.getUpdatingChunkIfPresent(i);
|
|
+ if ((updatingChunk != null && updatingChunk.isFullChunkReady()) || !isChunkInRange(i) || getChunkPriority(chunkPos) > 0) { // Copied from above
|
|
+ // no longer needed
|
|
+ task.accept(1);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ int desireDelay = 0;
|
|
+ double minDist = Double.MAX_VALUE;
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i);
|
|
+ if (elapsed == 0 && initialDistance <= 4) {
|
|
+ // Aim for no delay on initial 6 chunk radius tickets save on performance of the below code to only > 6
|
|
+ minDist = initialDistance;
|
|
+ } else if (players != null) {
|
|
+ Object[] backingSet = players.getBackingSet();
|
|
+
|
|
+ net.minecraft.core.BlockPos blockPos = chunkPos.getWorldPosition();
|
|
+
|
|
+ boolean isFront = false;
|
|
+ net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos();
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ if (!(backingSet[index] instanceof ServerPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ ServerPlayer player = (ServerPlayer) backingSet[index];
|
|
+
|
|
+ ChunkPos pointInFront = player.getChunkInFront(5);
|
|
+ pos.setValues(pointInFront.x << 4, 0, pointInFront.z << 4);
|
|
+ double frontDist = net.minecraft.server.MCUtil.distanceSq(pos, blockPos);
|
|
+
|
|
+ pos.setValues(player.getX(), 0, player.getZ());
|
|
+ double center = net.minecraft.server.MCUtil.distanceSq(pos, blockPos);
|
|
+
|
|
+ double dist = Math.min(frontDist, center);
|
|
+ if (!isFront) {
|
|
+ ChunkPos pointInBack = player.getChunkInFront(-7);
|
|
+ pos.setValues(pointInBack.x << 4, 0, pointInBack.z << 4);
|
|
+ double backDist = net.minecraft.server.MCUtil.distanceSq(pos, blockPos);
|
|
+ if (frontDist < backDist) {
|
|
+ isFront = true;
|
|
+ }
|
|
+ }
|
|
+ if (dist < minDist) {
|
|
+ minDist = dist;
|
|
+ }
|
|
+ }
|
|
+ if (minDist == Double.MAX_VALUE) {
|
|
+ minDist = 15;
|
|
+ } else {
|
|
+ minDist = Math.sqrt(minDist) / 16;
|
|
+ }
|
|
+ if (minDist > 4) {
|
|
+ int desiredTimeDelayMax = isFront ?
|
|
+ (minDist < 10 ? 7 : 15) : // Front
|
|
+ (minDist < 10 ? 15 : 45); // Back
|
|
+ desireDelay += (desiredTimeDelayMax * 20) * (minDist / 32);
|
|
+ }
|
|
+ } else {
|
|
+ minDist = initialDistance;
|
|
+ desireDelay = 1;
|
|
+ }
|
|
+ long delay = desireDelay - elapsed;
|
|
+ if (delay <= 0 && minDist > 4 && minDist < Double.MAX_VALUE) {
|
|
+ boolean hasAnyNeighbor = false;
|
|
+ for (int x = -1; x <= 1; x++) {
|
|
+ for (int z = -1; z <= 1; z++) {
|
|
+ if (x == 0 && z == 0) continue;
|
|
+ long pair = ChunkPos.asLong(chunkPos.x + x, chunkPos.z + z);
|
|
+ ChunkHolder neighbor = chunkMap.getUpdatingChunkIfPresent(pair);
|
|
+ ChunkStatus current = neighbor != null ? neighbor.getChunkHolderStatus() : null;
|
|
+ if (current != null && current.isOrAfter(ChunkStatus.LIGHT)) {
|
|
+ hasAnyNeighbor = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (!hasAnyNeighbor) {
|
|
+ delay += 20;
|
|
+ }
|
|
+ }
|
|
+ if (delay <= 0) {
|
|
+ task.accept((int) minDist);
|
|
+ } else {
|
|
+ int taskDelay = (int) Math.min(delay, minDist >= 10 ? 40 : (minDist < 6 ? 5 : 20));
|
|
+ net.minecraft.server.MCUtil.scheduleTask(taskDelay, () -> scheduleChunkLoad(i, startTick, initialDistance, task), "Player Ticket Delayer");
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public void runAllUpdates() {
|
|
super.runAllUpdates();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index 094c07c3208b0c05f918b7ee19f1d5b9ceeece47..275d9bdd884ca4ac7e57fc042d044eb49485191a 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -446,6 +446,26 @@ public class ServerChunkCache extends ChunkSource {
|
|
public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
|
|
this.distanceManager.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier);
|
|
}
|
|
+
|
|
+ public boolean markUrgent(ChunkPos coords) {
|
|
+ return this.distanceManager.markUrgent(coords);
|
|
+ }
|
|
+
|
|
+ public boolean markHighPriority(ChunkPos coords, int priority) {
|
|
+ return this.distanceManager.markHighPriority(coords, priority);
|
|
+ }
|
|
+
|
|
+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) {
|
|
+ this.distanceManager.markAreaHighPriority(center, priority, radius);
|
|
+ }
|
|
+
|
|
+ public void clearAreaPriorityTickets(ChunkPos center, int radius) {
|
|
+ this.distanceManager.clearAreaPriorityTickets(center, radius);
|
|
+ }
|
|
+
|
|
+ public void clearPriorityTickets(ChunkPos coords) {
|
|
+ this.distanceManager.clearPriorityTickets(coords);
|
|
+ }
|
|
// Paper end - async chunk io
|
|
|
|
@Nullable
|
|
@@ -486,6 +506,8 @@ public class ServerChunkCache extends ChunkSource {
|
|
Objects.requireNonNull(completablefuture);
|
|
if (!completablefuture.isDone()) { // Paper
|
|
// Paper start - async chunk io/loading
|
|
+ ChunkPos pair = new ChunkPos(x1, z1);
|
|
+ this.distanceManager.markUrgent(pair);
|
|
this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
|
|
com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
|
|
// Paper end
|
|
@@ -494,6 +516,8 @@ public class ServerChunkCache extends ChunkSource {
|
|
chunkproviderserver_a.managedBlock(completablefuture::isDone);
|
|
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
|
|
this.level.timings.syncChunkLoad.stopTiming(); // Paper
|
|
+ this.distanceManager.clearPriorityTickets(pair); // Paper
|
|
+ this.distanceManager.clearUrgent(pair); // Paper
|
|
} // Paper
|
|
ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
|
|
return ichunkaccess1;
|
|
@@ -567,10 +591,12 @@ public class ServerChunkCache extends ChunkSource {
|
|
if (flag && !currentlyUnloading) {
|
|
// CraftBukkit end
|
|
this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
|
|
+ if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper
|
|
if (this.chunkAbsent(playerchunk, l)) {
|
|
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
|
|
|
|
gameprofilerfiller.push("chunkLoad");
|
|
+ distanceManager.delayDistanceManagerTick = false; // Paper - ensure this is never false
|
|
this.runDistanceManagerUpdates();
|
|
playerchunk = this.getVisibleChunkIfPresent(k);
|
|
gameprofilerfiller.pop();
|
|
@@ -579,8 +605,13 @@ public class ServerChunkCache extends ChunkSource {
|
|
}
|
|
}
|
|
}
|
|
-
|
|
- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap);
|
|
+ // Paper start
|
|
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap);
|
|
+ if (isUrgent) {
|
|
+ future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair));
|
|
+ }
|
|
+ return future;
|
|
+ // Paper end
|
|
}
|
|
|
|
private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
|
|
@@ -632,6 +663,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
}
|
|
|
|
public boolean runDistanceManagerUpdates() {
|
|
+ if (distanceManager.delayDistanceManagerTick) return false; // Paper
|
|
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
|
|
boolean flag1 = this.chunkMap.promoteChunkMap();
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index d59e707f28a5f04545208ad33d122fc433b85933..8cbfcbc795c920420bd1906576dfc766910e9e62 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -183,6 +183,12 @@ public class ServerPlayer extends Player {
|
|
private int lastRecordedArmor = Integer.MIN_VALUE;
|
|
private int lastRecordedLevel = Integer.MIN_VALUE;
|
|
private int lastRecordedExperience = Integer.MIN_VALUE;
|
|
+ public long lastHighPriorityChecked; // Paper
|
|
+ public void forceCheckHighPriority() {
|
|
+ lastHighPriorityChecked = -1;
|
|
+ getLevel().getChunkSource().chunkMap.checkHighPriorityChunks(this);
|
|
+ }
|
|
+ public boolean isRealPlayer; // Paper
|
|
private float lastSentHealth = -1.0E8F;
|
|
private int lastSentFood = -99999999;
|
|
private boolean lastFoodSaturationZero = true;
|
|
@@ -324,6 +330,21 @@ public class ServerPlayer extends Player {
|
|
this.maxHealthCache = this.getMaxHealth();
|
|
this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
|
|
}
|
|
+ // Paper start
|
|
+ public BlockPos getPointInFront(double inFront) {
|
|
+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.getYRot()+90)); // MC rotates yaw 90 for some odd reason
|
|
+ final double x = getX() + inFront * Math.cos(rads);
|
|
+ final double z = getZ() + inFront * Math.sin(rads);
|
|
+ return new BlockPos(x, getY(), z);
|
|
+ }
|
|
+
|
|
+ public ChunkPos getChunkInFront(double inFront) {
|
|
+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.getYRot()+90)); // MC rotates yaw 90 for some odd reason
|
|
+ final double x = getX() + (inFront * 16) * Math.cos(rads);
|
|
+ final double z = getZ() + (inFront * 16) * Math.sin(rads);
|
|
+ return new ChunkPos(Mth.floor(x) >> 4, Mth.floor(z) >> 4);
|
|
+ }
|
|
+ // 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
|
|
@@ -645,6 +666,7 @@ public class ServerPlayer extends Player {
|
|
if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn)
|
|
super.tick();
|
|
}
|
|
+ if (valid && isAlive() && connection != null) ((ServerLevel)level).getChunkSource().chunkMap.checkHighPriorityChunks(this); // Paper
|
|
|
|
for (int i = 0; i < this.getInventory().getContainerSize(); ++i) {
|
|
ItemStack itemstack = this.getInventory().getItem(i);
|
|
diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
|
|
index a7aa7a9038d4812a9d1e4e72c4dbbbe10df15820..5c03bae6d34aae4752438650414460102a043a4b 100644
|
|
--- a/src/main/java/net/minecraft/server/level/Ticket.java
|
|
+++ b/src/main/java/net/minecraft/server/level/Ticket.java
|
|
@@ -8,6 +8,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
|
|
public final T key; public final T getObjectReason() { return this.key; } // Paper - OBFHELPER
|
|
private long createdTick; public final long getCreationTick() { return this.createdTick; } // Paper - OBFHELPER
|
|
public long delayUnloadBy; // Paper
|
|
+ public int priority = 0; // Paper
|
|
|
|
protected Ticket(TicketType<T> type, int level, T argument) {
|
|
this.type = type;
|
|
@@ -57,6 +58,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
|
|
return this.ticketLevel;
|
|
}
|
|
|
|
+ public final void setCurrentTick(long i) { this.setCreatedTick(i); } // Paper - OBFHELPER
|
|
protected void setCreatedTick(long tickCreated) {
|
|
this.createdTick = tickCreated;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
|
|
index 8770fe0db46b01e8b608637df4f1a669a3f4cdde..3c1698ba0d3bc412ab957777d9b5211dbc555208 100644
|
|
--- a/src/main/java/net/minecraft/server/level/TicketType.java
|
|
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
|
|
@@ -9,6 +9,8 @@ import net.minecraft.world.level.ChunkPos;
|
|
public class TicketType<T> {
|
|
public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
|
|
public static final TicketType<Long> ASYNC_LOAD = create("async_load", Long::compareTo); // Paper
|
|
+ public static final TicketType<ChunkPos> PRIORITY = create("priority", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper
|
|
+ public static final TicketType<ChunkPos> URGENT = create("urgent", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper
|
|
|
|
private final String name;
|
|
private final Comparator<T> comparator;
|
|
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
index fecb2a6b8674ca110d6af539396873e9dcdc0edb..6476d2a478b85116061a13f7ef2a5fec2ade4a04 100644
|
|
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
@@ -1566,6 +1566,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
|
|
|
|
this.awaitingTeleportTime = this.tickCount;
|
|
this.player.absMoveTo(d0, d1, d2, f, f1);
|
|
+ this.player.forceCheckHighPriority(); // Paper
|
|
this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag));
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
index 7eb3088d47ff78198e01a3a12b0ce6abe9d6ca6b..9c13479d2a508728c10803dee719ed7ad097e019 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -272,8 +272,8 @@ public abstract class PlayerList {
|
|
net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap;
|
|
net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager;
|
|
distanceManager.addTicketAtLevel(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong());
|
|
- worldserver1.getChunkSource().runDistanceManagerUpdates();
|
|
- worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> {
|
|
+ worldserver1.getChunkSource().markAreaHighPriority(pos, 28, 3);
|
|
+ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> {
|
|
net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong());
|
|
if (updatingChunk != null) {
|
|
return updatingChunk.getEntityTickingFuture();
|
|
@@ -686,6 +686,7 @@ public abstract class PlayerList {
|
|
SocketAddress socketaddress = loginlistener.connection.getRemoteAddress();
|
|
|
|
ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile);
|
|
+ entity.isRealPlayer = true; // Paper
|
|
Player player = entity.getBukkitEntity();
|
|
PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress());
|
|
|
|
@@ -874,6 +875,7 @@ public abstract class PlayerList {
|
|
// CraftBukkit end
|
|
|
|
worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper
|
|
+ entityplayer1.forceCheckHighPriority(); // Player
|
|
while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
|
|
entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index f72471ac82907a0d5112598b3289689495285944..29b2f5d3e6fd4859fbe94ad1cd5c355be7f9d4f3 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -2558,6 +2558,10 @@ public class CraftWorld implements World {
|
|
return future;
|
|
}
|
|
|
|
+ if (!urgent) {
|
|
+ // if not urgent, at least use a slightly boosted priority
|
|
+ world.getChunkSource().markHighPriority(new ChunkPos(x, z), 1);
|
|
+ }
|
|
return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
|
|
net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
|
|
if (chunk != null) addTicket(x, z); // Paper
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
index 5bab49aa7f4a99dc6387fcf57bc39b16f912e6ab..0ff8a2d898b2fce5a7234841bb10cd5be050e326 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -889,6 +889,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead.");
|
|
}
|
|
|
|
+ // Paper start
|
|
+ @Override
|
|
+ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location loc, @javax.annotation.Nonnull PlayerTeleportEvent.TeleportCause cause) {
|
|
+ ((CraftWorld)loc.getWorld()).getHandle().getChunkSource().markAreaHighPriority(new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(loc.getX()) >> 4, net.minecraft.util.Mth.floor(loc.getZ()) >> 4), 28, 3); // Paper - load area high priority
|
|
+ return super.teleportAsync(loc, cause);
|
|
+ }
|
|
+ // Paper end
|
|
@Override
|
|
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) {
|
|
Preconditions.checkArgument(location != null, "location");
|