Paper/patches/server/0448-incremental-chunk-and-player-saving.patch
Nassim Jahnke 07548192b1
Updated Upstream (CraftBukkit)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

CraftBukkit Changes:
cc7edd6bc SPIGOT-7051: Worldgen API does not run vanilla noise generation when shouldGenerateNoise is true
2022-06-10 14:45:58 +02:00

377 lines
19 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sun, 9 Jun 2019 03:53:22 +0100
Subject: [PATCH] incremental chunk and player saving
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index d40623b3ed69a0821057f82e6164b4c94b4a7087..54a155295ed2ee2386f180bf8f5d413984febe96 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -851,7 +851,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
try {
this.isSaving = true;
- this.getPlayerList().saveAll();
+ this.getPlayerList().saveAll(); // Diff on change
flag3 = this.saveAllChunks(suppressLogs, flush, force);
} finally {
this.isSaving = false;
@@ -1398,13 +1398,28 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
- if (this.autosavePeriod > 0 && this.tickCount % this.autosavePeriod == 0) { // CraftBukkit
- MinecraftServer.LOGGER.debug("Autosave started");
- this.profiler.push("save");
- this.saveEverything(true, false, false);
- this.profiler.pop();
- MinecraftServer.LOGGER.debug("Autosave finished");
+ // Paper start - incremental chunk and player saving
+ int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate;
+ if (playerSaveInterval < 0) {
+ playerSaveInterval = autosavePeriod;
}
+ this.profiler.push("save");
+ final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0;
+ try {
+ this.isSaving = true;
+ if (playerSaveInterval > 0) {
+ this.playerList.saveAll(playerSaveInterval);
+ }
+ for (ServerLevel level : this.getAllLevels()) {
+ if (level.paperConfig().chunks.autoSaveInterval.value() > 0) {
+ level.saveIncrementally(fullSave);
+ }
+ }
+ } finally {
+ this.isSaving = false;
+ }
+ this.profiler.pop();
+ // Paper end
io.papermc.paper.util.CachedLists.reset(); // Paper
// Paper start - move executeAll() into full server tick timing
try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 4e8a79f2d3b6f52c6284bc9b0ce2423dc43a154f..36a9d52d9af3bc398010c52dc16ab23e53f2702a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -92,6 +92,8 @@ public class ChunkHolder {
this.playersInChunkTickRange = null;
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
+ long lastAutoSaveTime; // Paper - incremental autosave
+ long inactiveTimeStart; // Paper - incremental autosave
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
@@ -502,7 +504,19 @@ public class ChunkHolder {
boolean flag2 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
boolean flag3 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ boolean prevHasBeenLoaded = this.wasAccessibleSinceLastSave; // Paper
this.wasAccessibleSinceLastSave |= flag3;
+ // Paper start - incremental autosave
+ if (this.wasAccessibleSinceLastSave & !prevHasBeenLoaded) {
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
+ if (timeSinceAutoSave < 0) {
+ // safest bet is to assume autosave is needed here
+ timeSinceAutoSave = this.chunkMap.level.paperConfig().chunks.autoSaveInterval.value();
+ }
+ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave;
+ this.chunkMap.autoSaveQueue.add(this);
+ }
+ // Paper end
if (!flag2 && flag3) {
int expectCreateCount = ++this.fullChunkCreateCount; // Paper
this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this);
@@ -633,9 +647,33 @@ public class ChunkHolder {
}
public void refreshAccessibility() {
+ boolean prev = this.wasAccessibleSinceLastSave; // Paper
this.wasAccessibleSinceLastSave = ChunkHolder.getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ // Paper start - incremental autosave
+ if (prev != this.wasAccessibleSinceLastSave) {
+ if (this.wasAccessibleSinceLastSave) {
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
+ if (timeSinceAutoSave < 0) {
+ // safest bet is to assume autosave is needed here
+ timeSinceAutoSave = this.chunkMap.level.paperConfig().chunks.autoSaveInterval.value();
+ }
+ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave;
+ this.chunkMap.autoSaveQueue.add(this);
+ } else {
+ this.inactiveTimeStart = this.chunkMap.level.getGameTime();
+ this.chunkMap.autoSaveQueue.remove(this);
+ }
+ }
+ // Paper end
}
+ // Paper start - incremental autosave
+ public boolean setHasBeenLoaded() {
+ this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ return this.wasAccessibleSinceLastSave;
+ }
+ // Paper end
+
public void replaceProtoChunk(ImposterProtoChunk chunk) {
for (int i = 0; i < this.futures.length(); ++i) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(i);
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 10ef89963ad805c775b9c649f4e17c9d4ec7f4b7..eb74a831fc439c56fe1ac2d4769ebefa1e5759a3 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -106,6 +106,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.phys.Vec3;
+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableObject;
import org.slf4j.Logger;
@@ -715,6 +716,64 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
+ // Paper start - incremental autosave
+ final ObjectRBTreeSet<ChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> {
+ int timeCompare = Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime);
+ if (timeCompare != 0) {
+ return timeCompare;
+ }
+
+ return Long.compare(MCUtil.getCoordinateKey(playerchunk1.pos), MCUtil.getCoordinateKey(playerchunk2.pos));
+ });
+
+ protected void saveIncrementally() {
+ int savedThisTick = 0;
+ // optimized since we search far less chunks to hit ones that need to be saved
+ List<ChunkHolder> reschedule = new java.util.ArrayList<>(this.level.paperConfig().chunks.maxAutoSaveChunksPerTick);
+ long currentTick = this.level.getGameTime();
+ long maxSaveTime = currentTick - this.level.paperConfig().chunks.autoSaveInterval.value();
+
+ for (Iterator<ChunkHolder> iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) {
+ ChunkHolder playerchunk = iterator.next();
+ if (playerchunk.lastAutoSaveTime > maxSaveTime) {
+ break;
+ }
+
+ iterator.remove();
+
+ ChunkAccess ichunkaccess = playerchunk.getChunkToSave().getNow(null);
+ if (ichunkaccess instanceof LevelChunk) {
+ boolean shouldSave = ((LevelChunk)ichunkaccess).lastSaveTime <= maxSaveTime;
+
+ if (shouldSave && this.save(ichunkaccess) && this.level.entityManager.storeChunkSections(playerchunk.pos.toLong(), entity -> {})) {
+ ++savedThisTick;
+
+ if (!playerchunk.setHasBeenLoaded()) {
+ // do not fall through to reschedule logic
+ playerchunk.inactiveTimeStart = currentTick;
+ if (savedThisTick >= this.level.paperConfig().chunks.maxAutoSaveChunksPerTick) {
+ break;
+ }
+ continue;
+ }
+ }
+ }
+
+ reschedule.add(playerchunk);
+
+ if (savedThisTick >= this.level.paperConfig().chunks.maxAutoSaveChunksPerTick) {
+ break;
+ }
+ }
+
+ for (int i = 0, len = reschedule.size(); i < len; ++i) {
+ ChunkHolder playerchunk = reschedule.get(i);
+ playerchunk.lastAutoSaveTime = this.level.getGameTime();
+ this.autoSaveQueue.add(playerchunk);
+ }
+ }
+ // Paper end
+
protected void saveAllChunks(boolean flush) {
if (flush) {
List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
@@ -799,13 +858,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
int l = 0;
- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
-
- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) {
- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) {
- ++l;
- }
- }
+ // Paper - incremental chunk and player saving
}
@@ -843,6 +896,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.level.unload(chunk);
}
+ this.autoSaveQueue.remove(holder); // Paper
this.lightEngine.updateChunkStatus(ichunkaccess.getPos());
this.lightEngine.tryScheduleUpdate();
@@ -1263,6 +1317,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
asyncSaveData, chunk);
chunk.setUnsaved(false);
+ chunk.setLastSaved(this.level.getGameTime()); // Paper - track last saved time
}
// Paper end
@@ -1272,6 +1327,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!chunk.isUnsaved()) {
return false;
} else {
+ chunk.setLastSaved(this.level.getGameTime()); // Paper - track save time
chunk.setUnsaved(false);
ChunkPos chunkcoordintpair = chunk.getPos();
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 1d9a0f6effa1654609f4d0752ec69eed6ab7134b..585892f19bc0aea89889a358c0407f2975b9efe5 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -814,6 +814,15 @@ public class ServerChunkCache extends ChunkSource {
} // Paper - Timings
}
+ // Paper start - duplicate save, but call incremental
+ public void saveIncrementally() {
+ this.runDistanceManagerUpdates();
+ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
+ this.chunkMap.saveIncrementally();
+ } // Paper - Timings
+ }
+ // Paper end
+
@Override
public void close() throws IOException {
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 13354b9e626ce7fec74cbaac68304a912517c40e..69611eaafe2edc0e937f54b63bfbf3a23ca7a857 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1082,6 +1082,37 @@ public class ServerLevel extends Level implements WorldGenLevel {
return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos);
}
+ // Paper start - derived from below
+ public void saveIncrementally(boolean doFull) {
+ ServerChunkCache chunkproviderserver = this.getChunkSource();
+
+ if (doFull) {
+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
+ }
+
+ try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) {
+ if (doFull) {
+ this.saveLevelData();
+ }
+
+ this.timings.worldSaveChunks.startTiming(); // Paper
+ if (!this.noSave()) chunkproviderserver.saveIncrementally();
+ this.timings.worldSaveChunks.stopTiming(); // Paper
+
+ // Copied from save()
+ // CraftBukkit start - moved from MinecraftServer.saveChunks
+ if (doFull) { // Paper
+ ServerLevel worldserver1 = this;
+
+ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
+ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save());
+ this.convertable.saveDataTag(this.server.registryHolder, this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
+ }
+ // CraftBukkit end
+ }
+ }
+ // Paper end
+
public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) {
ServerChunkCache chunkproviderserver = this.getChunkSource();
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index ea1f0477f416f9e852ea92083781d7eb6ab24861..58c67cc9a4c9c238fae89e165dc6ad01e569476f 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -177,6 +177,7 @@ import org.bukkit.inventory.MainHand;
public class ServerPlayer extends Player {
private static final Logger LOGGER = LogUtils.getLogger();
+ public long lastSave = MinecraftServer.currentTick; // Paper
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
public ServerGamePacketListenerImpl connection;
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 4b9e030016bef762c01ace5181ade7d1480b8702..c1e62a0d1655993430da7e4cbd8075cd4239adb4 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -561,6 +561,7 @@ public abstract class PlayerList {
protected void save(ServerPlayer player) {
if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
if (!player.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug)
+ player.lastSave = MinecraftServer.currentTick; // Paper
this.playerIo.save(player);
ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
@@ -1163,10 +1164,22 @@ public abstract class PlayerList {
}
public void saveAll() {
+ // Paper start - incremental player saving
+ this.saveAll(-1);
+ }
+
+ public void saveAll(int interval) {
net.minecraft.server.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
MinecraftTimings.savePlayers.startTiming(); // Paper
+ int numSaved = 0;
+ long now = MinecraftServer.currentTick;
for (int i = 0; i < this.players.size(); ++i) {
- this.save(this.players.get(i));
+ ServerPlayer entityplayer = this.players.get(i);
+ if (interval == -1 || now - entityplayer.lastSave >= interval) {
+ this.save(entityplayer);
+ if (interval != -1 && ++numSaved <= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) { break; }
+ }
+ // Paper end
}
MinecraftTimings.savePlayers.stopTiming(); // Paper
return null; }); // Paper - ensure main
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
index a5160f0336f1ab50e415bddaa958616e8a08dfee..bef890d2e8d883165a48a7f5b39a865198749a0b 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -455,6 +455,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
public LevelHeightAccessor getHeightAccessorForGeneration() {
return this;
}
+ public void setLastSaved(long ticks) {} // Paper
public static record TicksToSave(SerializableTickContainer<Block> blocks, SerializableTickContainer<Fluid> fluids) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 3fe94e580d2aaae9616ba83c0d3a44687505b249..797ff36295412ac8429d573e039d870fd85eb569 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -87,6 +87,12 @@ public class LevelChunk extends ChunkAccess {
private final Int2ObjectMap<GameEventDispatcher> gameEventDispatcherSections;
private final LevelChunkTicks<Block> blockTicks;
private final LevelChunkTicks<Fluid> fluidTicks;
+ // Paper start - track last save time
+ public long lastSaveTime;
+ public void setLastSaved(long ticks) {
+ this.lastSaveTime = ticks;
+ }
+ // Paper end
public LevelChunk(Level world, ChunkPos pos) {
this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null);