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/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 280ca8758cbaf710c2bf357e41dd2af6e14b49bc..4f502f1c8a86a28e2cd6513516f3846ed74be891 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -999,34 +999,46 @@ 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) {
+                    gameprofilerfiller.popPush("broadcast");
+                    this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+                    holder.broadcastChanges(chunk1);
+                    this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
+                    gameprofilerfiller.pop();
+                // Paper end - optimise chunk tick iteration
                 ChunkPos chunkcoordintpair = chunk1.getPos();
 
-                if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
+                if ((true || this.level.isPositionEntityTicking(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
                     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);
                     }
 
@@ -1034,7 +1046,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) {
@@ -1043,13 +1064,7 @@ public class ServerChunkCache extends ChunkSource {
                 } // 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 - no, iterating just ONCE is expensive enough! Don't do it TWICE! Code moved up
             gameprofilerfiller.pop();
             this.chunkMap.tick();
         }
