Skip to content

Commit ed4b36f

Browse files
committed
async save file
1 parent 9211f21 commit ed4b36f

7 files changed

Lines changed: 297 additions & 166 deletions

File tree

leaf-server/minecraft-patches/features/0160-Nitori-Async-playerdata-saving.patch

Lines changed: 123 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -3,157 +3,162 @@ From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
33
Date: Fri, 23 Aug 2024 22:04:20 -0400
44
Subject: [PATCH] Nitori: Async playerdata saving
55

6-
Original license: GPL v3
6+
Original license: GPL-3.0
77
Original project: https://github.com/Gensokyo-Reimagined/Nitori
88

9+
diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java
10+
index 78135cf45c8900eb142933d216744f4a73127965..14e33218bb9bd3e1f8484c88114900297ec65c1e 100644
11+
--- a/net/minecraft/server/PlayerAdvancements.java
12+
+++ b/net/minecraft/server/PlayerAdvancements.java
13+
@@ -111,6 +111,7 @@ public class PlayerAdvancements {
14+
15+
private void load(ServerAdvancementManager manager) {
16+
if (Files.isRegularFile(this.playerSavePath)) {
17+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(playerSavePath, org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.advancements); // Leaf - Async playerdata saving
18+
try (Reader bufferedReader = Files.newBufferedReader(this.playerSavePath, StandardCharsets.UTF_8)) {
19+
JsonElement jsonElement = StrictJsonParser.parse(bufferedReader);
20+
PlayerAdvancements.Data data = this.codec.parse(JsonOps.INSTANCE, jsonElement).getOrThrow(JsonParseException::new);
21+
@@ -128,17 +129,18 @@ public class PlayerAdvancements {
22+
23+
public void save() {
24+
if (org.spigotmc.SpigotConfig.disableAdvancementSaving) return; // Spigot
25+
+ // Leaf start - Async playerdata saving
26+
JsonElement jsonElement = this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow();
27+
-
28+
- try {
29+
- FileUtil.createDirectoriesSafe(this.playerSavePath.getParent());
30+
-
31+
- try (Writer bufferedWriter = Files.newBufferedWriter(this.playerSavePath, StandardCharsets.UTF_8)) {
32+
- GSON.toJson(jsonElement, GSON.newJsonWriter(bufferedWriter));
33+
- }
34+
- } catch (JsonIOException | IOException var7) {
35+
- LOGGER.error("Couldn't save player advancements to {}", this.playerSavePath, var7);
36+
- }
37+
+ Path path = this.playerSavePath;
38+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
39+
+ FileUtil.createDirectoriesSafe(path.getParent());
40+
+ String content = GSON.toJson(jsonElement);
41+
+ Path temp = org.dreeam.leaf.async.AsyncPlayerDataSaving.tempFile(path);
42+
+ org.apache.commons.io.FileUtils.writeStringToFile(temp.toFile(), content, java.nio.charset.StandardCharsets.UTF_8, false);
43+
+ Files.move(temp, path, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
44+
+ return null;
45+
+ }, this.playerSavePath, org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.advancements);
46+
+ // Leaf end - Async playerdata saving
47+
}
48+
49+
private void applyFrom(ServerAdvancementManager advancementManager, PlayerAdvancements.Data data) {
50+
diff --git a/net/minecraft/stats/ServerStatsCounter.java b/net/minecraft/stats/ServerStatsCounter.java
51+
index f239f445f83d50a69e7e6ffb97e7cf005c421e3a..13710f2fcceba200b6df8222d2402f33f83be0da 100644
52+
--- a/net/minecraft/stats/ServerStatsCounter.java
53+
+++ b/net/minecraft/stats/ServerStatsCounter.java
54+
@@ -68,6 +68,7 @@ public class ServerStatsCounter extends StatsCounter {
55+
if (Files.isRegularFile(file)) {
56+
try (Reader bufferedReader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
57+
JsonElement jsonElement = StrictJsonParser.parse(bufferedReader);
58+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(file, org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.stats); // Leaf - Async playerdata saving
59+
this.parse(server.getFixerUpper(), jsonElement);
60+
} catch (IOException var8) {
61+
LOGGER.error("Couldn't read statistics file {}", file, var8);
62+
@@ -90,15 +91,16 @@ public class ServerStatsCounter extends StatsCounter {
63+
64+
public void save() {
65+
if (org.spigotmc.SpigotConfig.disableStatSaving) return; // Spigot
66+
- try {
67+
- FileUtil.createDirectoriesSafe(this.file.getParent());
68+
-
69+
- try (Writer bufferedWriter = Files.newBufferedWriter(this.file, StandardCharsets.UTF_8)) {
70+
- GSON.toJson(this.toJson(), GSON.newJsonWriter(bufferedWriter));
71+
- }
72+
- } catch (JsonIOException | IOException var6) {
73+
- LOGGER.error("Couldn't save stats to {}", this.file, var6);
74+
- }
75+
+ // Leaf start - Async playerdata saving
76+
+ Path file = this.file;
77+
+ JsonElement data = this.toJson();
78+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
79+
+ java.nio.file.Path temp = org.dreeam.leaf.async.AsyncPlayerDataSaving.tempFile(file);
80+
+ org.apache.commons.io.FileUtils.writeStringToFile(temp.toFile(), GSON.toJson(data), java.nio.charset.StandardCharsets.UTF_8, false);
81+
+ java.nio.file.Files.move(temp, file, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
82+
+ return null;
83+
+ }, file, org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.stats);
84+
+ // Leaf end - Async playerdata saving
85+
}
86+
87+
@Override
988
diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java
10-
index 140630186c3b0324c248cc2a2f31d3b906557b9c..d5e9190f0a6baffd2b08225f595ff4d7eb662e65 100644
89+
index 140630186c3b0324c248cc2a2f31d3b906557b9c..75feaa87159f8bf399b6d51f73ccf1c25a50265e 100644
1190
--- a/net/minecraft/world/level/storage/LevelStorageSource.java
1291
+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
13-
@@ -516,15 +516,26 @@ public class LevelStorageSource {
92+
@@ -516,15 +516,20 @@ public class LevelStorageSource {
1493
private void saveLevelData(CompoundTag tag) {
1594
Path path = this.levelDirectory.path();
1695

96+
- try {
1797
+ // Leaf start - Async playerdata saving
1898
+ // Save level.dat asynchronously
19-
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
20-
try {
21-
- Path path1 = Files.createTempFile(path, "level", ".dat");
99+
+
100+
+ Path path2 = this.levelDirectory.oldDataFile();
101+
+ Path path3 = this.levelDirectory.dataFile();
102+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
103+
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
104+
+ NbtIo.writeCompressed(tag, nbtBytes);
105+
Path path1 = Files.createTempFile(path, "level", ".dat");
22106
- NbtIo.writeCompressed(tag, path1);
23107
- Path path2 = this.levelDirectory.oldDataFile();
24108
- Path path3 = this.levelDirectory.dataFile();
25-
- Util.safeReplaceFile(path3, path1, path2);
26-
+ NbtIo.writeCompressed(tag, nbtBytes);
27-
} catch (Exception var6) {
109+
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
110+
Util.safeReplaceFile(path3, path1, path2);
111+
- } catch (Exception var6) {
28112
- LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
29-
+ LevelStorageSource.LOGGER.error("Failed to encode level {}", path, var6);
30-
}
31-
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
32-
+ try {
33-
+ Path path1 = Files.createTempFile(path, "level", ".dat");
34-
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
35-
+ Path path2 = this.levelDirectory.oldDataFile();
36-
+ Path path3 = this.levelDirectory.dataFile();
37-
+ Util.safeReplaceFile(path3, path1, path2);
38-
+ } catch (Exception var6) {
39-
+ LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
40-
+ }
41-
+ });
113+
- }
114+
+ return null;
115+
+ }, path3, org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.levelData);
42116
+ // Leaf end - Async playerdata saving
43117
}
44118

45119
public Optional<Path> getIconFile() {
46120
diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
47-
index b8ef50bc3d07890c9da2c98d5f009a3adc52f4b0..4099f4ebf76c6bf74384aa5b9f5e889d53ebcfa9 100644
121+
index b8ef50bc3d07890c9da2c98d5f009a3adc52f4b0..11055fa067933083af5fca1c9d9e45435684f23b 100644
48122
--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
49123
+++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
50-
@@ -23,6 +23,7 @@ public class PlayerDataStorage {
51-
private static final Logger LOGGER = LogUtils.getLogger();
52-
private final File playerDir;
53-
protected final DataFixer fixerUpper;
54-
+ private final java.util.Map<java.util.UUID, java.util.concurrent.Future<?>> savingLocks = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // Leaf - Async playerdata saving
55-
56-
public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) {
57-
this.fixerUpper = fixerUpper;
58-
@@ -32,21 +33,84 @@ public class PlayerDataStorage {
59-
60-
public void save(Player player) {
61-
if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
62-
+ // Leaf start - Async playerdata saving
63-
+ CompoundTag compoundTag;
64-
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), LOGGER)) {
65-
TagValueOutput tagValueOutput = TagValueOutput.createWithContext(scopedCollector, player.registryAccess());
66-
player.saveWithoutId(tagValueOutput);
67-
- Path path = this.playerDir.toPath();
68-
- Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat");
69-
- CompoundTag compoundTag = tagValueOutput.buildResult();
124+
@@ -38,15 +38,28 @@ public class PlayerDataStorage {
125+
Path path = this.playerDir.toPath();
126+
Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat");
127+
CompoundTag compoundTag = tagValueOutput.buildResult();
70128
- NbtIo.writeCompressed(compoundTag, path1);
71129
- Path path2 = path.resolve(player.getStringUUID() + ".dat");
72130
- Path path3 = path.resolve(player.getStringUUID() + ".dat_old");
73131
- Util.safeReplaceFile(path2, path1, path3);
74-
- } catch (Exception var11) {
75-
- LOGGER.warn("Failed to save player data for {}", player.getPlainTextName(), var11); // Paper - Print exception
76-
+ compoundTag = tagValueOutput.buildResult();
77-
+ } catch (Exception exception) {
78-
+ LOGGER.warn("Failed to encode player data for {}", player.getPlainTextName(), exception);
79-
+ return;
132+
+ save(player.getStringUUID(), compoundTag);
133+
} catch (Exception var11) {
134+
LOGGER.warn("Failed to save player data for {}", player.getPlainTextName(), var11); // Paper - Print exception
80135
}
81-
+ save(player.getScoreboardName(), player.getUUID(), player.getStringUUID(), compoundTag);
82-
+ // Leaf end - Async playerdata saving
83136
}
84137

85138
+ // Leaf start - Async playerdata saving
86-
+ public void save(String playerName, java.util.UUID uniqueId, String stringId, CompoundTag compoundTag) {
87-
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
88-
+ try {
139+
+ public void save(String stringId, CompoundTag compoundTag) {
140+
+ Path path = this.playerDir.toPath();
141+
+ Path path2 = path.resolve(stringId + ".dat");
142+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
143+
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
89144
+ NbtIo.writeCompressed(compoundTag, nbtBytes);
90-
+ } catch (Exception exception) {
91-
+ LOGGER.warn("Failed to encode player data for {}", stringId, exception);
92-
+ }
93-
+ lockFor(uniqueId, playerName);
94-
+ synchronized (PlayerDataStorage.this) {
95-
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
96-
+ try {
97-
+ Path path = this.playerDir.toPath();
98-
+ Path path1 = Files.createTempFile(path, stringId + "-", ".dat");
99-
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
100-
+ Path path2 = path.resolve(stringId + ".dat");
101-
+ Path path3 = path.resolve(stringId + ".dat_old");
102-
+ Util.safeReplaceFile(path2, path1, path3);
103-
+ } catch (Exception var7) {
104-
+ LOGGER.warn("Failed to save player data for {}", playerName, var7);
105-
+ } finally {
106-
+ synchronized (PlayerDataStorage.this) {
107-
+ savingLocks.remove(uniqueId);
108-
+ }
109-
+ }
110-
+ }).ifPresent(future -> savingLocks.put(uniqueId, future));
111-
+ }
112-
+ }
113-
+
114-
+ private void lockFor(java.util.UUID uniqueId, String playerName) {
115-
+ java.util.concurrent.Future<?> fut;
116-
+ synchronized (this) {
117-
+ fut = savingLocks.get(uniqueId);
118-
+ }
119-
+ if (fut == null) {
120-
+ return;
121-
+ }
122-
+ while (true) {
123-
+ try {
124-
+ fut.get(10_000L, java.util.concurrent.TimeUnit.MILLISECONDS);
125-
+ break;
126-
+ } catch (InterruptedException ignored) {
127-
+ } catch (java.util.concurrent.ExecutionException
128-
+ | java.util.concurrent.TimeoutException exception) {
129-
+ LOGGER.warn("Failed to save player data for {}", playerName, exception);
130-
+
131-
+ String threadDump = "";
132-
+ var threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
133-
+ for (var threadInfo : threadMXBean.dumpAllThreads(true, true)) {
134-
+ if (threadInfo.getThreadName().equals("Leaf IO Thread")) { // TODO: We should use instanceOf here
135-
+ threadDump = threadInfo.toString();
136-
+ break;
137-
+ }
138-
+ }
139-
+ LOGGER.warn(threadDump);
140-
+ fut.cancel(true);
141-
+ break;
142-
+ } finally {
143-
+ savingLocks.remove(uniqueId);
144-
+ }
145-
+ }
145+
+ Path path1 = org.dreeam.leaf.async.AsyncPlayerDataSaving.tempFile(path, stringId, ".dat");
146+
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
147+
+ Path path3 = path.resolve(stringId + ".dat_old");
148+
+ Util.safeReplaceFile(path2, path1, path3);
149+
+ return null;
150+
+ }, path2, org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.playerdata);
146151
+ }
147152
+ // Leaf end - Async playerdata saving
148153
+
149154
private void backup(NameAndId nameAndId, String suffix) {
150155
Path path = this.playerDir.toPath();
151156
String string = nameAndId.id().toString();
152-
@@ -62,6 +126,7 @@ public class PlayerDataStorage {
153-
}
154-
155-
private Optional<CompoundTag> load(NameAndId nameAndId, String suffix) {
156-
+ lockFor(nameAndId.id(), nameAndId.name()); // Leaf - Async playerdata saving
157-
File file = new File(this.playerDir, nameAndId.id() + suffix);
158-
// Spigot start
159-
boolean usingWrongFile = false;
157+
@@ -76,6 +89,7 @@ public class PlayerDataStorage {
158+
if (file.exists() && file.isFile()) {
159+
try {
160+
// Spigot start
161+
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(file.toPath(), org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave.playerdata); // Leaf - Async playerdata saving
162+
Optional<CompoundTag> optional = Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()));
163+
if (usingWrongFile) {
164+
file.renameTo(new File(file.getPath() + ".offline-read"));

leaf-server/minecraft-patches/features/0179-SparklyPaper-Skip-dirty-stats-copy-when-requesting-p.patch

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ Subject: [PATCH] SparklyPaper: Skip dirty stats copy when requesting player
77
Original project: https://github.com/SparklyPower/SparklyPaper
88

99
diff --git a/net/minecraft/stats/ServerStatsCounter.java b/net/minecraft/stats/ServerStatsCounter.java
10-
index f239f445f83d50a69e7e6ffb97e7cf005c421e3a..e8af6a4877d01cc56ef0347013978ba9d583aa7e 100644
10+
index 13710f2fcceba200b6df8222d2402f33f83be0da..102001d521a925c3754253f1e47c0b6d0b3c4e9d 100644
1111
--- a/net/minecraft/stats/ServerStatsCounter.java
1212
+++ b/net/minecraft/stats/ServerStatsCounter.java
13-
@@ -109,11 +109,15 @@ public class ServerStatsCounter extends StatsCounter {
13+
@@ -111,11 +111,15 @@ public class ServerStatsCounter extends StatsCounter {
1414
this.dirty.add(stat);
1515
}
1616

@@ -26,7 +26,7 @@ index f239f445f83d50a69e7e6ffb97e7cf005c421e3a..e8af6a4877d01cc56ef0347013978ba9
2626

2727
public void parse(DataFixer fixerUpper, JsonElement json) {
2828
Dynamic<JsonElement> dynamic = new Dynamic<>(JsonOps.INSTANCE, json);
29-
@@ -140,10 +144,12 @@ public class ServerStatsCounter extends StatsCounter {
29+
@@ -142,10 +146,12 @@ public class ServerStatsCounter extends StatsCounter {
3030
public void sendStats(ServerPlayer player) {
3131
Object2IntMap<Stat<?>> map = new Object2IntOpenHashMap<>();
3232

leaf-server/minecraft-patches/features/0194-SparklyPaper-Parallel-world-ticking.patch

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ index 963e8cb72bb97daf516c72e954bcb02d1c4643a5..4b06d1314846593d38cc2ce07d6e86f5
294294
}
295295
// CraftBukkit end
296296
diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java
297-
index 78135cf45c8900eb142933d216744f4a73127965..96525c3107801cdb2904e5379cd59b98024141cd 100644
297+
index 14e33218bb9bd3e1f8484c88114900297ec65c1e..d991c98eb26e3acef33d8125edcd38927fb8ac68 100644
298298
--- a/net/minecraft/server/PlayerAdvancements.java
299299
+++ b/net/minecraft/server/PlayerAdvancements.java
300300
@@ -53,8 +53,10 @@ public class PlayerAdvancements {
@@ -310,7 +310,7 @@ index 78135cf45c8900eb142933d216744f4a73127965..96525c3107801cdb2904e5379cd59b98
310310
private ServerPlayer player;
311311
private @Nullable AdvancementHolder lastSelectedTab;
312312
private boolean isFirstPacket = true;
313-
@@ -149,7 +151,7 @@ public class PlayerAdvancements {
313+
@@ -151,7 +153,7 @@ public class PlayerAdvancements {
314314
if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.ignoredAdvancements) LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", path, this.playerSavePath); // Gale - Purpur - do not log ignored advancements
315315
} else {
316316
this.startProgress(advancementHolder, progress);
@@ -319,7 +319,7 @@ index 78135cf45c8900eb142933d216744f4a73127965..96525c3107801cdb2904e5379cd59b98
319319
this.markForVisibilityUpdate(advancementHolder);
320320
}
321321
});
322-
@@ -218,7 +220,7 @@ public class PlayerAdvancements {
322+
@@ -220,7 +222,7 @@ public class PlayerAdvancements {
323323
flag = true;
324324
}
325325

@@ -328,15 +328,15 @@ index 78135cf45c8900eb142933d216744f4a73127965..96525c3107801cdb2904e5379cd59b98
328328
this.markForVisibilityUpdate(advancement);
329329
}
330330

331-
@@ -264,6 +266,7 @@ public class PlayerAdvancements {
331+
@@ -266,6 +268,7 @@ public class PlayerAdvancements {
332332
}
333333

334334
public void flushDirty(ServerPlayer player, boolean showAdvancements) {
335335
+ final boolean isConcurrent = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled; // Leaf - SparklyPaper - parallel world ticking
336336
if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) {
337337
Map<Identifier, AdvancementProgress> map = new HashMap<>();
338338
Set<AdvancementHolder> set = new java.util.TreeSet<>(java.util.Comparator.comparing(adv -> adv.id().toString())); // Paper - Changed from HashSet to TreeSet ordered alphabetically.
339-
@@ -275,13 +278,23 @@ public class PlayerAdvancements {
339+
@@ -277,13 +280,23 @@ public class PlayerAdvancements {
340340

341341
this.rootsToUpdate.clear();
342342

0 commit comments

Comments
 (0)