aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0734-Optimise-chunk-tick-iteration.patch
blob: 3789453481ea4ba4ad64898b090eba77642265bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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 b75b3c4d274252a3a5c53059b9702728eeada389..8bea90cb57f38f33e8b3162e24e353993a98ebbf 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
@@ -253,7 +263,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();
             }
 
@@ -276,6 +286,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) {
@@ -290,8 +301,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 ae0d7295c88005749f13dd230136f4a39d0a578e..1bbb15354e457a6056d380f9ef318a4661f460e3 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -160,6 +160,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 ce88da358c8a89564a911e6c818e906e845006ff..438406936633b9c67d21b26527c3d1654118c744 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -47,6 +47,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp
 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 {
 
@@ -987,34 +988,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().entities.spawning.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);
                     }
 
@@ -1022,7 +1031,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) {
@@ -1030,15 +1048,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.getFullChunkNowUnchecked()); // 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();
         }
     }