9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2026-01-04 15:41:40 +00:00
Files
Leaf/leaf-server/minecraft-patches/features/0086-Nitori-Async-playerdata-saving.patch
2025-06-08 19:05:34 +09:00

318 lines
17 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/network/Connection.java b/net/minecraft/network/Connection.java
index 00a82873d226f113278632a53c0faca420dd67d4..2c4423eb2d465c2782a8dab851619ce539f69ae8 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -586,7 +586,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
// Paper end - Optimize network
private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
- private static int joinAttemptsThisTick; // Paper - Buffer joins to world
+ public static int joinAttemptsThisTick; // Paper - Buffer joins to world // Leaf - Async player IO
private static int currTick; // Paper - Buffer joins to world
private static int tickSecond; // Purpur - Max joins per second
public void tick() {
diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java
index d2159a747fe42aa95cfc6bca0e55e3f4485847bb..d5196b181a0a633cb04ce18b0471cda2dcaa8816 100644
--- a/net/minecraft/server/PlayerAdvancements.java
+++ b/net/minecraft/server/PlayerAdvancements.java
@@ -111,6 +111,7 @@ public class PlayerAdvancements {
}
private void load(ServerAdvancementManager manager) {
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.blockAdvancements(player.getUUID(), player.getScoreboardName()); // Leaf - Async player IO
if (Files.isRegularFile(this.playerSavePath)) {
try (JsonReader jsonReader = new JsonReader(Files.newBufferedReader(this.playerSavePath, StandardCharsets.UTF_8))) {
jsonReader.setLenient(false);
@@ -133,12 +134,18 @@ public class PlayerAdvancements {
JsonElement jsonElement = this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow();
try {
- FileUtil.createDirectoriesSafe(this.playerSavePath.getParent());
-
- try (Writer bufferedWriter = Files.newBufferedWriter(this.playerSavePath, StandardCharsets.UTF_8)) {
- GSON.toJson(jsonElement, GSON.newJsonWriter(bufferedWriter));
- }
- } catch (JsonIOException | IOException var7) {
+ // Leaf start - Async player IO
+ String content = GSON.toJson(jsonElement);
+ final Path path = this.playerSavePath;
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.submitAdvancements(
+ this.player.getUUID(),
+ this.player.getScoreboardName(),
+ () -> {
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.safeReplace(path, content);
+ return null;
+ });
+ } catch (JsonIOException /*| IOException*/ var7) {
+ // Leaf end - Async player IO
LOGGER.error("Couldn't save player advancements to {}", this.playerSavePath, var7);
}
}
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 114b25f933c6a1b011523581a5a02a5a2c1e827e..3da6dad3dd0f4c5750609b382f47a6cd14f18e7a 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -79,6 +79,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
.expireAfterWrite(org.dreeam.leaf.config.modules.misc.Cache.cachePlayerProfileResultTimeout, java.util.concurrent.TimeUnit.MINUTES)
.build();
// Leaf end - Cache player profileResult
+ @Nullable public java.util.UUID[] duplicateDisconnect = null; // Leaf - Async player IO
public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
this.server = server;
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 75fb49f1596f475278d12c8c7aea9ad4952b6056..52a0fa425a30caa2e592c0fdda44800da169c2a0 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -782,6 +782,31 @@ public abstract class PlayerList {
// UserBanListEntry userBanListEntry = this.bans.get(gameProfile);
// Moved from processLogin
UUID uuid = gameProfile.getId();
+
+ // Leaf start - Async player IO
+ if (org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.enabled) {
+ if (org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.isSaving(uuid)) {
+ if (Connection.joinAttemptsThisTick != 0) Connection.joinAttemptsThisTick -= 1;
+ return null;
+ }
+ if (loginlistener.duplicateDisconnect != null
+ && loginlistener.duplicateDisconnect.length != 0) {
+ // check last one
+ var last = loginlistener.duplicateDisconnect[loginlistener.duplicateDisconnect.length - 1];
+ if (org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.isSaving(last)) {
+ if (Connection.joinAttemptsThisTick != 0) Connection.joinAttemptsThisTick -= 1;
+ return null;
+ }
+ for (UUID uuid1 : loginlistener.duplicateDisconnect) {
+ if (org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.isSaving(uuid1)) {
+ if (Connection.joinAttemptsThisTick != 0) Connection.joinAttemptsThisTick -= 1;
+ return null;
+ }
+ }
+ loginlistener.duplicateDisconnect = null;
+ }
+ }
+ // Leaf end - Async player IO
List<ServerPlayer> list = Lists.newArrayList();
ServerPlayer entityplayer;
@@ -793,6 +818,23 @@ public abstract class PlayerList {
}
}
+ // Leaf start - Async player IO
+ if (org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.enabled && !list.isEmpty()) {
+ loginlistener.duplicateDisconnect = new UUID[list.size()];
+ java.util.Iterator<ServerPlayer> iterator = list.iterator();
+
+ int index = 0;
+ while (iterator.hasNext()) {
+ entityplayer = iterator.next();
+ // this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved
+ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause
+ loginlistener.duplicateDisconnect[index] = entityplayer.getUUID();
+ index++;
+ }
+ if (Connection.joinAttemptsThisTick != 0) Connection.joinAttemptsThisTick -= 1;
+ return null;
+ }
+ // Leaf end - Async player IO
java.util.Iterator iterator = list.iterator();
while (iterator.hasNext()) {
@@ -1582,7 +1624,7 @@ public abstract class PlayerList {
*/
// Leaf end - Remove useless creating stats json bases on player name logic
- serverStatsCounter = new ServerStatsCounter(this.server, file1);
+ serverStatsCounter = new ServerStatsCounter(this.server, file1, displayName, uuid);
// this.stats.put(uuid, serverStatsCounter); // CraftBukkit
}
diff --git a/net/minecraft/stats/ServerStatsCounter.java b/net/minecraft/stats/ServerStatsCounter.java
index b26dbe807e5cb0a42f6c06b933397902310e5616..a682ae890850a5e62acd54029f2a4eb1e69d200a 100644
--- a/net/minecraft/stats/ServerStatsCounter.java
+++ b/net/minecraft/stats/ServerStatsCounter.java
@@ -39,12 +39,31 @@ public class ServerStatsCounter extends StatsCounter {
private final File file;
private final Set<Stat<?>> dirty = Sets.newHashSet();
+ // Leaf start - Async player IO
+ private final String name;
+ private final java.util.UUID uuid;
+ @Deprecated(forRemoval = true)
public ServerStatsCounter(MinecraftServer server, File file) {
+ this(server, file, "UNKNOWN", parseUUID(file));
+ }
+ private static java.util.UUID parseUUID(File file) {
+ try {
+ return java.util.UUID.fromString(org.apache.commons.io.FilenameUtils.getBaseName(file.toString()));
+ } catch (IllegalArgumentException e) {
+ LOGGER.error("failed parse uuid {}", file, e);
+ return net.minecraft.Util.NIL_UUID;
+ }
+ }
+ public ServerStatsCounter(MinecraftServer server, File file, String name, java.util.UUID uuid) {
+ this.name = name;
+ this.uuid = uuid;
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.blockStats(uuid, name);
+ // Leaf end - Async player IO
this.server = server;
this.file = file;
if (file.isFile()) {
try {
- this.parseLocal(server.getFixerUpper(), FileUtils.readFileToString(file));
+ this.parseLocal(server.getFixerUpper(), FileUtils.readFileToString(file, java.nio.charset.StandardCharsets.UTF_8)); // Leaf - UTF-8
} catch (IOException var4) {
LOGGER.error("Couldn't read statistics file {}", file, var4);
} catch (JsonParseException var5) {
@@ -66,11 +85,37 @@ public class ServerStatsCounter extends StatsCounter {
public void save() {
if (org.spigotmc.SpigotConfig.disableStatSaving) return; // Spigot
+ // Leaf start - Async player IO
+ Map<StatType<?>, JsonObject> map = Maps.newHashMap();
+ for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<Stat<?>> entry : this.stats.object2IntEntrySet()) {
+ Stat<?> stat = entry.getKey();
+ map.computeIfAbsent(stat.getType(), type -> new JsonObject()).addProperty(getKey(stat).toString(), entry.getIntValue());
+ }
+ final File file = this.file;
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.submitStats(
+ uuid,
+ name,
+ () -> {
+ JsonObject jsonObject = new JsonObject();
+
+ for (Entry<StatType<?>, JsonObject> entry1 : map.entrySet()) {
+ jsonObject.add(BuiltInRegistries.STAT_TYPE.getKey(entry1.getKey()).toString(), entry1.getValue());
+ }
+
+ JsonObject jsonObject1 = new JsonObject();
+ jsonObject1.add("stats", jsonObject);
+ jsonObject1.addProperty("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion());
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.safeReplace(file.toPath(), jsonObject1.toString());
+ return null;
+ });
+ /*
try {
FileUtils.writeStringToFile(this.file, this.toJson());
} catch (IOException var2) {
LOGGER.error("Couldn't save stats", (Throwable)var2);
}
+ */
+ // Leaf end - Async player IO
}
@Override
diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
index c44110b123ba5912af18faf0065e9ded780da9b7..2eae5ccb37b942b94964c28391b96989ae85b072 100644
--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
+++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
@@ -34,19 +34,37 @@ public class PlayerDataStorage {
public void save(Player player) {
if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
+ // Leaf start - Async player IO
+ 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 player IO
}
+ // Leaf start - Async player IO
+ public void save(String playerName, java.util.UUID uuid, String stringId, CompoundTag compoundTag) {
+ final File playerDir = this.playerDir;
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.submitEntity(
+ uuid,
+ playerName,
+ () -> {
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
+ NbtIo.writeCompressed(compoundTag, nbtBytes);
+ Path path = playerDir.toPath();
+
+ Path current = path.resolve(stringId + ".dat");
+ Path old = path.resolve(stringId + ".dat_old");
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.safeReplaceBackup(current, old, nbtBytes.array, 0, nbtBytes.length);
+ return null;
+ });
+ }
+ // Leaf end - Async player IO
+
private void backup(String name, String stringUuid, String suffix) { // CraftBukkit
Path path = this.playerDir.toPath();
Path path1 = path.resolve(stringUuid + suffix); // CraftBukkit
@@ -60,7 +78,13 @@ public class PlayerDataStorage {
}
}
- private Optional<CompoundTag> load(String name, String stringUuid, String suffix) { // CraftBukkit
+ // Leaf start - Async player IO
+ 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
+ org.dreeam.leaf.async.storage.AsyncPlayerDataSaving.INSTANCE.blockEntity(playerUuid, name);
+ // Leaf end - Async player IO
File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit
// Spigot start
boolean usingWrongFile = false;
@@ -91,7 +115,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 player IO
if (player instanceof ServerPlayer serverPlayer) {
CraftPlayer craftPlayer = serverPlayer.getBukkitEntity();
// Only update first played if it is older than the one we have
@@ -106,20 +130,25 @@ public class PlayerDataStorage {
});
}
+ // Leaf start - Async player IO
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 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, compoundTag, dataVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - rewrite data conversion system
// player.load(compoundTag); // CraftBukkit - handled above
return compoundTag;
});
}
+ // Leaf end - Async player IO
// CraftBukkit start
public File getPlayerDir() {