From 1de60dda62bc84a8d2e00e038deceb2fa132195e Mon Sep 17 00:00:00 2001 From: Poweruser Date: Wed, 25 May 2016 06:10:55 +0200 Subject: [PATCH] Distribute autosave among multiple ticks diff --git a/src/main/java/net/frozenorb/autosave/AutoSave.java b/src/main/java/net/frozenorb/autosave/AutoSave.java new file mode 100644 index 000000000..216aa1287 --- /dev/null +++ b/src/main/java/net/frozenorb/autosave/AutoSave.java @@ -0,0 +1,197 @@ +package net.frozenorb.autosave; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Queue; + +import org.bukkit.event.world.WorldSaveEvent; +import org.spigotmc.SpigotConfig; + +import net.minecraft.server.ExceptionWorldConflict; +import net.minecraft.server.FileIOThread; +import net.minecraft.server.IProgressUpdate; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.RegionFileCache; +import net.minecraft.server.WorldServer; + +public class AutoSave { + + private AutoSaveStep step; + private Queue levelAndMapsQueue; + private Queue saveChunksQueue; + private Queue unloadChunksQueue; + private Queue eventQueue; + private long fileioStart; + private long fileioEnd; + private int regionFileCount; + private long regionFileCacheStart; + private long regionFileCacheEnd; + private int chunkQueuedCount; + + public AutoSave() { + this.levelAndMapsQueue = new ArrayDeque(); + this.saveChunksQueue = new ArrayDeque(); + this.unloadChunksQueue = new ArrayDeque(); + this.eventQueue = new ArrayDeque(); + this.reset(); + } + + public void queueWorld(WorldServer worldserver) { + this.levelAndMapsQueue.add(worldserver); + this.saveChunksQueue.add(worldserver); + this.unloadChunksQueue.add(worldserver); + this.eventQueue.add(worldserver); + } + + public void reset() { + this.levelAndMapsQueue.clear(); + this.saveChunksQueue.clear(); + this.unloadChunksQueue.clear(); + this.eventQueue.clear(); + this.step = AutoSaveStep.IDLE; + this.chunkQueuedCount = 0; + } + + private void moveToNextStep() { + this.step = AutoSaveStep.nextStep(this.step); + } + + public boolean execute() throws ExceptionWorldConflict { + WorldServer worldServer; + switch(this.step) { + default: + case IDLE: + break; + case START: + MinecraftServer.getLogger().info("[Autosave] Started .."); + for(WorldServer world: this.saveChunksQueue) { + world.getAutoSaveWorldData().setLastAutosaveTimeStamp(); + } + this.moveToNextStep(); + return this.execute(); + case SAVE_PLAYERS: + MinecraftServer.getServer().getPlayerList().savePlayers(); + this.moveToNextStep(); + break; + case SAVE_LEVEL_AND_MAPS: + worldServer = this.levelAndMapsQueue.poll(); + if(worldServer != null) { + worldServer.saveOnlyLevel(true, null); + } + + if(this.levelAndMapsQueue.isEmpty()) { + this.moveToNextStep(); + } + + break; + case SAVE_CHUNKS: + worldServer = this.saveChunksQueue.peek(); + if(worldServer != null) { + if(worldServer.saveOnlyChunks(false, null)) { + this.chunkQueuedCount += worldServer.getAutoSaveWorldData().getAutoSaveChunkCount(); + this.saveChunksQueue.poll(); + } + } + + if(this.saveChunksQueue.isEmpty()) { + this.moveToNextStep(); + } + + break; + case UNLOAD_CHUNKS: + worldServer = this.unloadChunksQueue.poll(); + if(worldServer != null) { + worldServer.unloadOnlyUnusedChunks(true, null); + } + + if(this.unloadChunksQueue.isEmpty()) { + this.moveToNextStep(); + } + + break; + case WRITE_FILES_START: + FileIOThread.a.setNoDelay(true); + this.fileioStart = System.nanoTime(); + this.moveToNextStep(); + break; + case WRITE_FILES_WAIT: + if(FileIOThread.a.isDone()) { + this.fileioEnd = System.nanoTime(); + FileIOThread.a.setNoDelay(false); + this.moveToNextStep(); + } + break; + case FIRE_WORLDSAVEEVENT: + if(SpigotConfig.autoSaveClearRegionFileCache) { + this.regionFileCount = RegionFileCache.a.size(); + this.regionFileCacheStart = System.nanoTime(); + RegionFileCache.a(); + this.regionFileCacheEnd = System.nanoTime(); + } + + if(SpigotConfig.autoSaveFireWorldSaveEvent) { + for(WorldServer worldserver: this.eventQueue) { + WorldSaveEvent event = new WorldSaveEvent(worldserver.getWorld()); + MinecraftServer.getServer().server.getPluginManager().callEvent(event); + } + } + this.moveToNextStep(); + break; + case FINISHED: + MinecraftServer.getLogger().info("[Autosave] Done. Queued " + this.chunkQueuedCount + " chunks for saving. Took " + formatLongTime(this.fileioEnd - this.fileioStart) + " seconds to write them."); + if(SpigotConfig.autoSaveClearRegionFileCache) { + MinecraftServer.getLogger().info("[Autosave] Cleared " + this.regionFileCount + " cached region files in " + formatLongTime(this.regionFileCacheEnd - this.regionFileCacheStart) + " seconds."); + } + this.reset(); + return true; + } + return false; + } + + private static String formatLongTime(long duration) { + return String.format("%.2f", ((double) (duration / 1000000L)) / 1000.0D); + } + + public boolean isActive() { + return this.step.isActiveState(); + } + + public void start() { + this.step = AutoSaveStep.START; + } + + public enum AutoSaveStep { + IDLE(false), + START(), + SAVE_PLAYERS(), + SAVE_LEVEL_AND_MAPS(), + SAVE_CHUNKS(), + UNLOAD_CHUNKS(), + WRITE_FILES_START(), + WRITE_FILES_WAIT(), + FIRE_WORLDSAVEEVENT(), + FINISHED(); + + private final boolean activeState; + + private AutoSaveStep() { + this(true); + } + + private AutoSaveStep(boolean activeState) { + this.activeState = activeState; + } + + public static AutoSaveStep nextStep(AutoSaveStep current) { + AutoSaveStep[] values = AutoSaveStep.values(); + if(current.ordinal() + 1 < values.length) { + return values[current.ordinal() + 1]; + } + return current; + } + + public boolean isActiveState() { + return this.activeState; + } + } +} diff --git a/src/main/java/net/frozenorb/autosave/AutoSaveWorldData.java b/src/main/java/net/frozenorb/autosave/AutoSaveWorldData.java new file mode 100644 index 000000000..0139d6891 --- /dev/null +++ b/src/main/java/net/frozenorb/autosave/AutoSaveWorldData.java @@ -0,0 +1,33 @@ +package net.frozenorb.autosave; + +import net.minecraft.server.World; +import net.minecraft.server.WorldData; + +public class AutoSaveWorldData { + + private long lastAutoSaveTimeStamp; + private int autoSaveChunkCount; + private final World world; + + public AutoSaveWorldData(World world) { + this.world = world; + this.setLastAutosaveTimeStamp(); + } + + public void setLastAutosaveTimeStamp() { + this.lastAutoSaveTimeStamp = this.world.worldData.getTime(); + this.autoSaveChunkCount = 0; + } + + public long getLastAutosaveTimeStamp() { + return this.lastAutoSaveTimeStamp; + } + + public void addAutoSaveChunkCount(int count) { + this.autoSaveChunkCount += count; + } + + public int getAutoSaveChunkCount() { + return this.autoSaveChunkCount; + } +} diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 9e3e66806..d3453f644 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -931,10 +931,15 @@ public class Chunk { if (this.o && this.world.getTime() != this.lastSaved || this.n) { return true; } - } else if (this.o && this.world.getTime() >= this.lastSaved + MinecraftServer.getServer().autosavePeriod * 4) { // PaperSpigot - Only save if we've passed 2 auto save intervals without modification + // Poweruser start + } else if (this.n && this.world.getAutoSaveWorldData().getLastAutosaveTimeStamp() >= this.lastSaved) { + return true; + } else if (this.o && this.world.getTime() >= this.lastSaved + MinecraftServer.getServer().autosavePeriod) { return true; + } else { + return false; } - + // Poweruser end return this.n; } diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index 1e297ab7e..b4ddc3bbf 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -331,11 +331,19 @@ public class ChunkProviderServer implements IChunkProvider { this.saveChunk(chunk); chunk.n = false; ++i; - if (i == 24 && !flag) { + // Poweruser start + if (i >= org.spigotmc.SpigotConfig.autoSaveChunksPerTick && !flag) { + this.world.getAutoSaveWorldData().addAutoSaveChunkCount(i); + // Poweruser end return false; } } } + // Poweruser start + if(!flag) { + this.world.getAutoSaveWorldData().addAutoSaveChunkCount(i); + } + // Poweruser end return true; } diff --git a/src/main/java/net/minecraft/server/FileIOThread.java b/src/main/java/net/minecraft/server/FileIOThread.java index e9c333bfb..28488a2f8 100644 --- a/src/main/java/net/minecraft/server/FileIOThread.java +++ b/src/main/java/net/minecraft/server/FileIOThread.java @@ -67,4 +67,14 @@ public class FileIOThread implements Runnable { this.e = false; } + + // Poweruser start + public boolean isDone() { + return this.c == this.d; + } + + public void setNoDelay(boolean active) { + this.e = active; + } + // Poweruser end } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 36d95e7a9..81d36ab27 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -45,6 +45,7 @@ import org.bukkit.event.world.WorldSaveEvent; // Poweruser start import net.frozenorb.ThreadingManager; +import net.frozenorb.autosave.AutoSave; // Poweruser end public abstract class MinecraftServer implements ICommandListener, Runnable, IMojangStatistics { @@ -126,6 +127,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo // Poweruser start private ThreadingManager threadingManager; + private AutoSave autoSaveManager; // Poweruser end public float lastTickTime = 0F; // MineHQ @@ -136,6 +138,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo j = this; this.d = proxy; this.threadingManager = new ThreadingManager(); // Poweruser + this.autoSaveManager = new AutoSave(); // Poweruser // this.universe = file1; // CraftBukkit // this.p = new ServerConnection(this); // Spigot this.o = new CommandDispatcher(); @@ -620,7 +623,22 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.q.b().a(agameprofile); } - if ((this.autosavePeriod > 0) && ((this.ticks % this.autosavePeriod) == 0)) { // CraftBukkit + // Poweruser start + if(this.autoSaveManager.isActive()) { + SpigotTimings.worldSaveTimer.startTiming(); // Spigot + server.playerCommandState = true; + this.autoSaveManager.execute(); + server.playerCommandState = false; + SpigotTimings.worldSaveTimer.stopTiming(); // Spigot + } else if ((this.autosavePeriod > 0) && ((this.ticks % this.autosavePeriod) == 0)) { // CraftBukkit + this.autoSaveManager.reset(); + for(WorldServer worldserver: worlds) { + this.autoSaveManager.queueWorld(worldserver); + } + this.autoSaveManager.start(); + } + // Poweruser end + /* SpigotTimings.worldSaveTimer.startTiming(); // Spigot this.methodProfiler.a("save"); this.u.savePlayers(); @@ -638,6 +656,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.methodProfiler.b(); SpigotTimings.worldSaveTimer.stopTiming(); // Spigot } + */ this.methodProfiler.a("tallying"); this.g[this.ticks % 100] = System.nanoTime() - i; diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 609fe32ae..54e304a7d 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -42,6 +42,7 @@ import net.frozenorb.LightingUpdater; import net.frozenorb.WeakChunkCache; import net.frozenorb.ThreadingManager; import net.frozenorb.ThreadingManager.TaskQueueWorker; +import net.frozenorb.autosave.AutoSaveWorldData; // Poweruser end public abstract class World implements IBlockAccess { @@ -212,6 +213,16 @@ public abstract class World implements IBlockAccess { return this.worldProvider.e; } + + // Poweruser start + public ChunkProviderServer chunkProviderServer; // moved here from WorldServer + private final AutoSaveWorldData autoSaveWorldData; + + public AutoSaveWorldData getAutoSaveWorldData() { + return this.autoSaveWorldData; + } + // Poweruser end + // CraftBukkit start private final CraftWorld world; public boolean pvpMode; @@ -313,6 +324,8 @@ public abstract class World implements IBlockAccess { if (!spigotConfig.mobsEnabled) { this.world.setSpawnFlags(false, false); } + + this.autoSaveWorldData = new AutoSaveWorldData(this); // Poweruser } protected abstract IChunkProvider j(); diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index a5d634fbb..8627ef832 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -337,7 +337,7 @@ public class WorldServer extends World { this.timings.doTickTiles_tickingChunks_getChunk.startTiming(); // Poweruser // Poweruser start Chunk chunk = this.getChunkIfLoaded(chunkX, chunkZ); - if(chunk == null || chunk.wasUnloaded() || !chunk.areNeighborsLoaded(1) || this.chunkProviderServer.unloadQueue.contains( chunkX, chunkZ )) { + if(chunk == null || !chunk.areNeighborsLoaded(1) || this.chunkProviderServer.unloadQueue.contains( chunkX, chunkZ )) { iter.remove(); continue; } @@ -811,18 +811,30 @@ public class WorldServer extends World { return this.worldProvider.h(); } - public void save(boolean flag, IProgressUpdate iprogressupdate) throws ExceptionWorldConflict { // CraftBukkit - added throws + // Poweruser start + public void saveOnlyLevel(boolean flag, IProgressUpdate iprogressupdate) throws ExceptionWorldConflict { if (this.chunkProvider.canSave()) { if (iprogressupdate != null) { iprogressupdate.a("Saving level"); } this.a(); + } + } + + public boolean saveOnlyChunks(boolean flag, IProgressUpdate iprogressupdate) { + if (this.chunkProvider.canSave()) { if (iprogressupdate != null) { iprogressupdate.c("Saving chunks"); } - this.chunkProvider.saveChunks(flag, iprogressupdate); + return this.chunkProvider.saveChunks(flag, iprogressupdate); + } + return true; + } + + public void unloadOnlyUnusedChunks(boolean flag, IProgressUpdate iprogressupdate) { + if (this.chunkProvider.canSave()) { // CraftBukkit - ArrayList -> Collection Collection arraylist = this.chunkProviderServer.a(); Iterator iterator = arraylist.iterator(); @@ -836,6 +848,17 @@ public class WorldServer extends World { } } } + // Poweruser end + + public void save(boolean flag, IProgressUpdate iprogressupdate) throws ExceptionWorldConflict { // CraftBukkit - added throws + if (this.chunkProvider.canSave()) { + // Poweruser start + this.saveOnlyLevel(flag, iprogressupdate); + this.saveOnlyChunks(flag, iprogressupdate); + this.unloadOnlyUnusedChunks(flag, iprogressupdate); + // Poweruser end + } + } public void flushSave() { if (this.chunkProvider.canSave()) { diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java index eff9a948e..c2d272c5f 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -430,5 +430,20 @@ public class SpigotConfig private static void playersPerChunkIOThread() { playersPerChunkIOThread = Math.max(1, getInt( "settings.chunkio.players-per-thread", 150) ); } + + public static int autoSaveChunksPerTick; + private static void autoSaveChunksPerTick() { + autoSaveChunksPerTick = getInt( "settings.autosave.chunks-per-tick" , 200 ); + } + + public static boolean autoSaveFireWorldSaveEvent; + private static void autoSaveFireWorldSaveEvent() { + autoSaveFireWorldSaveEvent = getBoolean ( "settings.autosave.fire-WorldSaveEvent", false); + } + + public static boolean autoSaveClearRegionFileCache; + private static void autoSaveClearRegionFileCache() { + autoSaveClearRegionFileCache = getBoolean ( "settings.autosave.clear-RegionFileCache", false); + } // Poweruser end } -- 2.13.3