aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches/0379-Reduce-sync-loads.patch
blob: 8b961aa5773b1ba576d0f20cac73315985a52bdc (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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 19 Jul 2019 03:29:14 -0700
Subject: [PATCH] Reduce sync loads

This reduces calls to getChunkAt which would load chunks.

This patch also adds a tool to find calls which are doing this, however
it must be enabled by setting the startup flag -Dpaper.debug-sync-loads=true

To get a debug log for sync loads, the command is /paper syncloadinfo

diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
index 9b8b49be032d7ceebcea8d7b98f999ed7166d0a1..f7d98a5ba54d041eef10b04f821e0958ad898b0a 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -1,10 +1,14 @@
 package com.destroystokyo.paper;
 
 import com.destroystokyo.paper.io.chunk.ChunkTaskManager;
+import com.destroystokyo.paper.io.SyncLoadFinder;
 import com.google.common.base.Functions;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
 import net.minecraft.server.*;
 import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.commons.lang3.tuple.Pair;
@@ -19,6 +23,9 @@ import org.bukkit.craftbukkit.CraftWorld;
 import org.bukkit.entity.Player;
 
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.StringWriter;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -29,14 +36,14 @@ public class PaperCommand extends Command {
     public PaperCommand(String name) {
         super(name);
         this.description = "Paper related commands";
-        this.usageMessage = "/paper [heap | entity | reload | version | debug | dumpwaiting | chunkinfo]";
+        this.usageMessage = "/paper [heap | entity | reload | version | debug | dumpwaiting | chunkinfo | syncloadinfo]";
         this.setPermission("bukkit.command.paper");
     }
 
     @Override
     public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
         if (args.length <= 1)
-            return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "dumpwaiting", "chunkinfo");
+            return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "dumpwaiting", "chunkinfo", "syncloadinfo");
 
         switch (args[0].toLowerCase(Locale.ENGLISH))
         {
@@ -134,6 +141,9 @@ public class PaperCommand extends Command {
             case "chunkinfo":
                 doChunkInfo(sender, args);
                 break;
+            case "syncloadinfo":
+                this.doSyncLoadInfo(sender, args);
+                break;
             case "ver":
             case "version":
                 Command ver = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version");
@@ -150,6 +160,40 @@ public class PaperCommand extends Command {
         return true;
     }
 
+    private void doSyncLoadInfo(CommandSender sender, String[] args) {
+        if (!SyncLoadFinder.ENABLED) {
+            sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.");
+            return;
+        }
+        File file = new File(new File(new File("."), "debug"),
+            "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
+        file.getParentFile().mkdirs();
+        sender.sendMessage(ChatColor.GREEN + "Writing sync load info to " + file.toString());
+
+
+        try {
+            final JsonObject data = SyncLoadFinder.serialize();
+
+            StringWriter stringWriter = new StringWriter();
+            JsonWriter jsonWriter = new JsonWriter(stringWriter);
+            jsonWriter.setIndent(" ");
+            jsonWriter.setLenient(false);
+            Streams.write(data, jsonWriter);
+
+            String fileData = stringWriter.toString();
+
+            try (
+                PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")
+            ) {
+                out.print(fileData);
+            }
+            sender.sendMessage(ChatColor.GREEN + "Successfully written sync load information!");
+        } catch (Throwable thr) {
+            sender.sendMessage(ChatColor.RED + "Failed to write sync load information");
+            thr.printStackTrace();
+        }
+    }
+
     private void doChunkInfo(CommandSender sender, String[] args) {
         List<org.bukkit.World> worlds;
         if (args.length < 2 || args[1].equals("*")) {
diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a68a8012f83bab9e814159c76b8c3710c7b1112
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
@@ -0,0 +1,172 @@
+package com.destroystokyo.paper.io;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.mojang.datafixers.util.Pair;
+import it.unimi.dsi.fastutil.longs.Long2IntMap;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.minecraft.server.World;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+public class SyncLoadFinder {
+
+    public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads");
+
+    private static final WeakHashMap<World, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> SYNC_LOADS = new WeakHashMap<>();
+
+    private static final class SyncLoadInformation {
+
+        public int times;
+
+        public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap();
+    }
+
+    public static void logSyncLoad(final World world, final int chunkX, final int chunkZ) {
+        if (!ENABLED) {
+            return;
+        }
+
+        final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace());
+
+        SYNC_LOADS.compute(world, (final World keyInMap, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation> map) -> {
+            if (map == null) {
+                map = new Object2ObjectOpenHashMap<>();
+            }
+
+            map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> {
+                if (valueInMap == null) {
+                    valueInMap = new SyncLoadInformation();
+                }
+
+                ++valueInMap.times;
+
+                valueInMap.coordinateTimes.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> {
+                    return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1);
+                });
+
+                return valueInMap;
+            });
+
+            return map;
+        });
+    }
+
+    public static JsonObject serialize() {
+        final JsonObject ret = new JsonObject();
+
+        final JsonArray worldsData = new JsonArray();
+
+        for (final Map.Entry<World, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> entry : SYNC_LOADS.entrySet()) {
+            final World world = entry.getKey();
+
+            final JsonObject worldData = new JsonObject();
+
+            worldData.addProperty("name", world.getWorld().getName());
+
+            final List<Pair<ThrowableWithEquals, SyncLoadInformation>> data = new ArrayList<>();
+
+            entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> {
+                data.add(new Pair<>(stacktrace, times));
+            });
+
+            data.sort((Pair<ThrowableWithEquals, SyncLoadInformation> pair1, Pair<ThrowableWithEquals, SyncLoadInformation> pair2) -> {
+                return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order
+            });
+
+            final JsonArray stacktraces = new JsonArray();
+
+            for (Pair<ThrowableWithEquals, SyncLoadInformation> pair : data) {
+                final JsonObject stacktrace = new JsonObject();
+
+                stacktrace.addProperty("times", pair.getSecond().times);
+
+                final JsonArray traces = new JsonArray();
+
+                for (StackTraceElement element : pair.getFirst().stacktrace) {
+                    traces.add(String.valueOf(element));
+                }
+
+                stacktrace.add("stacktrace", traces);
+
+                final JsonArray coordinates = new JsonArray();
+
+                for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) {
+                    final long key = coordinate.getLongKey();
+                    final int times = coordinate.getIntValue();
+                    coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + times);
+                }
+
+                stacktrace.add("coordinates", coordinates);
+
+                stacktraces.add(stacktrace);
+            }
+
+
+            worldData.add("stacktraces", stacktraces);
+            worldsData.add(worldData);
+        }
+
+        ret.add("worlds", worldsData);
+
+        return ret;
+    }
+
+    static final class ThrowableWithEquals {
+
+        private final StackTraceElement[] stacktrace;
+        private final int hash;
+
+        public ThrowableWithEquals(final StackTraceElement[] stacktrace) {
+            this.stacktrace = stacktrace;
+            this.hash = ThrowableWithEquals.hash(stacktrace);
+        }
+
+        public static int hash(final StackTraceElement[] stacktrace) {
+            int hash = 0;
+
+            for (int i = 0; i < stacktrace.length; ++i) {
+                hash *= 31;
+                hash += stacktrace[i].hashCode();
+            }
+
+            return hash;
+        }
+
+        @Override
+        public int hashCode() {
+            return this.hash;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == null || obj.getClass() != this.getClass()) {
+                return false;
+            }
+
+            final ThrowableWithEquals other = (ThrowableWithEquals)obj;
+            final StackTraceElement[] otherStackTrace = other.stacktrace;
+
+            if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) {
+                return false;
+            }
+
+            if (this == obj) {
+                return true;
+            }
+
+            for (int i = 0; i < this.stacktrace.length; ++i) {
+                if (!this.stacktrace[i].equals(otherStackTrace[i])) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index edd901bb53385fa3d189a0057d57f98bf8b7115c..707db4febac59a4d09d6420ea2add469cf54c2ec 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -471,6 +471,7 @@ public class ChunkProviderServer extends IChunkProvider {
                 this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
                 com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z);
                 // Paper end
+                com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.world, x, z); // Paper - sync load info
                 this.world.timings.syncChunkLoad.startTiming(); // Paper
             this.serverThreadQueue.awaitTasks(completablefuture::isDone);
                 com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 14ca9210e0501aff8e4559ddde9fd7d5db4c63c7..57f544eb6b2e3362b62e8541642bfc88419ab596 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1102,7 +1102,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
 
         for (int i1 = i; i1 <= j; ++i1) {
             for (int j1 = k; j1 <= l; ++j1) {
-                Chunk chunk = ichunkprovider.getChunkAt(i1, j1, false);
+                Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 
                 if (chunk != null) {
                     chunk.a(entity, axisalignedbb, list, predicate);
@@ -1123,7 +1123,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
 
         for (int i1 = i; i1 < j; ++i1) {
             for (int j1 = k; j1 < l; ++j1) {
-                Chunk chunk = this.getChunkProvider().getChunkAt(i1, j1, false);
+                Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 
                 if (chunk != null) {
                     chunk.a(entitytypes, axisalignedbb, list, predicate);
@@ -1146,7 +1146,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
 
         for (int i1 = i; i1 < j; ++i1) {
             for (int j1 = k; j1 < l; ++j1) {
-                Chunk chunk = ichunkprovider.getChunkAt(i1, j1, false);
+                Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 
                 if (chunk != null) {
                     chunk.a(oclass, axisalignedbb, list, predicate);
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 2c61b97ed1571f2032b29b142a839f012a0c2cac..89dd2695d829024aa3dc48eec5c7dcc114ce3d92 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -165,6 +165,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
     };
     public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
     // Paper end
+    // Paper start
+    @Override
+    public boolean isChunkLoaded(int x, int z) {
+        return this.getChunkProvider().getChunkAtIfLoadedImmediately(x, z) != null;
+    }
+    // Paper end
 
     // Add env and gen to constructor, WorldData -> WorldDataServer
     public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey<World> resourcekey, ResourceKey<DimensionManager> resourcekey1, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<MobSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {