9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
Files
Leaf/leaf-server/minecraft-patches/features/0176-Nitori-Async-playerdata-saving.patch
2025-06-03 09:49:56 +08:00

219 lines
11 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Fri, 23 Aug 2024 22:04:20 -0400
Subject: [PATCH] Nitori: Async playerdata saving
Original license: GPL v3
Original project: https://github.com/Gensokyo-Reimagined/Nitori
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 3ca7405318a4076000250203be4b91dbde95191b..2aed6f594c73bea5e8919e65cf04bc7eb6775c3e 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1067,6 +1067,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
this.onServerExit();
// Paper end - Improved watchdog support - move final shutdown items here
+ // Leaf start - Async playerdata saving
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.IO_POOL.shutdown();
+ try {
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.IO_POOL.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
+ } catch (java.lang.InterruptedException ignored) {}
+ // Leaf end - Async playerdata saving
}
public String getLocalIp() {
diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java
index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..169b4544ab3d0e8515a2d7020a23ae0e2e0c952d 100644
--- a/net/minecraft/world/level/storage/LevelStorageSource.java
+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
@@ -521,15 +521,26 @@ public class LevelStorageSource {
private void saveLevelData(CompoundTag tag) {
Path path = this.levelDirectory.path();
+ // Leaf start - Async playerdata saving
+ // Save level.dat asynchronously
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
try {
- Path path1 = Files.createTempFile(path, "level", ".dat");
- NbtIo.writeCompressed(tag, path1);
- Path path2 = this.levelDirectory.oldDataFile();
- Path path3 = this.levelDirectory.dataFile();
- Util.safeReplaceFile(path3, path1, path2);
+ NbtIo.writeCompressed(tag, nbtBytes);
} catch (Exception var6) {
- LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
+ LevelStorageSource.LOGGER.error("Failed to encode level {}", path, var6);
}
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
+ try {
+ Path path1 = Files.createTempFile(path, "level", ".dat");
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
+ Path path2 = this.levelDirectory.oldDataFile();
+ Path path3 = this.levelDirectory.dataFile();
+ Util.safeReplaceFile(path3, path1, path2);
+ } catch (Exception var6) {
+ LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
+ }
+ });
+ // Leaf end - Async playerdata saving
}
public Optional<Path> getIconFile() {
diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
index ab9282c04c1996b037567d07f95e2b150bcfcd38..7a39ea109dee258e7fb83982572ee18731b5446f 100644
--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
+++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
@@ -23,6 +23,7 @@ public class PlayerDataStorage {
private final File playerDir;
protected final DataFixer fixerUpper;
private static final DateTimeFormatter FORMATTER = FileNameDateFormatter.create();
+ private final java.util.Map<java.util.UUID, java.util.concurrent.Future<?>> savingLocks = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // Leaf - Async playerdata saving
public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) {
this.fixerUpper = fixerUpper;
@@ -32,19 +33,82 @@ public class PlayerDataStorage {
public void save(Player player) {
if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
+ // Leaf start - Async playerdata saving
+ CompoundTag compoundTag;
try {
- CompoundTag compoundTag = player.saveWithoutId(new CompoundTag());
- Path path = this.playerDir.toPath();
- Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat");
- NbtIo.writeCompressed(compoundTag, path1);
- Path path2 = path.resolve(player.getStringUUID() + ".dat");
- Path path3 = path.resolve(player.getStringUUID() + ".dat_old");
- Util.safeReplaceFile(path2, path1, path3);
- } catch (Exception var7) {
- LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), var7); // Paper - Print exception
+ compoundTag = player.saveWithoutId(new CompoundTag());
+ } catch (Exception exception) {
+ LOGGER.warn("Failed to encode player data for {}", player.getScoreboardName(), exception);
+ return;
}
+ save(player.getScoreboardName(), player.getUUID(), player.getStringUUID(), compoundTag);
+ // Leaf end - Async playerdata saving
}
+ // Leaf start - Async playerdata saving
+ public void save(String playerName, java.util.UUID uniqueId, String stringId, CompoundTag compoundTag) {
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
+ try {
+ NbtIo.writeCompressed(compoundTag, nbtBytes);
+ } catch (Exception exception) {
+ LOGGER.warn("Failed to encode player data for {}", stringId, exception);
+ }
+ lockFor(uniqueId, playerName);
+ synchronized (PlayerDataStorage.this) {
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
+ try {
+ Path path = this.playerDir.toPath();
+ Path path1 = Files.createTempFile(path, stringId + "-", ".dat");
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
+ Path path2 = path.resolve(stringId + ".dat");
+ Path path3 = path.resolve(stringId + ".dat_old");
+ Util.safeReplaceFile(path2, path1, path3);
+ } catch (Exception var7) {
+ LOGGER.warn("Failed to save player data for {}", playerName, var7);
+ } finally {
+ synchronized (PlayerDataStorage.this) {
+ savingLocks.remove(uniqueId);
+ }
+ }
+ }).ifPresent(future -> savingLocks.put(uniqueId, future));
+ }
+ }
+
+ private void lockFor(java.util.UUID uniqueId, String playerName) {
+ java.util.concurrent.Future<?> fut;
+ synchronized (this) {
+ fut = savingLocks.get(uniqueId);
+ }
+ if (fut == null) {
+ return;
+ }
+ while (true) {
+ try {
+ fut.get(10_000L, java.util.concurrent.TimeUnit.MILLISECONDS);
+ break;
+ } catch (InterruptedException ignored) {
+ } catch (java.util.concurrent.ExecutionException
+ | java.util.concurrent.TimeoutException exception) {
+ LOGGER.warn("Failed to save player data for {}", playerName, exception);
+
+ String threadDump = "";
+ var threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
+ for (var threadInfo : threadMXBean.dumpAllThreads(true, true)) {
+ if (threadInfo.getThreadName().equals("Leaf IO Thread")) {
+ threadDump = threadInfo.toString();
+ break;
+ }
+ }
+ LOGGER.warn(threadDump);
+ fut.cancel(true);
+ break;
+ } finally {
+ savingLocks.remove(uniqueId);
+ }
+ }
+ }
+ // Leaf end - Async playerdata saving
+
private void backup(String name, String stringUuid, String suffix) { // CraftBukkit
Path path = this.playerDir.toPath();
Path path1 = path.resolve(stringUuid + suffix); // CraftBukkit
@@ -58,7 +122,13 @@ public class PlayerDataStorage {
}
}
- private Optional<CompoundTag> load(String name, String stringUuid, String suffix) { // CraftBukkit
+ // Leaf start - Async playerdata saving
+ private Optional<CompoundTag> load(String name, String stringUuid, String suffix) {
+ return load(name, stringUuid, suffix, java.util.UUID.fromString(stringUuid));
+ }
+ private Optional<CompoundTag> load(String name, String stringUuid, String suffix, java.util.UUID playerUuid) { // CraftBukkit
+ lockFor(playerUuid, name);
+ // Leaf end - Async playerdata saving
File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit
// Spigot start
boolean usingWrongFile = false;
@@ -89,7 +159,7 @@ public class PlayerDataStorage {
public Optional<CompoundTag> load(Player player) {
// CraftBukkit start
- return this.load(player.getName().getString(), player.getStringUUID()).map((tag) -> {
+ return this.load(player.getName().getString(), player.getStringUUID(), player.getUUID()).map((tag) -> { // Leaf - Async playerdata saving
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer = serverPlayer.getBukkitEntity();
// Only update first played if it is older than the one we have
@@ -104,20 +174,25 @@ public class PlayerDataStorage {
});
}
+ // Leaf start - Async playerdata saving
public Optional<CompoundTag> load(String name, String uuid) {
+ return this.load(name, uuid, java.util.UUID.fromString(uuid));
+ }
+ public Optional<CompoundTag> load(String name, String uuid, java.util.UUID playerUuid) {
// CraftBukkit end
- Optional<CompoundTag> optional = this.load(name, uuid, ".dat"); // CraftBukkit
+ Optional<CompoundTag> optional = this.load(name, uuid, ".dat", playerUuid); // CraftBukkit
if (optional.isEmpty()) {
this.backup(name, uuid, ".dat"); // CraftBukkit
}
- return optional.or(() -> this.load(name, uuid, ".dat_old")).map(compoundTag -> { // CraftBukkit
+ return optional.or(() -> this.load(name, uuid, ".dat_old", playerUuid)).map(compoundTag -> { // CraftBukkit
int dataVersion = NbtUtils.getDataVersion(compoundTag, -1);
compoundTag = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundTag, dataVersion);
// player.load(compoundTag); // CraftBukkit - handled above
return compoundTag;
});
}
+ // Leaf end - Async playerdata saving
// CraftBukkit start
public File getPlayerDir() {