mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-28 23:38:25 +01:00
69ee95fa42
PaperMC believes that 1.16.2 is now ready for general release as we fixed the main issue plagueing the 1.16.x release, the MapLike data conversion issues. Until now, it was not safe for a server to convert a world to 1.16.2 without data conversion issues around villages and potentially other things. If you did, those MapLike errors meant something went wrong. This is now resolved. Big thanks to all those that helped, notably @BillyGalbreath and @Proximyst who did large parts of the update process with me. Please as always, backup your worlds and test before updating to 1.16.2! If you update to 1.16.2, there is no going back to an older build than this. --------------------------------- Co-authored-by: William Blake Galbreath <Blake.Galbreath@GMail.com> Co-authored-by: Mariell Hoversholm <proximyst@proximyst.com> Co-authored-by: krolik-exe <69214078+krolik-exe@users.noreply.github.com> Co-authored-by: BillyGalbreath <BillyGalbreath@users.noreply.github.com> Co-authored-by: stonar96 <minecraft.stonar96@gmail.com> Co-authored-by: Shane Freeder <theboyetronic@gmail.com> Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Co-authored-by: Riley Park <rileysebastianpark@gmail.com> Co-authored-by: Aurora <21148213+aurorasmiles@users.noreply.github.com> Co-authored-by: Nassim Jahnke <nassim@njahnke.dev> Co-authored-by: commandblockguy <commandblockguy1@gmail.com> Co-authored-by: DigitalRegent <misterwener@gmail.com> Co-authored-by: ishland <ishlandmc@yeah.net>
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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
@@ -0,0 +0,0 @@
|
|
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;
|
|
@@ -0,0 +0,0 @@ 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.*;
|
|
@@ -0,0 +0,0 @@ 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))
|
|
{
|
|
@@ -0,0 +0,0 @@ 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");
|
|
@@ -0,0 +0,0 @@ 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..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
|
|
@@ -0,0 +0,0 @@
|
|
+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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -0,0 +0,0 @@ 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);
|
|
@@ -0,0 +0,0 @@ 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);
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -0,0 +0,0 @@ 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, 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) {
|