diff options
Diffstat (limited to 'patches/server/0845-Replace-ticket-level-propagator.patch')
-rw-r--r-- | patches/server/0845-Replace-ticket-level-propagator.patch | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/patches/server/0845-Replace-ticket-level-propagator.patch b/patches/server/0845-Replace-ticket-level-propagator.patch new file mode 100644 index 0000000000..8959b46f3f --- /dev/null +++ b/patches/server/0845-Replace-ticket-level-propagator.patch @@ -0,0 +1,258 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf <[email protected]> +Date: Sun, 21 Mar 2021 16:25:42 -0700 +Subject: [PATCH] Replace ticket level propagator + +Mojang's propagator is slow, and this isn't surprising +given it's built on the same utilities the vanilla light engine +is built on. The simple propagator I wrote is approximately 4x +faster when simulating player movement. For a long time timing +reports have shown this function take up significant tick, ( +approx 10% or more), and async sampling data shows the level +propagation alone takes up a significant amount. So this +should help with that. A big side effect is that mid-tick +will be more effective, since more time will be allocated +to actually processing chunk tasks vs the ticket level updates. + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 06e4d3a02e0d1326b7029157856476db4ef3575e..f581a9f79b2357118d912a15344ff94df3b0c50e 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -38,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.LevelChunk; + import org.slf4j.Logger; + ++import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper + public abstract class DistanceManager { + + static final Logger LOGGER = LogUtils.getLogger(); +@@ -48,7 +49,7 @@ public abstract class DistanceManager { + private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33; + final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap(); +- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); ++ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator + public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final TickingTracker tickingTicketsTracker = new TickingTracker(); + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); +@@ -83,6 +84,46 @@ public abstract class DistanceManager { + this.chunkMap = chunkMap; // Paper + } + ++ // Paper start - replace ticket level propagator ++ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() { ++ @Override ++ protected void rehash(int newN) { ++ // no downsizing allowed ++ if (newN < this.n) { ++ return; ++ } ++ super.rehash(newN); ++ } ++ }; ++ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D( ++ (long coordinate, byte oldLevel, byte newLevel) -> { ++ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel)); ++ } ++ ); ++ // function for converting between ticket levels and propagator levels and vice versa ++ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects ++ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator ++ // and the levels we get out of the propagator ++ ++ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on ++ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded ++ public static int convertBetweenTicketLevels(final int level) { ++ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1; ++ } ++ ++ protected final int getPropagatedTicketLevel(final long coordinate) { ++ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate)); ++ } ++ ++ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { ++ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) { ++ this.ticketLevelPropagator.removeSource(coordinate); ++ } else { ++ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel)); ++ } ++ } ++ // Paper end - replace ticket level propagator ++ + protected void purgeStaleTickets() { + ++this.ticketTickCounter; + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); +@@ -117,7 +158,7 @@ public abstract class DistanceManager { + } + + if (flag) { +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); ++ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator + } + + if (((SortedArraySet) entry.getValue()).isEmpty()) { +@@ -140,61 +181,94 @@ public abstract class DistanceManager { + @Nullable + protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); + ++ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator + public boolean runAllUpdates(ChunkMap chunkStorage) { + //this.f.a(); // Paper - no longer used + this.tickingTicketsTracker.runAllUpdates(); + org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper + this.playerTicketManager.runAllUpdates(); +- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); +- boolean flag = i != 0; ++ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator + + if (flag) { + ; + } + +- // Paper start +- if (!this.pendingChunkUpdates.isEmpty()) { +- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority +- while(!this.pendingChunkUpdates.isEmpty()) { +- ChunkHolder remove = this.pendingChunkUpdates.remove(); +- remove.isUpdateQueued = false; +- remove.updateFutures(chunkStorage, this.mainThreadExecutor); +- } +- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority +- // Paper end +- return true; +- } else { +- if (!this.ticketsToRelease.isEmpty()) { +- LongIterator longiterator = this.ticketsToRelease.iterator(); ++ // Paper start - replace level propagator ++ ticket_update_loop: ++ while (!this.ticketLevelUpdates.isEmpty()) { ++ flag = true; + +- while (longiterator.hasNext()) { +- long j = longiterator.nextLong(); ++ boolean oldPolling = this.pollingPendingChunkUpdates; ++ this.pollingPendingChunkUpdates = true; ++ try { ++ for (java.util.Iterator<Long2IntMap.Entry> iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ Long2IntMap.Entry entry = iterator.next(); ++ long key = entry.getLongKey(); ++ int newLevel = entry.getIntValue(); ++ ChunkHolder chunk = this.getChunk(key); ++ ++ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) { ++ // not loaded and it shouldn't be loaded! ++ continue; ++ } + +- if (this.getTickets(j).stream().anyMatch((ticket) -> { +- return ticket.getType() == TicketType.PLAYER; +- })) { +- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j); ++ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel(); + +- if (playerchunk == null) { +- throw new IllegalStateException(); ++ if (currentLevel == newLevel) { ++ // nothing to do ++ continue; ++ } ++ ++ this.updateChunkScheduling(key, newLevel, chunk, currentLevel); ++ } ++ ++ long recursiveCheck = ++this.ticketLevelUpdateCount; ++ while (!this.ticketLevelUpdates.isEmpty()) { ++ long key = this.ticketLevelUpdates.firstLongKey(); ++ int newLevel = this.ticketLevelUpdates.removeFirstInt(); ++ ChunkHolder chunk = this.getChunk(key); ++ ++ if (chunk == null) { ++ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) { ++ throw new IllegalStateException("Expected chunk holder to be created"); + } ++ // not loaded and it shouldn't be loaded! ++ continue; ++ } + +- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getEntityTickingChunkFuture(); ++ int currentLevel = chunk.oldTicketLevel; + +- completablefuture.thenAccept((either) -> { +- this.mainThreadExecutor.execute(() -> { +- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { +- }, j, false)); +- }); +- }); ++ if (currentLevel == newLevel) { ++ // nothing to do ++ continue; ++ } ++ ++ chunk.updateFutures(chunkStorage, this.mainThreadExecutor); ++ if (recursiveCheck != this.ticketLevelUpdateCount) { ++ // back to the start, we must create player chunks and update the ticket level fields before ++ // processing the actual level updates ++ continue ticket_update_loop; + } + } + +- this.ticketsToRelease.clear(); +- } ++ for (;;) { ++ if (recursiveCheck != this.ticketLevelUpdateCount) { ++ continue ticket_update_loop; ++ } ++ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll(); ++ if (pendingUpdate == null) { ++ break; ++ } + +- return flag; ++ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor); ++ } ++ } finally { ++ this.pollingPendingChunkUpdates = oldPolling; ++ } + } ++ ++ return flag; ++ // Paper end - replace level propagator + } + boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority + +@@ -206,7 +280,7 @@ public abstract class DistanceManager { + + ticket1.setCreatedTick(this.ticketTickCounter); + if (ticket.getTicketLevel() < j) { +- this.ticketTracker.update(i, ticket.getTicketLevel(), true); ++ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator + } + + return ticket == ticket1; // CraftBukkit +@@ -250,7 +324,7 @@ public abstract class DistanceManager { + // Paper start - Chunk priority + int newLevel = getTicketLevelAt(arraysetsorted); + if (newLevel > oldLevel) { +- this.ticketTracker.update(i, newLevel, false); ++ this.updateTicketLevel(i, newLevel); // Paper // Paper - replace ticket level propagator + } + // Paper end + return removed; // CraftBukkit +@@ -564,7 +638,7 @@ public abstract class DistanceManager { + } + + if (flag) { +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); ++ this.updateTicketLevel(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue())); // Paper - replace ticket level propagator + } + + if (((SortedArraySet) entry.getValue()).isEmpty()) { +@@ -587,7 +661,7 @@ public abstract class DistanceManager { + SortedArraySet<Ticket<?>> tickets = entry.getValue(); + if (tickets.remove(target)) { + // copied from removeTicket +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); ++ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator + + // can't use entry after it's removed + if (tickets.isEmpty()) { |