Paper/patches/server/0742-Optimise-chunk-tick-iteration.patch
Nassim Jahnke 1358d1e914
Updated Upstream (CraftBukkit/Spigot) (#7580)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
881e06e5 PR-725: Add Item Unlimited Lifetime APIs

CraftBukkit Changes:
74c08312 SPIGOT-6962: Call EntityChangeBlockEvent when when FallingBlockEntity starts to fall
64db5126 SPIGOT-6959: Make /loot command ignore empty items for spawn
2d760831 Increase outdated build delay
9ed7e4fb SPIGOT-6138, SPIGOT-6415: Don't call CreatureSpawnEvent after cross-dimensional travel
fc4ad813 SPIGOT-6895: Trees grown with applyBoneMeal() don't fire the StructureGrowthEvent
59733a2e SPIGOT-6961: Actually return a copy of the ItemMeta

Spigot Changes:
ffceeae3 SPIGOT-6956: Drop unload queue patch as attempt at fixing stop issue
e19ddabd PR-1011: Add Item Unlimited Lifetime APIs
34d40b0e SPIGOT-2942: give command fires PlayerDropItemEvent, cancelling it causes Item Duplication
2022-03-13 08:47:54 +01:00

204 lines
11 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 7 May 2020 05:48:54 -0700
Subject: [PATCH] Optimise chunk tick iteration
Use a dedicated list of entity ticking chunks to reduce the cost
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index fbfbe9adeca7364e6346c887616890bf968f38a1..bd43fbc8a93afa7604aa467392520ed7b30a1d83 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -86,11 +86,21 @@ public class ChunkHolder {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
void onChunkRemove() {
this.playersInMobSpawnRange = null;
this.playersInChunkTickRange = null;
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.remove(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
long lastAutoSaveTime; // Paper - incremental autosave
@@ -246,7 +256,7 @@ public class ChunkHolder {
if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
if (this.changedBlocksPerSection[i] == null) {
- this.hasChangedSections = true;
+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
this.changedBlocksPerSection[i] = new ShortOpenHashSet();
}
@@ -266,6 +276,7 @@ public class ChunkHolder {
int k = this.lightEngine.getMaxLightSection();
if (y >= j && y <= k) {
+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
int l = y - j;
if (lightType == LightLayer.SKY) {
@@ -279,8 +290,19 @@ public class ChunkHolder {
}
}
+ // Paper start - optimise chunk tick iteration
+ public final boolean needsBroadcastChanges() {
+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
+ }
+
+ private void addToBroadcastMap() {
+ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update");
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
+
public void broadcastChanges(LevelChunk chunk) {
- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
+ if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call
Level world = chunk.getLevel();
int i = 0;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 5d60fa26e9f5d3c9c57a61c509d5b924bbed3281..ef28e0f57ba593265a3eca4d3f21d0b1b51e8740 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -159,6 +159,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private final Queue<Runnable> unloadQueue;
int viewDistance;
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
+ public final ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new ReferenceOpenHashSet<>();
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index adab778c11ef11cb57418675a98129afb01ec06e..d5eb5a365c8e8cdcd8e9cf54918cc2fb383c6625 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -46,6 +46,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.LevelStorageSource;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
public class ServerChunkCache extends ChunkSource {
@@ -994,34 +995,42 @@ public class ServerChunkCache extends ChunkSource {
this.lastSpawnState = spawnercreature_d;
gameprofilerfiller.popPush("filteringLoadedChunks");
- List<ServerChunkCache.ChunkAndHolder> list = Lists.newArrayListWithCapacity(l);
- Iterator iterator = this.chunkMap.getChunks().iterator();
+ // Paper - moved down
this.level.timings.chunkTicks.startTiming(); // Paper
- while (iterator.hasNext()) {
- ChunkHolder playerchunk = (ChunkHolder) iterator.next();
- LevelChunk chunk = playerchunk.getTickingChunk();
-
- if (chunk != null) {
- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk));
- }
- }
+ // Paper - moved down
gameprofilerfiller.popPush("spawnAndTick");
boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
- Collections.shuffle(list);
+ // Paper - only shuffle if per-player mob spawning is disabled
// Paper - moved natural spawn event up
- Iterator iterator1 = list.iterator();
+ // Paper start - optimise chunk tick iteration
+ Iterator<LevelChunk> iterator1;
+ if (this.level.paperConfig.perPlayerMobSpawns) {
+ iterator1 = this.entityTickingChunks.iterator();
+ } else {
+ iterator1 = this.entityTickingChunks.unsafeIterator();
+ List<LevelChunk> shuffled = Lists.newArrayListWithCapacity(this.entityTickingChunks.size());
+ while (iterator1.hasNext()) {
+ shuffled.add(iterator1.next());
+ }
+ Collections.shuffle(shuffled);
+ iterator1 = shuffled.iterator();
+ }
+ try {
while (iterator1.hasNext()) {
- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
- LevelChunk chunk1 = chunkproviderserver_a.chunk;
+ LevelChunk chunk1 = iterator1.next();
+ ChunkHolder holder = chunk1.playerChunk;
+ if (holder != null) {
+ // Paper - move down
+ // Paper end - optimise chunk tick iteration
ChunkPos chunkcoordintpair = chunk1.getPos();
- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
+ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
chunk1.incrementInhabitedTime(j);
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
}
@@ -1029,7 +1038,16 @@ public class ServerChunkCache extends ChunkSource {
this.level.tickChunk(chunk1, k);
}
}
+ // Paper start - optimise chunk tick iteration
+ }
}
+
+ } finally {
+ if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) {
+ safeIterator.finishedIterating();
+ }
+ }
+ // Paper end - optimise chunk tick iteration
this.level.timings.chunkTicks.stopTiming(); // Paper
gameprofilerfiller.popPush("customSpawners");
if (flag2) {
@@ -1037,15 +1055,24 @@ public class ServerChunkCache extends ChunkSource {
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
} // Paper - timings
}
-
- gameprofilerfiller.popPush("broadcast");
- list.forEach((chunkproviderserver_a1) -> {
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
- });
gameprofilerfiller.pop();
+ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded
+ gameprofilerfiller.popPush("broadcast");
+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
+ ReferenceOpenHashSet<ChunkHolder> copy = this.chunkMap.needsChangeBroadcasting.clone();
+ this.chunkMap.needsChangeBroadcasting.clear();
+ for (ChunkHolder holder : copy) {
+ holder.broadcastChanges(holder.getFullChunkUnchecked()); // LevelChunks are NEVER unloaded
+ if (holder.needsBroadcastChanges()) {
+ // I DON'T want to KNOW what DUMB plugins might be doing.
+ this.chunkMap.needsChangeBroadcasting.add(holder);
+ }
+ }
+ }
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
gameprofilerfiller.pop();
+ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
this.chunkMap.tick();
}
}