e792da723a
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: 30885166 Update to Minecraft 1.16.4 CraftBukkit Changes: 3af81c71 Update to Minecraft 1.16.4 Spigot Changes: f011ca24 Update to Minecraft 1.16.4 Co-authored-by: Mariell Hoversholm <proximyst@proximyst.com>
324 Zeilen
15 KiB
Diff
324 Zeilen
15 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 saving
|
|
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
index 44811683cfe47adbdce6c8bd627bdbeb13ce114c..e7c73a4ddbb42aa52112147a60c8a761bfd3d07b 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -439,4 +439,19 @@ public class PaperWorldConfig {
|
|
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
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -43,7 +43,7 @@ public class Chunk implements IChunkAccess {
|
|
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;
|
|
@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 {
|
|
// CraftBukkit start
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 8886572361e56e8006bcdf2b066267336634cd7d..35630b80d474d7ccbf17a610ff6297ccc828c7a1 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -152,6 +152,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
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;
|
|
// CraftBukkit end
|
|
@@ -1141,14 +1142,24 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
this.serverPing.b().a(agameprofile);
|
|
}
|
|
|
|
- if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit
|
|
- MinecraftServer.LOGGER.debug("Autosave started");
|
|
+ //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down
|
|
+ //MinecraftServer.LOGGER.debug("Autosave started"); // Paper
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
this.methodProfiler.exit();
|
|
- MinecraftServer.LOGGER.debug("Autosave finished");
|
|
- }
|
|
+ //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper
|
|
+ //} // 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) {
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index d8bedba819fa9ee0a4d3bdfbf0b010da7144dd68..c4ed4d58f7b344626acb13baeb14288970a7bb90 100644
|
|
--- 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 {
|
|
|
|
}
|
|
|
|
+ // 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
|
|
+ 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;
|
|
+
|
|
+ if (!playerchunk.setHasBeenLoaded()) {
|
|
+ // do not fall through to reschedule logic
|
|
+ playerchunk.inactiveTimeStart = currentTick;
|
|
+ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) {
|
|
+ break;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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());
|
|
});
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index b2ef67642e2e0d0573f4312e88ba583352a743a6..5a35395434ffdefd410573302a3d46cca9642a81 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -775,11 +775,43 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
return !this.server.a(this, blockposition, entityhuman) && this.getWorldBorder().a(blockposition);
|
|
}
|
|
|
|
+ // Paper start - derived from below
|
|
+ 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) {
|
|
+ this.saveData();
|
|
+ }
|
|
+
|
|
+ timings.worldSaveChunks.startTiming(); // Paper
|
|
+ if (!this.isSavingDisabled()) chunkproviderserver.saveIncrementally();
|
|
+ timings.worldSaveChunks.stopTiming(); // Paper
|
|
+
|
|
+
|
|
+ // Copied from save()
|
|
+ // CraftBukkit start - moved from MinecraftServer.saveChunks
|
|
+ if (doFull) { // Paper
|
|
+ WorldServer worldserver1 = this;
|
|
+
|
|
+ worldDataServer.a(worldserver1.getWorldBorder().t());
|
|
+ worldDataServer.setCustomBossEvents(this.server.getBossBattleCustomData().save());
|
|
+ convertable.a(this.server.customRegistry, this.worldDataServer, this.server.getPlayerList().save());
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public void save(@Nullable IProgressUpdate iprogressupdate, boolean flag, boolean flag1) {
|
|
ChunkProviderServer chunkproviderserver = this.getChunkProvider();
|
|
|
|
if (!flag1) {
|
|
- org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
|
|
+ if (flag) org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit // Paper
|
|
try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper
|
|
if (iprogressupdate != null) {
|
|
iprogressupdate.a(new ChatMessage("menu.savingLevel"));
|
|
@@ -805,6 +837,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
// CraftBukkit end
|
|
}
|
|
|
|
+ private void saveData() { this.aj(); } // Paper - OBFHELPER
|
|
private void aj() {
|
|
if (this.dragonBattle != null) {
|
|
this.worldDataServer.a(this.dragonBattle.a()); // CraftBukkit
|