2020-05-06 11:48:49 +02:00
|
|
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
2019-08-19 01:40:04 +02:00
|
|
|
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
|
2020-05-20 11:11:57 +02:00
|
|
|
index 331493a172f58e71b464d635efdba461082bd27d..182b440ba4802d199b8e44f7779b3401ace495d5 100644
|
2019-08-19 01:40:04 +02:00
|
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
2020-05-20 11:11:57 +02:00
|
|
|
@@ -1,10 +1,14 @@
|
2019-08-19 01:40:04 +02:00
|
|
|
package com.destroystokyo.paper;
|
|
|
|
|
2020-05-20 11:11:57 +02:00
|
|
|
import com.destroystokyo.paper.io.chunk.ChunkTaskManager;
|
2019-08-19 01:40:04 +02:00
|
|
|
+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;
|
2020-05-20 11:11:57 +02:00
|
|
|
@@ -19,6 +23,9 @@ import org.bukkit.craftbukkit.CraftWorld;
|
2019-08-19 01:40:04 +02:00
|
|
|
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.*;
|
2020-05-20 11:11:57 +02:00
|
|
|
@@ -29,14 +36,14 @@ public class PaperCommand extends Command {
|
2020-04-29 10:14:47 +02:00
|
|
|
public PaperCommand(String name) {
|
|
|
|
super(name);
|
|
|
|
this.description = "Paper related commands";
|
2020-05-20 11:11:57 +02:00
|
|
|
- this.usageMessage = "/paper [heap | entity | reload | version | debug | dumpwaiting | chunkinfo]";
|
|
|
|
+ this.usageMessage = "/paper [heap | entity | reload | version | debug | dumpwaiting | chunkinfo | syncloadinfo]";
|
2020-04-29 10:14:47 +02:00
|
|
|
this.setPermission("bukkit.command.paper");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
|
|
|
|
if (args.length <= 1)
|
2020-05-20 11:11:57 +02:00
|
|
|
- return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "dumpwaiting", "chunkinfo");
|
|
|
|
+ return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "dumpwaiting", "chunkinfo", "syncloadinfo");
|
2020-04-29 10:14:47 +02:00
|
|
|
|
|
|
|
switch (args[0].toLowerCase(Locale.ENGLISH))
|
|
|
|
{
|
2020-05-20 11:11:57 +02:00
|
|
|
@@ -134,6 +141,9 @@ public class PaperCommand extends Command {
|
2019-08-19 01:40:04 +02:00
|
|
|
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");
|
2020-05-20 11:11:57 +02:00
|
|
|
@@ -150,6 +160,40 @@ public class PaperCommand extends Command {
|
2019-08-19 01:40:04 +02:00
|
|
|
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
|
2020-06-26 01:38:24 +02:00
|
|
|
index 0000000000000000000000000000000000000000..1a68a8012f83bab9e814159c76b8c3710c7b1112
|
2019-08-19 01:40:04 +02:00
|
|
|
--- /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;
|
|
|
|
+
|
2020-06-26 01:38:24 +02:00
|
|
|
+ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) {
|
2019-08-19 01:40:04 +02:00
|
|
|
+ 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
|
2020-06-26 01:38:24 +02:00
|
|
|
index edd901bb53385fa3d189a0057d57f98bf8b7115c..707db4febac59a4d09d6420ea2add469cf54c2ec 100644
|
2019-08-19 01:40:04 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
2020-06-26 01:38:24 +02:00
|
|
|
@@ -471,6 +471,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
2019-08-19 01:40:04 +02:00
|
|
|
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
|
2020-04-26 08:05:41 +02:00
|
|
|
this.world.timings.syncChunkLoad.startTiming(); // Paper
|
2019-08-19 01:40:04 +02:00
|
|
|
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
|
2020-06-26 01:38:24 +02:00
|
|
|
index 4564c6ebb3aaed1ca63ff26524bba2edda551f85..ddd66c2519d67585df06d68612a0c8a8830669ac 100644
|
2019-08-19 01:40:04 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
2020-06-26 01:38:24 +02:00
|
|
|
@@ -1101,7 +1101,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
2019-08-19 01:40:04 +02:00
|
|
|
|
|
|
|
for (int i1 = i; i1 <= j; ++i1) {
|
|
|
|
for (int j1 = k; j1 <= l; ++j1) {
|
2020-06-26 01:38:24 +02:00
|
|
|
- Chunk chunk = ichunkprovider.getChunkAt(i1, j1, false);
|
2019-08-19 01:40:04 +02:00
|
|
|
+ Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
|
|
|
|
|
|
|
|
if (chunk != null) {
|
|
|
|
chunk.a(entity, axisalignedbb, list, predicate);
|
2020-06-26 01:38:24 +02:00
|
|
|
@@ -1122,7 +1122,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
2019-08-19 01:40:04 +02:00
|
|
|
|
|
|
|
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);
|
2020-06-26 01:38:24 +02:00
|
|
|
@@ -1145,7 +1145,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
2019-08-19 01:40:04 +02:00
|
|
|
|
|
|
|
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);
|
2019-09-02 00:17:51 +02:00
|
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
2020-06-26 01:38:24 +02:00
|
|
|
index 3e42d52d2256c85b7dcbbd0e363fa1e1c789b1bf..0dd249f94905c30b5320109a3f0b17cac70def8d 100644
|
2019-09-02 00:17:51 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
2020-06-26 01:38:24 +02:00
|
|
|
@@ -166,6 +166,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
2019-09-02 00:17:51 +02:00
|
|
|
};
|
|
|
|
public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
|
|
|
|
// Paper end
|
|
|
|
+ // Paper start
|
|
|
|
+ @Override
|
|
|
|
+ public boolean isChunkLoaded(int x, int z) {
|
2019-10-18 17:05:45 +02:00
|
|
|
+ return this.getChunkProvider().getChunkAtIfLoadedImmediately(x, z) != null;
|
2019-09-02 00:17:51 +02:00
|
|
|
+ }
|
|
|
|
+ // Paper end
|
|
|
|
|
2020-06-26 01:38:24 +02:00
|
|
|
// 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) {
|