This also cleans up the implementation of Async Chunks to get rid of most Consumer callbacks and instead return futures. This lets us propogate errors correctly up the future chain (barring one isn't lost even deeper in the chain...) So exceptions can now bubble to plugins using getChunkAtAsync
347 lines
15 KiB
Diff
347 lines
15 KiB
Diff
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 af810987846efcd2bffbd23c31481b2d31c168dd..ddb60e9a48e5e7225ad575240b94fda24b6b78ca 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
@@ -1,9 +1,13 @@
|
|
package com.destroystokyo.paper;
|
|
|
|
+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;
|
|
@@ -18,6 +22,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.*;
|
|
@@ -28,14 +35,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 | chunkinfo]";
|
|
+ this.usageMessage = "/paper [heap | entity | reload | version | debug | 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", "chunkinfo");
|
|
+ return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "chunkinfo", "syncloadinfo");
|
|
|
|
switch (args[0].toLowerCase(Locale.ENGLISH))
|
|
{
|
|
@@ -130,6 +137,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");
|
|
@@ -146,6 +156,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..59aec103295f747793fdc0a52eb45f4121aba921
|
|
--- /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) {
|
|
+ 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 cabe2a5908dd9ee721c13c1825e65a37f72361d4..61640d814426732a03d8bb3394c2cd3414a27a6c 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -458,6 +458,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 311685180fe720706dfb6c82b1b54f9876187b73..568e04faa314552e14286efdfcdfb79e682fda95 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -1173,7 +1173,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(entity, axisalignedbb, list, predicate);
|
|
@@ -1194,7 +1194,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);
|
|
@@ -1217,7 +1217,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 30dbf95a02bd8e68471400245b46189f8e0560cc..4fea35783caa4d93de83e256b565f0e80212e9a5 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -154,6 +154,12 @@ public class WorldServer extends World {
|
|
};
|
|
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
|
|
public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
|