From 0000000000000000000000000000000000000000 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 621c585e7..da0984a35 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
         flyingKickPlayerMessage = getString("messages.kick.flying-player", flyingKickPlayerMessage);
         flyingKickVehicleMessage = getString("messages.kick.flying-vehicle", flyingKickVehicleMessage);
     }
+
+    public static int playerAutoSaveRate = -1;
+    private static void playerAutoSaveRate() {
+        playerAutoSaveRate = getInt("settings.player-auto-save-rate", -1);
+    }
 }
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index d182e716a..d1655de42 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -0,0 +0,0 @@ package com.destroystokyo.paper;
 
 import java.util.List;
 
+import net.minecraft.server.MinecraftServer;
 import org.bukkit.configuration.file.YamlConfiguration;
 import org.spigotmc.SpigotWorldConfig;
 
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
     private void elytraHitWallDamage() {
         elytraHitWallDamage = getBoolean("elytra-hit-wall-damage", true);
     }
+
+    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 8ec26b97c..87730aec3 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -0,0 +0,0 @@ public class Chunk {
             if (this.t && this.world.getTime() != this.lastSaved || this.s) {
                 return true;
             }
-        } else if (this.t && 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.s;
+        // This !flag section should say if s(isModified) or t(hasEntities), then check auto save
+        return ((this.s || this.t) && this.world.getTime() >= this.lastSaved + world.paperConfig.autoSavePeriod); // Paper - Make world configurable and incremental
     }
 
     public Random a(long i) {
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 0adfcaa8b..9e8f67ffd 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -0,0 +0,0 @@
 package net.minecraft.server;
 
+import com.destroystokyo.paper.PaperConfig;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
                 this.saveChunk(chunk, false); // Spigot
                 chunk.f(false);
                 ++i;
-                if (i == 24 && !flag && false) { // Spigot
+                if (!flag && i >= world.paperConfig.maxAutoSaveChunksPerTick) { // Spigot - // Paper - Incremental Auto Save - cap max per tick
                     return false;
                 }
             }
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index b937b6d0d..ef081a57f 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
 
     private static final Logger bV = LogManager.getLogger();
     public String locale = null; // PAIL: private -> public // 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 ab7933079..5c09c6ff7 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
     public final Thread primaryThread;
     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
     public int autosavePeriod;
+    public boolean serverAutoSave = false; // Paper
     // CraftBukkit end
     // Spigot start
     public final SlackActivityAccountant slackActivityAccountant = new SlackActivityAccountant();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
             this.q.b().a(agameprofile);
         }
 
-        if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit
             this.methodProfiler.a("save");
-            this.v.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.v.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 : worlds) {
-                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.b();
-        }
+        //} // Paper - Incremental Auto Saving
 
         this.methodProfiler.a("tallying");
         // Spigot start
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index ed5852ef4..0e82c16b7 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -0,0 +0,0 @@ public abstract class PlayerList {
     }
 
     protected void savePlayerFile(EntityPlayer entityplayer) {
+        entityplayer.lastSave = MinecraftServer.currentTick; // Paper
         this.playerFileData.save(entityplayer);
         ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit
 
@@ -0,0 +0,0 @@ public abstract class PlayerList {
 
     }
 
+    // Paper start
     public void savePlayers() {
+        savePlayers(null);
+    }
+
+    public void savePlayers(Integer interval) {
+        long now = MinecraftServer.currentTick;
         MinecraftTimings.savePlayers.startTiming(); // 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);
+            }
         }
         MinecraftTimings.savePlayers.stopTiming(); // Paper
     }
+    // Paper end
 
     public void addWhitelist(GameProfile gameprofile) {
         this.whitelist.add(new WhiteListEntry(gameprofile));
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index f2ddc22dd..8493dccee 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -0,0 +0,0 @@ public class WorldServer extends World implements IAsyncTaskHandler {
         ChunkProviderServer chunkproviderserver = this.getChunkProviderServer();
 
         if (chunkproviderserver.e()) {
-            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("Saving level");
             }
@@ -0,0 +0,0 @@ public class WorldServer extends World implements IAsyncTaskHandler {
             if (iprogressupdate != null) {
                 iprogressupdate.c("Saving chunks");
             }
+            } // Paper
 
             timings.worldSaveChunks.startTiming(); // Paper
             chunkproviderserver.a(flag);
--