diff --git a/src/main/java/serverutils/ServerUtilitiesConfig.java b/src/main/java/serverutils/ServerUtilitiesConfig.java index ed7ef30f..761e1ef4 100644 --- a/src/main/java/serverutils/ServerUtilitiesConfig.java +++ b/src/main/java/serverutils/ServerUtilitiesConfig.java @@ -276,6 +276,9 @@ public static class Commands { @Config.DefaultBoolean(true) public boolean dump_stats; + + @Config.DefaultBoolean(true) + public boolean pregen; } public static class Backups { diff --git a/src/main/java/serverutils/command/ServerUtilitiesCommands.java b/src/main/java/serverutils/command/ServerUtilitiesCommands.java index 3ae76c4d..31f7e9d0 100644 --- a/src/main/java/serverutils/command/ServerUtilitiesCommands.java +++ b/src/main/java/serverutils/command/ServerUtilitiesCommands.java @@ -3,6 +3,7 @@ import cpw.mods.fml.common.event.FMLServerStartingEvent; import serverutils.ServerUtilitiesConfig; import serverutils.command.chunks.CmdChunks; +import serverutils.command.pregen.CmdPregen; import serverutils.command.ranks.CmdRanks; import serverutils.command.team.CmdTeam; import serverutils.command.tp.CmdBack; @@ -143,5 +144,8 @@ public static void registerCommands(FMLServerStartingEvent event) { if (ServerUtilitiesConfig.commands.dump_stats) { event.registerServerCommand(new CmdDumpStats()); } + if (ServerUtilitiesConfig.commands.pregen) { + event.registerServerCommand(new CmdPregen()); + } } } diff --git a/src/main/java/serverutils/command/pregen/CmdPregen.java b/src/main/java/serverutils/command/pregen/CmdPregen.java new file mode 100644 index 00000000..4bcfb26a --- /dev/null +++ b/src/main/java/serverutils/command/pregen/CmdPregen.java @@ -0,0 +1,15 @@ +package serverutils.command.pregen; + +import serverutils.lib.command.CmdTreeBase; +import serverutils.lib.command.CmdTreeHelp; + +public class CmdPregen extends CmdTreeBase { + + public CmdPregen() { + super("pregen"); + addSubcommand(new CmdProgress()); + addSubcommand(new CmdStart()); + addSubcommand(new CmdStop()); + addSubcommand(new CmdTreeHelp(this)); + } +} diff --git a/src/main/java/serverutils/command/pregen/CmdProgress.java b/src/main/java/serverutils/command/pregen/CmdProgress.java new file mode 100644 index 00000000..3abb2d78 --- /dev/null +++ b/src/main/java/serverutils/command/pregen/CmdProgress.java @@ -0,0 +1,23 @@ +package serverutils.command.pregen; + +import net.minecraft.command.ICommandSender; +import net.minecraft.util.ChatComponentText; + +import serverutils.lib.command.CmdBase; +import serverutils.pregenerator.ChunkLoaderManager; + +public class CmdProgress extends CmdBase { + + public CmdProgress() { + super("progress", Level.OP); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + if (ChunkLoaderManager.instance.isGenerating()) { + sender.addChatMessage(new ChatComponentText(ChunkLoaderManager.instance.progressString())); + } else { + sender.addChatMessage(new ChatComponentText("No generator running.")); + } + } +} diff --git a/src/main/java/serverutils/command/pregen/CmdStart.java b/src/main/java/serverutils/command/pregen/CmdStart.java new file mode 100644 index 00000000..2586ecf5 --- /dev/null +++ b/src/main/java/serverutils/command/pregen/CmdStart.java @@ -0,0 +1,98 @@ +package serverutils.command.pregen; + +import java.io.IOException; + +import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentText; + +import serverutils.lib.command.CmdBase; +import serverutils.lib.command.ICommandWithParent; +import serverutils.lib.util.misc.PregeneratorCommandInfo; +import serverutils.pregenerator.ChunkLoaderManager; + +public class CmdStart extends CmdBase { + + public CmdStart() { + super("start", Level.OP); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + checkArgs(sender, args, 1); + + int radius; + double xLoc, zLoc; + + if (args.length == 3) { + xLoc = parseDoubleBounded(sender, args[0], -30000000.0D, 30000000.0D); + zLoc = parseDoubleBounded(sender, args[1], -30000000.0D, 30000000.0D); + + radius = parseInt(sender, args[2]); + if (radius > 2000) { + sender.addChatMessage( + new ChatComponentText( + "Radii larger than 2000 are not permitted. World sizes will be 100's of gbs")); + return; + } + } else { + xLoc = sender.getPlayerCoordinates().posX; + zLoc = sender.getPlayerCoordinates().posZ; + + radius = parseInt(sender, args[0]); + if (radius > 2000) { + sender.addChatMessage( + new ChatComponentText( + "Radii larger than 2000 are not permitted. World sizes will be 100's of gbs")); + return; + } + } + + int dimensionID = sender.getEntityWorld().provider.dimensionId; + PregeneratorCommandInfo commandInfo = new PregeneratorCommandInfo(xLoc, zLoc, radius, dimensionID); + + if (!ChunkLoaderManager.instance.isGenerating()) { + try { + sender.addChatMessage( + new ChatComponentText("Initializing pregenerator. Check progress with '/pregen progress'.")); + ChunkLoaderManager.instance.initializePregenerator(commandInfo, MinecraftServer.getServer()); + } catch (IOException e) { + e.printStackTrace(); + sender.addChatMessage( + new ChatComponentText( + "Cannot start a pregenerator! File exception when starting pregenerator!")); + } + } else { + sender.addChatMessage( + new ChatComponentText("Cannot start a pregenerator! There's already generation in progress!")); + } + } + + @Override + public void checkArgs(ICommandSender sender, String[] args, int i) { + + if (args.length < i) { + throw new WrongUsageException(getCommandUsage(sender)); + } + + if (sender instanceof MinecraftServer) { + if (args.length != 3) { + throw new WrongUsageException(getCommandUsage(sender)); + } + } else { + if (!(args.length == 1 || args.length == 3)) { + throw new WrongUsageException(getCommandUsage(sender)); + } + } + } + + @Override + public String getCommandUsage(ICommandSender sender) { + if (sender instanceof MinecraftServer) { + return "commands." + ICommandWithParent.getCommandPath(this) + ".usage_server"; + } + + return "commands." + ICommandWithParent.getCommandPath(this) + ".usage_client"; + } +} diff --git a/src/main/java/serverutils/command/pregen/CmdStop.java b/src/main/java/serverutils/command/pregen/CmdStop.java new file mode 100644 index 00000000..efe1086d --- /dev/null +++ b/src/main/java/serverutils/command/pregen/CmdStop.java @@ -0,0 +1,24 @@ +package serverutils.command.pregen; + +import net.minecraft.command.ICommandSender; +import net.minecraft.util.ChatComponentText; + +import serverutils.lib.command.CmdBase; +import serverutils.pregenerator.ChunkLoaderManager; + +public class CmdStop extends CmdBase { + + public CmdStop() { + super("stop", Level.OP); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + if (ChunkLoaderManager.instance.isGenerating()) { + ChunkLoaderManager.instance.reset(true); + sender.addChatMessage(new ChatComponentText("Cancelling pregeneration.")); + } else { + sender.addChatMessage(new ChatComponentText("No generator running.")); + } + } +} diff --git a/src/main/java/serverutils/handlers/ServerUtilitiesServerEventHandler.java b/src/main/java/serverutils/handlers/ServerUtilitiesServerEventHandler.java index 70cceb85..4724c451 100644 --- a/src/main/java/serverutils/handlers/ServerUtilitiesServerEventHandler.java +++ b/src/main/java/serverutils/handlers/ServerUtilitiesServerEventHandler.java @@ -38,6 +38,7 @@ import serverutils.lib.util.text_components.Notification; import serverutils.lib.util.text_components.TextComponentParser; import serverutils.net.MessageUpdatePlayTime; +import serverutils.pregenerator.ChunkLoaderManager; import serverutils.ranks.Ranks; public class ServerUtilitiesServerEventHandler { @@ -227,6 +228,10 @@ public void onServerTick(TickEvent.ServerTickEvent event) { playerToKickForAfk.playerNetServerHandler .onDisconnect(new ChatComponentTranslation("multiplayer.disconnect.idling")); } + + if (ChunkLoaderManager.instance.isGenerating()) { + ChunkLoaderManager.instance.queueChunks(1); + } } } diff --git a/src/main/java/serverutils/handlers/ServerUtilitiesWorldEventHandler.java b/src/main/java/serverutils/handlers/ServerUtilitiesWorldEventHandler.java index 83815fdc..889609a1 100644 --- a/src/main/java/serverutils/handlers/ServerUtilitiesWorldEventHandler.java +++ b/src/main/java/serverutils/handlers/ServerUtilitiesWorldEventHandler.java @@ -18,11 +18,13 @@ import net.minecraftforge.event.world.WorldEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import serverutils.ServerUtilities; import serverutils.ServerUtilitiesConfig; import serverutils.data.ClaimedChunk; import serverutils.data.ClaimedChunks; import serverutils.data.ServerUtilitiesUniverseData; import serverutils.lib.math.ChunkDimPos; +import serverutils.pregenerator.ChunkLoaderManager; public class ServerUtilitiesWorldEventHandler { @@ -95,4 +97,26 @@ public void onExplosionDetonate(ExplosionEvent.Detonate event) { } } } + + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load event) { + if (!event.world.isRemote) { + int dimensionId = event.world.provider.dimensionId; + MinecraftServer server = MinecraftServer.getServer(); + if (!ChunkLoaderManager.instance.isGenerating() + && ChunkLoaderManager.instance.initializeFromPregeneratorFiles(server, dimensionId)) { + ServerUtilities.LOGGER.info("Pregenerator loaded and running for dimension Id: " + dimensionId); + } + } + } + + @SubscribeEvent + public void onWorldUnload(WorldEvent.Unload event) { + if (!event.world.isRemote) { + if (event.world.provider.dimensionId == ChunkLoaderManager.instance.getDimensionID() + && ChunkLoaderManager.instance.isGenerating()) { + ChunkLoaderManager.instance.reset(false); + } + } + } } diff --git a/src/main/java/serverutils/lib/util/misc/PregeneratorCommandInfo.java b/src/main/java/serverutils/lib/util/misc/PregeneratorCommandInfo.java new file mode 100644 index 00000000..343dc0ac --- /dev/null +++ b/src/main/java/serverutils/lib/util/misc/PregeneratorCommandInfo.java @@ -0,0 +1,42 @@ +package serverutils.lib.util.misc; + +public class PregeneratorCommandInfo { + + private final double xLoc; + private final double zLoc; + private final int radius; + private final int dimensionID; + private final int iteration; + + public PregeneratorCommandInfo(double xLoc, double zLoc, int radius, int dimensionID, int iteration) { + this.xLoc = xLoc; + this.zLoc = zLoc; + this.radius = radius; + this.iteration = iteration; + this.dimensionID = dimensionID; + } + + public PregeneratorCommandInfo(double xLoc, double zLoc, int radius, int dimensionID) { + this(xLoc, zLoc, radius, dimensionID, -1); + } + + public double getXLoc() { + return xLoc; + } + + public double getZLoc() { + return zLoc; + } + + public int getRadius() { + return radius; + } + + public int getIteration() { + return this.iteration; + } + + public int getDimensionID() { + return this.dimensionID; + } +} diff --git a/src/main/java/serverutils/pregenerator/ChunkLoader.java b/src/main/java/serverutils/pregenerator/ChunkLoader.java new file mode 100644 index 00000000..5d13fcec --- /dev/null +++ b/src/main/java/serverutils/pregenerator/ChunkLoader.java @@ -0,0 +1,35 @@ +package serverutils.pregenerator; + +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.unpackX; +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.unpackZ; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.gen.ChunkProviderServer; + +import serverutils.ServerUtilities; +import serverutils.pregenerator.filemanager.PregeneratorFileManager; + +public class ChunkLoader { + + private PregeneratorFileManager fileManager; + private int loadIteration = 0; + + public ChunkLoader(PregeneratorFileManager fileManager) { + this.fileManager = fileManager; + } + + public void processLoadChunk(MinecraftServer server, int dimensionId, Long chunk) { + int x = unpackX(chunk); + int z = unpackZ(chunk); + + ChunkProviderServer cps = server.worldServerForDimension(dimensionId).theChunkProviderServer; + cps.loadChunk(x, z, () -> { + ChunkLoaderManager.instance.removeChunkFromList(); + this.fileManager.saveIteration(ChunkLoaderManager.instance.getChunkToLoadSize()); + loadIteration++; + if (loadIteration % 100 == 0) { + ServerUtilities.LOGGER.info(ChunkLoaderManager.instance.progressString()); + } + }); + } +} diff --git a/src/main/java/serverutils/pregenerator/ChunkLoaderManager.java b/src/main/java/serverutils/pregenerator/ChunkLoaderManager.java new file mode 100644 index 00000000..004bf4e2 --- /dev/null +++ b/src/main/java/serverutils/pregenerator/ChunkLoaderManager.java @@ -0,0 +1,196 @@ +package serverutils.pregenerator; + +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.pack; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import net.minecraft.server.MinecraftServer; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import serverutils.lib.util.misc.PregeneratorCommandInfo; +import serverutils.pregenerator.filemanager.PregeneratorFileManager; + +public class ChunkLoaderManager { + + public final static ChunkLoaderManager instance = new ChunkLoaderManager(); + private boolean isGenerating = false; + private int dimensionID; + private MinecraftServer serverType; + private LongArrayList chunksToLoad = new LongArrayList(1000); + private int totalChunksToLoad; + private int chunkToLoadIndex; + private ChunkLoader loader; + private PregeneratorFileManager fileManager; + + public void initializePregenerator(PregeneratorCommandInfo commandInfo, MinecraftServer server) throws IOException { + findChunksToLoadCircle(commandInfo.getRadius(), commandInfo.getXLoc(), commandInfo.getZLoc()); + this.totalChunksToLoad = chunksToLoad.size(); + this.chunkToLoadIndex = chunksToLoad.size() - 1; + this.dimensionID = commandInfo.getDimensionID(); + this.isGenerating = true; + this.serverType = server; + this.fileManager = new PregeneratorFileManager( + this.serverType, + commandInfo.getXLoc(), + commandInfo.getZLoc(), + commandInfo.getRadius(), + commandInfo.getDimensionID()); + this.loader = new ChunkLoader(this.fileManager); + } + + public boolean initializeFromPregeneratorFiles(MinecraftServer server, int dimensionToCheck) { + try { + Path commandFolderPath = Paths.get("saves").resolve(server.getFolderName()) + .resolve(PregeneratorFileManager.COMMAND_FOLDER); + if (Files.exists(commandFolderPath.resolve(PregeneratorFileManager.COMMAND_FILE)) + && Files.exists(commandFolderPath.resolve(PregeneratorFileManager.COMMAND_ITERATION))) { + this.fileManager = new PregeneratorFileManager(server); + Optional commandInfoOptional = this.fileManager.getCommandInfo(); + if (commandInfoOptional.isPresent()) { + PregeneratorCommandInfo commandInfo = commandInfoOptional.get(); + if (commandInfo.getDimensionID() != dimensionToCheck) { + return false; + } + findChunksToLoadCircle(commandInfo.getRadius(), commandInfo.getXLoc(), commandInfo.getZLoc()); + this.totalChunksToLoad = chunksToLoad.size(); + this.chunkToLoadIndex = commandInfo.getIteration() - 1; + this.dimensionID = commandInfo.getDimensionID(); + if (this.chunkToLoadIndex < chunksToLoad.size()) { + this.chunksToLoad.subList(this.chunkToLoadIndex + 1, chunksToLoad.size()).clear(); + this.serverType = server; + this.isGenerating = true; + this.loader = new ChunkLoader(this.fileManager); + return this.fileManager.isReady(); + } + } + } + + } catch (IOException e) { + this.reset(true); + e.printStackTrace(); + } + return false; + } + + public boolean isGenerating() { + return this.isGenerating; + } + + // Passed in xCenter and passed in zCenter are both in block coordinates. Be sure to transform to chunk coordinates + // I've done a ton of testing with this. It works without duplicates and holes in the raster. + public void findChunksToLoadCircle(int radius, double xCenter, double zCenter) { + // This is a solved problem. I'll use the wikipedia entry on this: + // https://en.wikipedia.org/wiki/Midpoint_circle_algorithm + int chunkXCenter = (int) Math.floor(xCenter / 16); + int chunkZCenter = (int) Math.floor(zCenter / 16); + double decisionTracker = 1 - radius; // This is used to tell if we need to step X down. + int x = radius; + int z = 0; + int previousX = radius; + while (x >= z) { + // Add all symmetrical points + addChunk(chunkXCenter + x, chunkZCenter + z); + addChunk(chunkXCenter - x, chunkZCenter + z); + if (z != x) { + addChunk(chunkXCenter + z, chunkZCenter + x); + addChunk(chunkXCenter + z, chunkZCenter - x); + } + + if (z != 0) { + addChunk(chunkXCenter + x, chunkZCenter - z); + addChunk(chunkXCenter - x, chunkZCenter - z); + if (z != x) { + addChunk(chunkXCenter - z, chunkZCenter + x); + addChunk(chunkXCenter - z, chunkZCenter - x); + } + + } + + if (x != previousX) { + addChunksBetween(chunkXCenter + x, chunkZCenter - z, chunkZCenter + z); + addChunksBetween(chunkXCenter - x, chunkZCenter - z, chunkZCenter + z); + } + previousX = x; + + if (x != z) { + addChunksBetween(chunkXCenter + z, chunkZCenter - x, chunkZCenter + x); + if (z != 0) { + addChunksBetween(chunkXCenter - z, chunkZCenter - x, chunkZCenter + x); + } + } + + z++; + if (decisionTracker < 0) { + decisionTracker += 2 * z + 1; + } else { + x--; + decisionTracker += 2 * (z - x) + 1; + } + } + System.out.printf("Found %s chunks to load", chunksToLoad.size()); + } + + public void removeChunkFromList() { + this.chunksToLoad.remove(this.chunksToLoad.size() - 1); + } + + public int getChunkToLoadSize() { + return this.chunksToLoad.size(); + } + + public int getTotalChunksToLoad() { + return this.totalChunksToLoad; + } + + public int getDimensionID() { + return dimensionID; + } + + public void queueChunks(int numChunksToQueue) { + for (int i = 0; i < numChunksToQueue; i++) { + if (!chunksToLoad.isEmpty()) { + loader.processLoadChunk(this.serverType, this.dimensionID, chunksToLoad.getLong(chunkToLoadIndex)); + chunkToLoadIndex--; + } else { + fileManager.closeAndRemoveAllFiles(); + isGenerating = false; + } + } + } + + public String progressString() { + int chunksLoaded = totalChunksToLoad - chunksToLoad.size();; + double percentage = (double) chunksLoaded / totalChunksToLoad * 100; + return String + .format("Loaded %d chunks of a total of %d. %.1f%% done.", chunksLoaded, totalChunksToLoad, percentage); + } + + public void reset(boolean hardReset) { + this.isGenerating = false; + this.chunksToLoad.clear(); + this.chunkToLoadIndex = -1; + this.serverType = null; + this.loader = null; + this.dimensionID = Integer.MIN_VALUE; + if (hardReset) { + fileManager.closeAndRemoveAllFiles(); + } else { + fileManager.closeAllFiles(); + } + this.fileManager = null; + } + + private void addChunk(int chunkX, int chunkZ) { + chunksToLoad.add(pack(chunkX, 0, chunkZ)); + } + + private void addChunksBetween(int xLine, int zMin, int zMax) { + for (int z = zMin + 1; z <= zMax - 1; z++) { + addChunk(xLine, z); + } + } +} diff --git a/src/main/java/serverutils/pregenerator/filemanager/PregeneratorFileManager.java b/src/main/java/serverutils/pregenerator/filemanager/PregeneratorFileManager.java new file mode 100644 index 00000000..3f9cc778 --- /dev/null +++ b/src/main/java/serverutils/pregenerator/filemanager/PregeneratorFileManager.java @@ -0,0 +1,101 @@ +package serverutils.pregenerator.filemanager; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import net.minecraft.server.MinecraftServer; + +import serverutils.lib.util.misc.PregeneratorCommandInfo; +import serverutils.pregenerator.filemanager.readwriters.FileReadWriter; +import serverutils.pregenerator.filemanager.readwriters.SafeFileReadWriter; + +public class PregeneratorFileManager { + + private final FileReadWriter commandReadWriter; + private final SafeFileReadWriter iterationReadWriter; + public static final String COMMAND_FOLDER = "pregenerationFiles"; + public static final String COMMAND_FILE = "fileCommand"; + public static final String COMMAND_ITERATION = "fileIteration"; + + public PregeneratorFileManager(MinecraftServer server, double xLoc, double zLoc, int radius, int dimensionID) + throws IOException { + Path temporaryFileSaveFolder = Paths.get("saves").resolve(getWorldFolderPath(server).resolve(COMMAND_FOLDER)); + if (!Files.exists(temporaryFileSaveFolder)) { + Files.createDirectories(temporaryFileSaveFolder); + } + this.iterationReadWriter = new SafeFileReadWriter(temporaryFileSaveFolder.resolve(COMMAND_ITERATION), 100); + this.commandReadWriter = new FileReadWriter(temporaryFileSaveFolder.resolve(COMMAND_FILE)); + + commandReadWriter.clearFile(); + commandReadWriter.writeDouble(xLoc); + commandReadWriter.writeDouble(zLoc); + commandReadWriter.writeInt(radius); + commandReadWriter.writeInt(dimensionID); + commandReadWriter.close(); + } + + // Constructor to load the information from file + public PregeneratorFileManager(MinecraftServer server) throws IOException { + Path temporaryFileSaveFolder = Paths.get("saves").resolve(getWorldFolderPath(server).resolve(COMMAND_FOLDER)); + if (!Files.exists(temporaryFileSaveFolder)) { + Files.createDirectories(temporaryFileSaveFolder); + } + this.iterationReadWriter = new SafeFileReadWriter(temporaryFileSaveFolder.resolve(COMMAND_ITERATION), 100); + this.commandReadWriter = new FileReadWriter(temporaryFileSaveFolder.resolve(COMMAND_FILE)); + } + + public Optional getCommandInfo() { + try { + commandReadWriter.openForReading(); + iterationReadWriter.openForReading(); + return Optional.of( + new PregeneratorCommandInfo( + commandReadWriter.readDouble(), + commandReadWriter.readDouble(), + commandReadWriter.readInt(), + commandReadWriter.readInt(), + iterationReadWriter.readInt())); + } catch (IOException ignored) {} // Ignoring this because often there's just nothing in the file if you're + // loading a world + return Optional.empty(); + } + + public void saveIteration(int iteration) { + try { + iterationReadWriter.writeAndCommitIntAfterIterations(iteration); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void closeAndRemoveAllFiles() { + try { + iterationReadWriter.close(); + iterationReadWriter.deleteFile(); + commandReadWriter.close(); + commandReadWriter.deleteFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void closeAllFiles() { + try { + iterationReadWriter.close(); + commandReadWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public boolean isReady() { + return true; + } + + private Path getWorldFolderPath(MinecraftServer server) { + return Paths.get(server.getFolderName()); + } +} diff --git a/src/main/java/serverutils/pregenerator/filemanager/readwriters/FileReadWriter.java b/src/main/java/serverutils/pregenerator/filemanager/readwriters/FileReadWriter.java new file mode 100644 index 00000000..505f4400 --- /dev/null +++ b/src/main/java/serverutils/pregenerator/filemanager/readwriters/FileReadWriter.java @@ -0,0 +1,61 @@ +package serverutils.pregenerator.filemanager.readwriters; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileReadWriter { + + protected RandomAccessFile randomAccessFile; + protected boolean randomAccessFileIsClosed = false; + protected final Path filePath; + + public FileReadWriter(Path path) throws IOException { + this.filePath = path; + randomAccessFile = new RandomAccessFile(this.filePath.toFile(), "rw"); + } + + public void close() throws IOException { + randomAccessFileIsClosed = true; + randomAccessFile.close(); + } + + public void clearFile() throws IOException { + randomAccessFile.setLength(0); + } + + public void writeDouble(double value) throws IOException { + randomAccessFile.writeDouble(value); + } + + public void writeInt(int value) throws IOException { + randomAccessFile.writeInt(value); + } + + public void openForWriting() throws IOException { + if (randomAccessFile == null || !filePath.toFile().exists() || randomAccessFileIsClosed) { + randomAccessFileIsClosed = false; + randomAccessFile = new RandomAccessFile(filePath.toFile(), "rw"); + } + } + + public void openForReading() throws IOException { + if (randomAccessFile == null || !filePath.toFile().exists() || randomAccessFileIsClosed) { + randomAccessFileIsClosed = false; + randomAccessFile = new RandomAccessFile(filePath.toFile(), "r"); + } + } + + public int readInt() throws IOException { + return randomAccessFile.readInt(); + } + + public double readDouble() throws IOException { + return randomAccessFile.readDouble(); + } + + public void deleteFile() throws IOException { + Files.deleteIfExists(filePath); + } +} diff --git a/src/main/java/serverutils/pregenerator/filemanager/readwriters/SafeFileReadWriter.java b/src/main/java/serverutils/pregenerator/filemanager/readwriters/SafeFileReadWriter.java new file mode 100644 index 00000000..3e1fdd18 --- /dev/null +++ b/src/main/java/serverutils/pregenerator/filemanager/readwriters/SafeFileReadWriter.java @@ -0,0 +1,78 @@ +package serverutils.pregenerator.filemanager.readwriters; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +// TODO : MAKE THIS BOUNCE BETWEEN 2 FILES +public class SafeFileReadWriter extends FileReadWriter { + + private final Path tempFile; + private RandomAccessFile randomAccessFileTemp; + private boolean randomAccessFileTempIsClosed = false; + private final int iterationsBetweenWrites; + private int writeIteration = 0; + + public SafeFileReadWriter(Path path, int iterationsBetweenWrites) throws IOException { + super(path); + this.iterationsBetweenWrites = iterationsBetweenWrites; + this.tempFile = Paths.get(path + ".tmp"); + randomAccessFileTemp = new RandomAccessFile(this.tempFile.toFile(), "rw"); + } + + public void writeAndCommitIntAfterIterations(int value) throws IOException { + if (writeIteration >= iterationsBetweenWrites) { + writeIteration = 0; + this.writeInt(value); + this.commit(); + } else { + writeIteration++; + } + } + + @Override + public void writeInt(int value) throws IOException { + if (this.randomAccessFileTempIsClosed) { + this.openForWriting(); + } + randomAccessFileTemp.seek(0); + randomAccessFileTemp.writeInt(value); + randomAccessFileTemp.getChannel().force(true); + } + + public void commit() throws IOException { + randomAccessFileTemp.close(); + randomAccessFileTempIsClosed = true; + randomAccessFile.close(); + randomAccessFileIsClosed = true; + Files.move(tempFile, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + this.openForWriting(); + } + + @Override + public void clearFile() throws IOException { + randomAccessFile.setLength(0); + randomAccessFileTemp.setLength(0); + } + + @Override + public void close() throws IOException { + randomAccessFileIsClosed = true; + randomAccessFile.close(); + + randomAccessFileTempIsClosed = true; + randomAccessFileTemp.close(); + Files.deleteIfExists(tempFile); + } + + @Override + public void openForWriting() throws IOException { + if (randomAccessFileTemp == null || !tempFile.toFile().exists() || randomAccessFileTempIsClosed) { + randomAccessFileTempIsClosed = false; + randomAccessFileTemp = new RandomAccessFile(tempFile.toFile(), "rw"); + } + } +} diff --git a/src/main/resources/assets/serverutilities/lang/en_US.lang b/src/main/resources/assets/serverutilities/lang/en_US.lang index bddb1326..9e7e8389 100644 --- a/src/main/resources/assets/serverutilities/lang/en_US.lang +++ b/src/main/resources/assets/serverutilities/lang/en_US.lang @@ -435,6 +435,10 @@ commands.god.usage=/god commands.rtp.usage=/rtp commands.dump_chunkloaders.usage=/dump_chunkloaders commands.dump_permissions.usage=/dump_permissions +commands.pregen.start.server_usage=/pregen start +commands.pregen.start.server_client=/pregen start or /pregen +commands.pregen.stop.usage=/pregen stop +commands.pregen.progress.usage=/pregen progress # Chunks serverutilities.lang.chunks.cant_modify_chunk=Can't modify this chunk!