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/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index dc47259e40089a4793d950d4c3dc5bcc51ff680f..34b10db71d22cb7211dcfc5565e6833c8d4d2413 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -955,7 +955,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         boolean var4;
         try {
             this.isSaving = true;
-            this.getPlayerList().saveAll();
+            this.getPlayerList().saveAll(); // Paper - Incremental chunk and player saving; diff on change
             var4 = this.saveAllChunks(suppressLog, flush, forced);
         } finally {
             this.isSaving = false;
@@ -1534,9 +1534,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         }
 
         this.ticksUntilAutosave--;
-        if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) { // CraftBukkit
-            this.autoSave();
+        // Paper start - Incremental chunk and player saving
+        final ProfilerFiller profiler = Profiler.get();
+        int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate;
+        if (playerSaveInterval < 0) {
+            playerSaveInterval = autosavePeriod;
+        }
+        profiler.push("save");
+        final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0;
+        try {
+            this.isSaving = true;
+            if (playerSaveInterval > 0) {
+                this.playerList.saveAll(playerSaveInterval);
+            }
+            for (final ServerLevel level : this.getAllLevels()) {
+                if (level.paperConfig().chunks.autoSaveInterval.value() > 0) {
+                    level.saveIncrementally(fullSave);
+                }
+            }
+        } finally {
+            this.isSaving = false;
         }
+        profiler.pop();
+        // Paper end - Incremental chunk and player saving
 
         ProfilerFiller profilerFiller = Profiler.get();
         this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 34b7769663e235b93c6388ab0c92c00f0297e42f..dda8d38ef61672cc714d9e5a475f9b0412ed5ff9 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1339,6 +1339,28 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
         return !(entity instanceof Player player && (this.server.isUnderSpawnProtection(this, pos, player) || !this.getWorldBorder().isWithinBounds(pos)));
     }
 
+    // Paper start - Incremental chunk and player saving
+    public void saveIncrementally(boolean doFull) {
+        if (doFull) {
+            org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld()));
+        }
+
+        if (doFull) {
+            this.saveLevelData(false);
+        }
+        // chunk autosave is already called by the ChunkSystem during unload processing (ChunkMap#processUnloads)
+        // Copied from save()
+        // CraftBukkit start - moved from MinecraftServer.saveChunks
+        if (doFull) { // Paper
+            ServerLevel serverLevel1 = this;
+            this.serverLevelData.setWorldBorder(serverLevel1.getWorldBorder().createSettings());
+            this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess()));
+            this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
+        }
+        // CraftBukkit end
+    }
+    // Paper end - Incremental chunk and player saving
+
     public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
         // Paper start - add close param
         this.save(progress, flush, skipSave, false);
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 18439057b996a55833e1bfc60dc293de432d2902..18c0a3de3fd04879f84306e09f5a30502a3d5077 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -195,6 +195,7 @@ import org.slf4j.Logger;
 
 public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
     private static final Logger LOGGER = LogUtils.getLogger();
+    public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
     private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
     private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
     private static final int FLY_STAT_RECORDING_SPEED = 25;
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 5a60527c9feb3606d85488b3cf215c451ca033de..dea72819f2933e5a806bb3a0603e4ebdf7f99456 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -489,6 +489,7 @@ public abstract class PlayerList {
 
     protected void save(ServerPlayer player) {
         if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
+        player.lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
         this.playerIo.save(player);
         ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit
         if (serverStatsCounter != null) {
@@ -1059,9 +1060,23 @@ public abstract class PlayerList {
     }
 
     public void saveAll() {
+        // Paper start - Incremental chunk and player saving
+        this.saveAll(-1);
+    }
+
+    public void saveAll(final int interval) {
         io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
+        int numSaved = 0;
+        final long now = MinecraftServer.currentTick;
         for (int i = 0; i < this.players.size(); i++) {
-            this.save(this.players.get(i));
+            final ServerPlayer player = this.players.get(i);
+            if (interval == -1 || now - player.lastSave >= interval) {
+                this.save(player);
+                if (interval != -1 && ++numSaved >= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) {
+                    break;
+                }
+            }
+            // Paper end - Incremental chunk and player saving
         }
         return null; }); // Paper - ensure main
     }
