Yatopia/patches/server/0010-Player-saving-async-FileIO.patch
Ivan Pekov 5f55124016
Initial 1.16.2 support
This update had major internal changes, which took us 8 hours to figure out and resolve all things untill we have a successful build.
YatopiaMC members wish you happy playing using Yatopia for your server software

MAKE A BACKUP OF YOUR WORLD BEFORE RUNNING IT ON YOUR SERVER. YOU HAVE BEEN WARNED.
People have reported to paper that after upgrading villagers are gone. There could be even more issues we are unknown of.
MAKE A BACKUP OF YOUR WORLD BEFORE RUNNING IT ON YOUR SERVER. YOU HAVE BEEN WARNED.

Co-authored-by: Ovydux <68059159+Ovydux@users.noreply.github.com>
Co-authored-by: Simon Gardling <Titaniumtown@gmail.com>
Co-authored-by: budgidiere <sgidiere@gmail.com>
2020-08-13 18:53:32 +03:00

211 lines
10 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: tr7zw <tr7zw@live.de>
Date: Fri, 31 Jul 2020 22:39:45 -0500
Subject: [PATCH] Player-saving-async-FileIO
diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java
index cf539c98073b475eb5b769c8cc11d48a7e6d58f1..b19c702bae3e750bee13c8b1b3eaa62f0d3ba1ae 100644
--- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java
+++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java
@@ -50,6 +50,7 @@ public class AdvancementDataPlayer {
@Nullable
private Advancement l;
private boolean m = true;
+ public static java.util.concurrent.ExecutorService saveThread = java.util.concurrent.Executors.newSingleThreadExecutor(); // Yatopia
// Paper start - fix advancement data player leakage
final Map<CriterionTriggerAbstract, Set<CriterionTrigger.a>> criterionData = Maps.newIdentityHashMap();
@@ -228,6 +229,15 @@ public class AdvancementDataPlayer {
jsonelement.getAsJsonObject().addProperty("DataVersion", SharedConstants.getGameVersion().getWorldVersion());
+ // Yatopia start - replace whole logic
+ saveThread.submit(() -> {
+ try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(f), Charsets.UTF_8.newEncoder())) {
+ AdvancementDataPlayer.b.toJson(jsonelement, writer);
+ } catch (Throwable e) {
+ AdvancementDataPlayer.LOGGER.error("Couldn't save player advancements to {}", this.f, e);
+ }
+ });
+ /*
try {
FileOutputStream fileoutputstream = new FileOutputStream(this.f);
Throwable throwable = null;
@@ -275,7 +285,7 @@ public class AdvancementDataPlayer {
} catch (IOException ioexception) {
AdvancementDataPlayer.LOGGER.error("Couldn't save player advancements to {}", this.f, ioexception);
}
-
+ */ // Yatopia end
}
public boolean grantCriteria(Advancement advancement, String s) {
diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java
index ce4ebc96c01f3dacf4e4d0569d86f52140440d43..a52c0391b171c8a57de75f87c534ce1e0e78c44a 100644
--- a/src/main/java/net/minecraft/server/EntityHuman.java
+++ b/src/main/java/net/minecraft/server/EntityHuman.java
@@ -700,11 +700,20 @@ public abstract class EntityHuman extends EntityLiving {
}
+ // Yatopia start
+ private NBTTagList inventorySnapshot = null;
+ private NBTTagList enderchestSnapshot = null;
+ public void takeSnapshot() {
+ inventorySnapshot = this.inventory.a(new NBTTagList());
+ enderchestSnapshot = this.enderChest.g();
+ }
+ // Yatopia end
+
@Override
public void saveData(NBTTagCompound nbttagcompound) {
super.saveData(nbttagcompound);
nbttagcompound.setInt("DataVersion", SharedConstants.getGameVersion().getWorldVersion());
- nbttagcompound.set("Inventory", this.inventory.a(new NBTTagList()));
+ nbttagcompound.set("Inventory", inventorySnapshot != null ? inventorySnapshot : this.inventory.a(new NBTTagList())); inventorySnapshot = null; // Yatopia
nbttagcompound.setInt("SelectedItemSlot", this.inventory.itemInHandIndex);
nbttagcompound.setShort("SleepTimer", (short) this.sleepTicks);
nbttagcompound.setFloat("XpP", this.exp);
@@ -714,7 +723,7 @@ public abstract class EntityHuman extends EntityLiving {
nbttagcompound.setInt("Score", this.getScore());
this.foodData.b(nbttagcompound);
this.abilities.a(nbttagcompound);
- nbttagcompound.set("EnderItems", this.enderChest.g());
+ nbttagcompound.set("EnderItems", enderchestSnapshot != null ? enderchestSnapshot : this.enderChest.g()); enderchestSnapshot = null; // Yatopia
if (!this.getShoulderEntityLeft().isEmpty()) {
nbttagcompound.set("ShoulderEntityLeft", this.getShoulderEntityLeft());
}
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 4c6e708b6d6af45159315dc6ab5fdf72320d9825..2b7fb65942f5d22737cbbf0f8e7ef2194fc607a2 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -1284,6 +1284,28 @@ public abstract class PlayerList {
if (team != null) scoreboard.removeTeam(team);
}
// Paper end
+
+ // Yatopia start - make sure all saves are done
+ try {
+ playerFileData.saveThread.shutdown();
+ boolean done = playerFileData.saveThread.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
+ if (!done) {
+ LOGGER.error("Players did not save completely!");
+ }
+ ServerStatisticManager.saveThread.shutdown();
+ done = ServerStatisticManager.saveThread.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
+ if (!done) {
+ LOGGER.error("Stats did not save completely!");
+ }
+ AdvancementDataPlayer.saveThread.shutdown();
+ done = AdvancementDataPlayer.saveThread.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
+ if (!done) {
+ LOGGER.error("Advancements did not save completely!");
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Yatopia end
}
// Paper end
@@ -1321,13 +1343,13 @@ public abstract class PlayerList {
File file = this.server.a(SavedFile.STATS).toFile();
File file1 = new File(file, uuid + ".json");
- if (!file1.exists()) {
+ /*if (!file1.exists()) { // Yatopia - don't check for old stats files with sync file IO
File file2 = new File(file, displayName + ".json"); // CraftBukkit
if (file2.exists() && file2.isFile()) {
file2.renameTo(file1);
}
- }
+ }*/ // Yatopia
serverstatisticmanager = new ServerStatisticManager(this.server, file1);
// this.o.put(uuid, serverstatisticmanager); // CraftBukkit
diff --git a/src/main/java/net/minecraft/server/ServerStatisticManager.java b/src/main/java/net/minecraft/server/ServerStatisticManager.java
index 3c3b87e37cbf69c223da007e8b7eb646ec83691e..e2d12750bce9cb5ef087412b03809632a678bc04 100644
--- a/src/main/java/net/minecraft/server/ServerStatisticManager.java
+++ b/src/main/java/net/minecraft/server/ServerStatisticManager.java
@@ -30,6 +30,7 @@ public class ServerStatisticManager extends StatisticManager {
private final File d;
private final Set<Statistic<?>> e = Sets.newHashSet();
private int f = -300;
+ public static java.util.concurrent.ExecutorService saveThread = java.util.concurrent.Executors.newSingleThreadExecutor(); // Yatopia
public ServerStatisticManager(MinecraftServer minecraftserver, File file) {
this.c = minecraftserver;
@@ -41,6 +42,7 @@ public class ServerStatisticManager extends StatisticManager {
this.a.put( wrapper, entry.getValue().intValue() );
}
// Spigot end
+ saveThread.submit(() -> { // Yatopia
if (file.isFile()) {
try {
this.a(minecraftserver.getDataFixer(), org.apache.commons.io.FileUtils.readFileToString(file));
@@ -50,17 +52,20 @@ public class ServerStatisticManager extends StatisticManager {
ServerStatisticManager.LOGGER.error("Couldn't parse statistics file {}", file, jsonparseexception);
}
}
-
+ }); // Yatopia
}
public void save() {
if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot
+ // Yatopia start
+ String str = this.b();
+ saveThread.submit(() -> {
try {
- org.apache.commons.io.FileUtils.writeStringToFile(this.d, this.b());
+ org.apache.commons.io.FileUtils.writeStringToFile(this.d, str);
} catch (IOException ioexception) {
ServerStatisticManager.LOGGER.error("Couldn't save stats", ioexception);
}
-
+ }); // Yatopia end
}
@Override
@@ -111,7 +116,7 @@ public class ServerStatisticManager extends StatisticManager {
if (nbttagcompound2.hasKeyOfType(s2, 99)) {
SystemUtils.a(this.a(statisticwrapper, s2), (statistic) -> {
- this.a.put(statistic, nbttagcompound2.getInt(s2));
+ this.a.put(statistic, nbttagcompound2.getInt(s2) + this.a.getOrDefault(statistic, 0)); // Yatopia
}, () -> {
ServerStatisticManager.LOGGER.warn("Invalid statistic in {}: Don't know what {} is", this.d, s2);
});
diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java
index a959672f5857b987001252c3fd7ace9e83e07c9b..bcae104ac104d6dcdf8653f291b24601247b5a55 100644
--- a/src/main/java/net/minecraft/server/WorldNBTStorage.java
+++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java
@@ -17,6 +17,7 @@ public class WorldNBTStorage {
private static final Logger LOGGER = LogManager.getLogger();
private final File playerDir;
protected final DataFixer a;
+ public java.util.concurrent.ExecutorService saveThread = java.util.concurrent.Executors.newSingleThreadExecutor(); // Yatopia
public WorldNBTStorage(Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer) {
this.a = datafixer;
@@ -26,6 +27,8 @@ public class WorldNBTStorage {
public void save(EntityHuman entityhuman) {
if(!com.destroystokyo.paper.PaperConfig.savePlayerData) return; // Paper - Make player data saving configurable
+ entityhuman.takeSnapshot(); // Yatopia
+ saveThread.submit(() -> { // Yatopia
try {
NBTTagCompound nbttagcompound = entityhuman.save(new NBTTagCompound());
File file = File.createTempFile(entityhuman.getUniqueIDString() + "-", ".dat", this.playerDir);
@@ -38,7 +41,7 @@ public class WorldNBTStorage {
} catch (Exception exception) {
WorldNBTStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper
}
-
+ }); // Yatopia
}
@Nullable