Paper/Spigot-Server-Patches/0361-incremental-chunk-saving.patch

324 lines
15 KiB
Diff
Raw Normal View History

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2019-06-09 21:22:44 +02:00
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sun, 9 Jun 2019 03:53:22 +0100
Subject: [PATCH] incremental chunk saving
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index e9c03546c42657dd5f5d4c6f71bd7e0cc7e2cb15..d1ff673d8b5aafac58b082c9abc9257f0b2dfb15 100644
2019-06-09 21:22:44 +02:00
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -440,4 +440,19 @@ public class PaperWorldConfig {
2019-06-09 21:22:44 +02:00
keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16);
log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16));
}
+
+ public int autoSavePeriod = -1;
+ private void autoSavePeriod() {
+ autoSavePeriod = getInt("auto-save-interval", -1);
+ if (autoSavePeriod > 0) {
+ log("Auto Save Interval: " +autoSavePeriod + " (" + (autoSavePeriod / 20) + "s)");
+ } else if (autoSavePeriod < 0) {
+ autoSavePeriod = net.minecraft.server.MinecraftServer.getServer().autosavePeriod;
+ }
+ }
+
+ public int maxAutoSaveChunksPerTick = 24;
+ private void maxAutoSaveChunksPerTick() {
+ maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
+ }
}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 0f8745ede62894f8d88fc8985e34b32acee5c698..71efd1efab43d5aeabba2ad385016b616f4e4849 100644
2019-06-09 21:22:44 +02:00
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
2020-06-26 01:38:24 +02:00
@@ -43,7 +43,7 @@ public class Chunk implements IChunkAccess {
2019-06-09 21:22:44 +02:00
private TickList<Block> o;
private TickList<FluidType> p;
private boolean q;
- private long lastSaved;
+ public long lastSaved; // Paper
private volatile boolean s;
private long inhabitedTime;
2019-06-09 21:22:44 +02:00
@Nullable
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 0c5a14d98f824591c553684191b32ccb507ebe2f..4140de8bcd1ee93f77574d892d32e7c7821be9e0 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -536,6 +536,15 @@ public class ChunkProviderServer extends IChunkProvider {
} // Paper - Timings
}
+ // Paper start - duplicate save, but call incremental
+ public void saveIncrementally() {
+ this.tickDistanceManager();
+ try (co.aikar.timings.Timing timed = world.timings.chunkSaveData.startTiming()) { // Paper - Timings
+ this.playerChunkMap.saveIncrementally();
+ } // Paper - Timings
+ }
+ // Paper end
+
@Override
public void close() throws IOException {
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#2415) * fixup patch and rebuild * Updated Upstream (Bukkit/CraftBukkit/Spigot) Upstream has released updates that appears to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: bde198c9 SPIGOT-5246: PlayerQuitEvent.get/setQuitMessage() is incorrectly marked as NotNull 24ad5a79 SPIGOT-5240: Vector.angle not valid for angles very close to each other a143db9a SPIGOT-5231: ShotAtAngle API for Fireworks 10db5c3d SPIGOT-5226: Update Javadoc of PlayerDeathEvent CraftBukkit Changes: 1ec1b05e SPIGOT-5245: Unneeded cast to WorldNBTStorage in CraftWorld#getWorldFolder e5e8eec2 SPIGOT-5241: setAttributeModifiers does not work on untouched stack 803eaa31 SPIGOT-5231: ShotAtAngle API for Fireworks 7881d2ae SPIGOT-5237: Horses, pigs do not drop their inventory 06efc9ec Don't accept connections until all plugins have enabled da62a66a SPIGOT-5225: World handle isn't closed if world is unloaded without saving 104b3831 SPIGOT-5222: Cannot get Long values from Entity memory f0b3fe43 SPIGOT-5220: Server CPU usage reaches 100% when stdin is null Spigot Changes: e5b1b5db SPIGOT-5235: Destroy expired area effect clouds / fireworks that are inactive cbcc8e87 Make region files more reliable to write to 8887c5f4 Remove redundant late-bind option dac29063 Rebuild patches * Preserve old flush on save flag for reliable regionfiles Originally this patch was in paper * Fix some issues with the death event - Entities potentially entering a glitched state to the client where they appear to be falling over - Donkeys losing their chest if the event was cancelled (only an issue since the upstream merge) - Some wither death logic running for an entity killed by a wither
2019-08-05 18:35:40 +02:00
// CraftBukkit start
2019-06-09 21:22:44 +02:00
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 7d9a3375d289deccdfa0f4caa8a2088550c560aa..8e2971458b4fbac7d288c190245bb0f8e1c84d7a 100644
2019-06-09 21:22:44 +02:00
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
2020-06-26 01:38:24 +02:00
@@ -152,6 +152,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
2019-06-09 21:22:44 +02:00
public static int currentTick = 0; // Paper - Further improve tick loop
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
+ public boolean serverAutoSave = false; // Paper
public CommandDispatcher vanillaCommandDispatcher;
private boolean forceTicks;
2020-06-26 18:20:03 +02:00
// CraftBukkit end
@@ -1137,14 +1138,24 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
2019-06-09 21:22:44 +02:00
this.serverPing.b().a(agameprofile);
}
- if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit
- MinecraftServer.LOGGER.debug("Autosave started");
2019-06-09 21:22:44 +02:00
+ //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down
+ //MinecraftServer.LOGGER.debug("Autosave started"); // Paper
2019-06-09 21:22:44 +02:00
+ serverAutoSave = (autosavePeriod > 0 && this.ticks % autosavePeriod == 0); // Paper
this.methodProfiler.enter("save");
+ if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // Paper
this.playerList.savePlayers();
- this.saveChunks(true, false, false);
+ }// Paper
+ // Paper start
+ for (WorldServer world : getWorlds()) {
+ if (world.paperConfig.autoSavePeriod > 0) {
+ world.saveIncrementally(serverAutoSave);
+ }
2019-06-09 21:22:44 +02:00
+ }
+ // Paper end
+
this.methodProfiler.exit();
- MinecraftServer.LOGGER.debug("Autosave finished");
2019-06-09 21:22:44 +02:00
- }
+ //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper
2019-06-09 21:22:44 +02:00
+ //} // Paper
this.methodProfiler.enter("snooper");
if (((DedicatedServer) this).getDedicatedServerProperties().snooperEnabled && !this.snooper.d() && this.ticks > 100) { // Spigot
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 94a6b5742b3ce2cac0f7567117fadeeb05e888ef..10dfdecba53cf16f9fae8a2be1144ef8cda1006d 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -42,6 +42,9 @@ public class PlayerChunk {
private final PlayerChunkMap chunkMap; // Paper
+ long lastAutoSaveTime; // Paper - incremental autosave
+ long inactiveTimeStart; // Paper - incremental autosave
+
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
@@ -393,7 +396,19 @@ public class PlayerChunk {
boolean flag2 = playerchunk_state.isAtLeast(PlayerChunk.State.BORDER);
boolean flag3 = playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER);
+ boolean prevHasBeenLoaded = this.hasBeenLoaded; // Paper
this.hasBeenLoaded |= flag3;
+ // Paper start - incremental autosave
+ if (this.hasBeenLoaded & !prevHasBeenLoaded) {
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
+ if (timeSinceAutoSave < 0) {
+ // safest bet is to assume autosave is needed here
+ timeSinceAutoSave = this.chunkMap.world.paperConfig.autoSavePeriod;
+ }
+ this.lastAutoSaveTime = this.chunkMap.world.getTime() - timeSinceAutoSave;
+ this.chunkMap.autoSaveQueue.add(this);
+ }
+ // Paper end
if (!flag2 && flag3) {
// Paper start - cache ticking ready status
int expectCreateCount = ++this.fullChunkCreateCount;
@@ -513,8 +528,32 @@ public class PlayerChunk {
}
public void m() {
+ boolean prev = this.hasBeenLoaded; // Paper
+ this.hasBeenLoaded = getChunkState(this.ticketLevel).isAtLeast(PlayerChunk.State.BORDER);
+ // Paper start - incremental autosave
+ if (prev != this.hasBeenLoaded) {
+ if (this.hasBeenLoaded) {
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
+ if (timeSinceAutoSave < 0) {
+ // safest bet is to assume autosave is needed here
+ timeSinceAutoSave = this.chunkMap.world.paperConfig.autoSavePeriod;
+ }
+ this.lastAutoSaveTime = this.chunkMap.world.getTime() - timeSinceAutoSave;
+ this.chunkMap.autoSaveQueue.add(this);
+ } else {
+ this.inactiveTimeStart = this.chunkMap.world.getTime();
+ this.chunkMap.autoSaveQueue.remove(this);
+ }
+ }
+ // Paper end
+ }
+
+ // Paper start - incremental autosave
+ public boolean setHasBeenLoaded() {
this.hasBeenLoaded = getChunkState(this.ticketLevel).isAtLeast(PlayerChunk.State.BORDER);
+ return this.hasBeenLoaded;
}
+ // Paper end
public void a(ProtoChunkExtension protochunkextension) {
for (int i = 0; i < this.statusFutures.length(); ++i) {
2019-06-09 21:22:44 +02:00
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index d8bedba819fa9ee0a4d3bdfbf0b010da7144dd68..c4ed4d58f7b344626acb13baeb14288970a7bb90 100644
2019-06-09 21:22:44 +02:00
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -47,6 +47,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -333,6 +334,64 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
2019-06-09 21:22:44 +02:00
+ // Paper start - incremental autosave
+ final ObjectRBTreeSet<PlayerChunk> autoSaveQueue = new ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> {
+ int timeCompare = Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime);
+ if (timeCompare != 0) {
+ return timeCompare;
+ }
+
+ return Long.compare(MCUtil.getCoordinateKey(playerchunk1.location), MCUtil.getCoordinateKey(playerchunk2.location));
+ });
+
+ protected void saveIncrementally() {
+ int savedThisTick = 0;
+ // optimized since we search far less chunks to hit ones that need to be saved
2020-06-26 01:38:24 +02:00
+ List<PlayerChunk> reschedule = new java.util.ArrayList<>(this.world.paperConfig.maxAutoSaveChunksPerTick);
+ long currentTick = this.world.getTime();
+ long maxSaveTime = currentTick - this.world.paperConfig.autoSavePeriod;
+
+ for (Iterator<PlayerChunk> iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) {
+ PlayerChunk playerchunk = iterator.next();
+ if (playerchunk.lastAutoSaveTime > maxSaveTime) {
+ break;
+ }
+
+ iterator.remove();
+
+ IChunkAccess ichunkaccess = playerchunk.getChunkSave().getNow(null);
+ if (ichunkaccess instanceof Chunk) {
+ boolean shouldSave = ((Chunk)ichunkaccess).lastSaved <= maxSaveTime;
+
+ if (shouldSave && this.saveChunk(ichunkaccess)) {
+ ++savedThisTick;
2019-06-09 21:22:44 +02:00
+
+ if (!playerchunk.setHasBeenLoaded()) {
+ // do not fall through to reschedule logic
+ playerchunk.inactiveTimeStart = currentTick;
+ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) {
+ break;
+ }
+ continue;
2019-06-09 21:22:44 +02:00
+ }
+ }
+ }
+
+ reschedule.add(playerchunk);
+
+ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) {
+ break;
+ }
+ }
+
+ for (int i = 0, len = reschedule.size(); i < len; ++i) {
+ PlayerChunk playerchunk = reschedule.get(i);
+ playerchunk.lastAutoSaveTime = this.world.getTime();
+ this.autoSaveQueue.add(playerchunk);
+ }
+ }
+ // Paper end
+
protected void save(boolean flag) {
if (flag) {
List<PlayerChunk> list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList());
@@ -443,6 +502,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.world.unloadChunk(chunk);
}
+ this.autoSaveQueue.remove(playerchunk); // Paper
this.lightEngine.a(ichunkaccess.getPos());
this.lightEngine.queueUpdate();
@@ -635,6 +695,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
playerchunk.a(new ProtoChunkExtension(chunk));
}
+ chunk.setLastSaved(this.world.getTime() - 1); // Paper - avoid autosaving newly generated/loaded chunks
+
chunk.a(() -> {
return PlayerChunk.getChunkState(playerchunk.getTicketLevel());
});
2019-06-09 21:22:44 +02:00
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 644167d400e0f4af142f0afe6a11cae4ac591ddf..3c7752769fb6a2da644f9d41ef783de9772ce5f7 100644
2019-06-09 21:22:44 +02:00
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -776,11 +776,43 @@ public class WorldServer extends World implements GeneratorAccessSeed {
2020-06-26 01:38:24 +02:00
return !this.server.a(this, blockposition, entityhuman) && this.getWorldBorder().a(blockposition);
}
+ // Paper start - derived from below
2020-06-26 01:38:24 +02:00
+ public void saveIncrementally(boolean doFull) {
+ ChunkProviderServer chunkproviderserver = this.getChunkProvider();
+
+ if (doFull) {
+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
+ }
+
+ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) {
+ if (doFull) {
2019-12-13 02:18:18 +01:00
+ this.saveData();
+ }
+
+ timings.worldSaveChunks.startTiming(); // Paper
+ if (!this.isSavingDisabled()) chunkproviderserver.saveIncrementally();
+ timings.worldSaveChunks.stopTiming(); // Paper
+
+
2020-06-26 01:38:24 +02:00
+ // Copied from save()
+ // CraftBukkit start - moved from MinecraftServer.saveChunks
2020-06-26 01:38:24 +02:00
+ if (doFull) { // Paper
+ WorldServer worldserver1 = this;
+
2020-06-26 01:38:24 +02:00
+ worldDataServer.a(worldserver1.getWorldBorder().t());
+ worldDataServer.setCustomBossEvents(this.server.getBossBattleCustomData().save());
+ convertable.a(this.server.customRegistry, this.worldDataServer, this.server.getPlayerList().save());
+ }
2020-06-26 01:38:24 +02:00
+ // CraftBukkit end
+ }
+ }
+ // Paper end
+
2020-06-26 01:38:24 +02:00
public void save(@Nullable IProgressUpdate iprogressupdate, boolean flag, boolean flag1) {
ChunkProviderServer chunkproviderserver = this.getChunkProvider();
2019-06-09 21:22:44 +02:00
if (!flag1) {
- org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
2020-06-26 01:38:24 +02:00
+ if (flag) org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit // Paper
2019-06-09 21:22:44 +02:00
try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper
if (iprogressupdate != null) {
2020-06-26 01:38:24 +02:00
iprogressupdate.a(new ChatMessage("menu.savingLevel"));
@@ -806,6 +838,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
2019-12-13 02:18:18 +01:00
// CraftBukkit end
}
+ private void saveData() { this.aj(); } // Paper - OBFHELPER
private void aj() {
2020-06-26 01:38:24 +02:00
if (this.dragonBattle != null) {
this.worldDataServer.a(this.dragonBattle.a()); // CraftBukkit