eff22eb054
Looks like they would not have been removed if they disconnected during the chunk load, preventing the world from being unloaded.
376 lines
19 KiB
Diff
376 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 7d2fee97f4d08eae245475c4b60c1a7ba46c840d..48650bc1c09b18f1b57d9828dfe27f51c74c4a75 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -854,7 +854,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;
|
|
@@ -1400,13 +1400,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 6188f35e2b5300b6ff4a16e4d6e0e4a846261f8a..4d74eb7883c72fb42275fc5358917244bb36181d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -103,6 +103,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;
|
|
@@ -712,6 +713,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());
|
|
@@ -796,13 +855,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
|
|
|
|
}
|
|
|
|
@@ -840,6 +893,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();
|
|
@@ -1260,6 +1314,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
|
|
|
|
@@ -1269,6 +1324,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 22a0ca38ae03b8064db7f77dd17335ed4abc72bc..92603bc69e7782f252b2ad7b8c8a4546c9b23e12 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -1085,6 +1085,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 28944fc50ea43a3ea40bd1e69c560c8fe022337e..0720b748ed42bbd2a12cc5de79224f609a5e29be 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -179,6 +179,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 b1f6dc751c3dab5220a02223f6ba205aa0949f84..2ceddd78fb3b69eff60b5cd2b1f42c641f6ecd70 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -571,6 +571,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
|
|
|
|
@@ -1173,10 +1174,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 dc164608bfb2fb18a1adf83fa10bac4028dcac0a..a97909e77b9b28aede8c8716831c3f9a90618f09 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
@@ -457,6 +457,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 291bcca206722c86bca6d13058d18c413c38d256..f8a3048fa80758d82f2e92d48bd3cd2c585ae6c2 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);
|