From e82072ef380eb0d17fb93cee8bf8275fb11f29de Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 19 Sep 2016 23:16:39 -0400
Subject: [PATCH] Auto Save Improvements

Makes Auto Save Rate setting configurable per-world. If the auto save rate is left -1, the global bukkit.yml value will be used.

Process auto save every tick instead of once per auto tick interval, so that chunk saves will distribute over many ticks instead of all at once.

Re-introduce a cap per tick for auto save (Spigot disabled the vanilla cap) and make it configurable.

Adds incremental player auto saving too

diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index fb7ada8139..17b4e01aa4 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -269,4 +269,15 @@ public class PaperConfig {
         flyingKickPlayerMessage = getString("messages.kick.flying-player", flyingKickPlayerMessage);
         flyingKickVehicleMessage = getString("messages.kick.flying-vehicle", flyingKickVehicleMessage);
     }
+
+    public static int playerAutoSaveRate = -1;
+    public static int maxPlayerAutoSavePerTick = 10;
+    private static void playerAutoSaveRate() {
+        playerAutoSaveRate = getInt("settings.player-auto-save-rate", -1);
+        maxPlayerAutoSavePerTick = getInt("settings.max-player-auto-save-per-tick", -1);
+        if (maxPlayerAutoSavePerTick == -1) { // -1 Automatic / "Recommended"
+            // 10 should be safe for everyone unless your mass spamming player auto save
+            maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20;
+        }
+    }
 }
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index c70771614d..6e28410c37 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -2,6 +2,7 @@ package com.destroystokyo.paper;
 
 import java.util.List;
 
+import net.minecraft.server.MinecraftServer;
 import org.bukkit.configuration.file.YamlConfiguration;
 import org.spigotmc.SpigotWorldConfig;
 
@@ -324,4 +325,19 @@ public class PaperWorldConfig {
     private void skipEntityTickingInChunksScheduledForUnload() {
         skipEntityTickingInChunksScheduledForUnload = getBoolean("skip-entity-ticking-in-chunks-scheduled-for-unload", skipEntityTickingInChunksScheduledForUnload);
     }
+
+    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 = 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 bdf922db50..0bf614ce57 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -53,9 +53,9 @@ public class Chunk implements IChunkAccess {
     private final TickList<Block> s;
     private final TickList<FluidType> t;
     private boolean u;
-    private boolean v;
+    private boolean v;public boolean hasEntities() { return v; } // Paper - OBFHELPER
     private long lastSaved;
-    private boolean x;
+    private boolean x; public boolean isModified() { return x; } // Paper - OBFHELPER
     private int y;
     private long z;
     private int A;
@@ -1071,11 +1071,11 @@ public class Chunk implements IChunkAccess {
             if (this.v && this.world.getTime() != this.lastSaved || this.x) {
                 return true;
             }
-        } else if (this.v && this.world.getTime() >= this.lastSaved + MinecraftServer.getServer().autosavePeriod * 4) { // Spigot - Only save if we've passed 2 auto save intervals without modification
-            return true;
         }
-
-        return this.x;
+        // Paper start - Make world configurable and incremental
+        // This !flag section should say if isModified or hasEntities, then check auto save
+        return ((isModified() || hasEntities()) && this.world.getTime() >= this.lastSaved + world.paperConfig.autoSavePeriod);
+        // Paper end
     }
 
     public boolean isEmpty() {
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index d0bf0f72da..fbc69b5ba5 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -243,7 +243,7 @@ public class ChunkProviderServer implements IChunkProvider {
                     this.saveChunk(chunk, false); // Spigot
                     chunk.a(false);
                     ++i;
-                    if (i == 24 && !flag && false) { // Spigot
+                    if (!flag && i >= world.paperConfig.maxAutoSaveChunksPerTick) { // Spigot - // Paper - Incremental Auto Save - cap max
                         return false;
                     }
                 }
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 621e79bc53..260fa3e678 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -34,6 +34,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
 
     private static final Logger cc = LogManager.getLogger();
     public String locale = null; // CraftBukkit - lowercase // Paper - default to null
+    public long lastSave = MinecraftServer.currentTick; // Paper
     public PlayerConnection playerConnection;
     public final MinecraftServer server;
     public final PlayerInteractManager playerInteractManager;
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 7ffb061414..6283c774d6 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -143,6 +143,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
     public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
     public ConsoleReader reader;
     public static int currentTick = 0; // Paper - Further improve tick loop
+    public boolean serverAutoSave = false; // Paper
     public final Thread primaryThread;
     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
     public int autosavePeriod;
@@ -938,22 +939,30 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
             this.m.b().a(agameprofile);
         }
 
-        if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit
             this.methodProfiler.enter("save");
-            this.playerList.savePlayers();
+
+        serverAutoSave = (autosavePeriod > 0 && this.ticks % autosavePeriod == 0); // Paper
+        int playerSaveInterval = com.destroystokyo.paper.PaperConfig.playerAutoSaveRate;
+        if (playerSaveInterval < 0) {
+            playerSaveInterval = autosavePeriod;
+        }
+        if (playerSaveInterval > 0) { // CraftBukkit // Paper
+            this.playerList.savePlayers(playerSaveInterval);
             // Spigot Start
+        } // Paper - Incremental Auto Saving
+
             // We replace this with saving each individual world as this.saveChunks(...) is broken,
             // and causes the main thread to sleep for random amounts of time depending on chunk activity
             // Also pass flag to only save modified chunks
             server.playerCommandState = true;
             for (World world : getWorlds()) {
-                world.getWorld().save(false);
+                if (world.paperConfig.autoSavePeriod > 0) world.getWorld().save(false); // Paper - Incremental / Configurable Auto Saving
             }
             server.playerCommandState = false;
             // this.saveChunks(true);
             // Spigot End
             this.methodProfiler.exit();
-        }
+        //} // Paper - Incremental Auto Saving
 
         this.methodProfiler.enter("snooper");
         if (getSnooperEnabled() && !this.snooper.d() && this.ticks > 100) { // Spigot
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 79641a73a8..fe6649224a 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -341,6 +341,7 @@ public abstract class PlayerList {
 
     protected void savePlayerFile(EntityPlayer entityplayer) {
         if (!entityplayer.getBukkitEntity().isPersistent()) return; // CraftBukkit
+        entityplayer.lastSave = MinecraftServer.currentTick; // Paper
         this.playerFileData.save(entityplayer);
         ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit
 
@@ -1207,13 +1208,25 @@ public abstract class PlayerList {
 
     }
 
+    // Paper start
     public void savePlayers() {
+        savePlayers(null);
+    }
+
+    public void savePlayers(Integer interval) {
+        long now = MinecraftServer.currentTick;
         MinecraftTimings.savePlayers.startTiming(); // Paper
+        int numSaved = 0; // Paper
         for (int i = 0; i < this.players.size(); ++i) {
-            this.savePlayerFile((EntityPlayer) this.players.get(i));
+            EntityPlayer entityplayer = this.players.get(i);
+            if (interval == null || now - entityplayer.lastSave >= interval) {
+                this.savePlayerFile(entityplayer);
+                if (interval != null && ++numSaved <= com.destroystokyo.paper.PaperConfig.maxPlayerAutoSavePerTick) { break; } // Paper
+            }
         }
         MinecraftTimings.savePlayers.stopTiming(); // Paper
     }
+    // Paper end
 
     public WhiteList getWhitelist() {
         return this.whitelist;
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 639068d2dc..49c5b0b5cc 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -855,8 +855,9 @@ public class WorldServer extends World implements IAsyncTaskHandler {
         ChunkProviderServer chunkproviderserver = this.getChunkProvider();
 
         if (chunkproviderserver.d()) {
-            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 - Incremental Auto Saving - Only fire event on full save
             timings.worldSave.startTiming(); // Paper
+            if (flag || server.serverAutoSave) { // Paper
             if (iprogressupdate != null) {
                 iprogressupdate.a(new ChatMessage("menu.savingLevel", new Object[0]));
             }
@@ -865,6 +866,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
             if (iprogressupdate != null) {
                 iprogressupdate.c(new ChatMessage("menu.savingChunks", new Object[0]));
             }
+            } // Paper
 
             timings.worldSaveChunks.startTiming(); // Paper
             chunkproviderserver.a(flag);
-- 
2.21.0

