From acf302e8c13bdf84a4975bc65d45557e084602b2 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 26 Jul 2020 16:52:04 +0200 Subject: [PATCH 01/16] Feature/async chunkmaster (#81) * Change generation to be asynchronous At this stage it will most likely crash the server at a certain point. Pausing and resuming isn't stable. Saving the progress isn't stable as well. Chunks are being unloaded in the main thread by an unloader class. * Switch to native threads - Use thread instead of async tasks - Store pending paper chunks in the database - Interrupt the thread when it should be stopped * Fix insertion of pending chunks Fix an error that is thrown when the sql for inserting pending chunks doesn't have any chunks to insert. * Add task states Add states to differentiate between generating and validating tasks as well as a field in the database to store this information. A task will first generate a world until the required radius or the worldborder is reached. Then it validates that each chunk has been generated. * Add object representation of world_properties table * Add DAO for pending_chunks table * Add DAO for generation_tasks table * Add state updating to periodic save * Fix loading of world properties * Add states to tasks and fix completion handling * Fix progress report and spiral shape * Modify the paper generation task so it works with spigot This change is being made because normal chunk generation doesn't allow chunks to be requested from a different thread. With PaperLib this issue can be solved. * Add workarounds for spigot problems * Fix some blocking issues and update README * Add locking to ChunkUnloader class --- README.md | 37 +- build.gradle | 2 +- .../net/trivernis/chunkmaster/Chunkmaster.kt | 14 +- .../chunkmaster/commands/CmdCancel.kt | 5 +- .../chunkmaster/commands/CmdGetCenter.kt | 24 +- .../trivernis/chunkmaster/commands/CmdList.kt | 4 +- .../chunkmaster/commands/CmdSetCenter.kt | 2 +- .../chunkmaster/commands/CmdStats.kt | 6 +- .../lib/database/GenerationTaskData.kt | 14 + .../lib/database/GenerationTasks.kt | 103 ++++++ .../chunkmaster/lib/database/PendingChunks.kt | 57 +++ .../lib/{ => database}/SqliteManager.kt | 25 +- .../lib/database/WorldProperties.kt | 82 +++++ .../lib/generation/ChunkCoordinates.kt | 4 + .../lib/generation/ChunkUnloader.kt | 60 ++++ .../lib/generation/DefaultGenerationTask.kt | 145 ++++++++ .../lib/generation/GenerationManager.kt | 326 +++++++++--------- .../lib/generation/GenerationTask.kt | 120 +++---- .../lib/generation/GenerationTaskPaper.kt | 121 ------- .../lib/generation/GenerationTaskSpigot.kt | 72 ---- .../lib/generation/PausedTaskEntry.kt | 10 - .../lib/generation/PendingChunkEntry.kt | 10 + .../chunkmaster/lib/generation/TaskEntry.kt | 13 - .../chunkmaster/lib/generation/TaskState.kt | 24 ++ .../generation/taskentry/PausedTaskEntry.kt | 9 + .../{ => taskentry}/RunningTaskEntry.kt | 44 ++- .../lib/generation/taskentry/TaskEntry.kt | 11 + .../chunkmaster/lib/shapes/Circle.kt | 31 +- .../trivernis/chunkmaster/lib/shapes/Shape.kt | 10 + .../chunkmaster/lib/shapes/Spiral.kt | 23 +- .../resources/i18n/DEFAULT.i18n.properties | 12 +- src/main/resources/i18n/de.i18n.properties | 10 +- src/main/resources/i18n/en.i18n.properties | 10 +- src/main/resources/plugin.yml | 2 +- 34 files changed, 901 insertions(+), 541 deletions(-) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTaskData.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt rename src/main/kotlin/net/trivernis/chunkmaster/lib/{ => database}/SqliteManager.kt (85%) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt delete mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt delete mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt delete mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PausedTaskEntry.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt delete mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskEntry.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskState.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt rename src/main/kotlin/net/trivernis/chunkmaster/lib/generation/{ => taskentry}/RunningTaskEntry.kt (53%) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/TaskEntry.kt diff --git a/README.md b/README.md index cc2fd67..85b37fb 100644 --- a/README.md +++ b/README.md @@ -82,39 +82,30 @@ generation: # The maximum amount of chunks that are loaded before unloading and saving them. # Higher values mean higher generation speed but greater memory usage. # The value should be a positive integer. - max-loaded-chunks: 10 + max-loaded-chunks: 1000 # Paper Only # The maximum amount of requested chunks with the asynchronous paper chunk - # loading method. Higher values mean faster generation but more memory usage - # (and probably bigger performance impact). + # loading method. Higher values mean faster generation but more memory usage and + # bigger performance impact. Configuring it too hight might crash the server. # The value should be a positive integer. - max-pending-chunks: 10 - - # The period (in ticks) in which a generation step is run. - # Higher values mean less performance impact but slower generation. - # The value should be a positive integer. - period: 2 - - # The max amount of chunks that should be generated per step. - # Higher values mean higher generation speed but higher performance impact. - # The value should be a positive integer. - chunks-per-step: 4 - - # Paper Only - # The number of already generated chunks that will be skipped for each step. - # Notice that these still have a performance impact because the server needs to check - # if the chunk is generated. - # Higher values mean faster generation but greater performance impact. - # The value should be a positive integer. - chunk-skips-per-step: 100 + max-pending-chunks: 500 # The maximum milliseconds per tick the server is allowed to have # during the cunk generation process. # If the mspt is greather than this, the chunk generation task pauses. - # The value should be a positive integer greater than 50. + # The value should be a positive integer greater than 50. mspt-pause-threshold: 500 + # The period in ticks for how often loaded chunks get unloaded. + # Unloading happens in the main thread and can impact the server performance. + # You can tweak this setting with the max-loaded-chunks setting to have either + # a lot of chunks unloaded at once or fewer chunks unloaded more often. + # If the maximum number of loaded chunks is reached the generation pauses until the + # unloading task runs again so keep that in mind. + # The value should be a positive integer. + unloading-period: 50 + # Pauses the generation if the number of players on the server is larger or equal # to the configured value # Notice that playing on a server that constantly generates chunks can be diff --git a/build.gradle b/build.gradle index ce371b7..eb3994f 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ idea { } group "net.trivernis" -version "1.2.3" +version "1.3.0" sourceCompatibility = 1.8 diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index ab5a687..d87b605 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -3,13 +3,12 @@ package net.trivernis.chunkmaster import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.commands.CommandChunkmaster import net.trivernis.chunkmaster.lib.LanguageManager -import net.trivernis.chunkmaster.lib.SqliteManager +import net.trivernis.chunkmaster.lib.database.SqliteManager import net.trivernis.chunkmaster.lib.generation.GenerationManager import org.bstats.bukkit.Metrics import org.bukkit.plugin.java.JavaPlugin import org.bukkit.scheduler.BukkitTask import org.dynmap.DynmapAPI -import java.util.logging.Level class Chunkmaster: JavaPlugin() { lateinit var sqliteManager: SqliteManager @@ -67,6 +66,7 @@ class Chunkmaster: JavaPlugin() { override fun onDisable() { logger.info(langManager.getLocalized("STOPPING_ALL_TASKS")) generationManager.stopAll() + server.scheduler.cancelTasks(this) } /** @@ -74,13 +74,11 @@ class Chunkmaster: JavaPlugin() { */ private fun configure() { dataFolder.mkdir() - config.addDefault("generation.period", 2L) - config.addDefault("generation.chunks-per-step", 2) - config.addDefault("generation.chunk-skips-per-step", 100) config.addDefault("generation.mspt-pause-threshold", 500L) config.addDefault("generation.pause-on-player-count", 1) - config.addDefault("generation.max-pending-chunks", 10) - config.addDefault("generation.max-loaded-chunks", 10) + config.addDefault("generation.max-pending-chunks", 500) + config.addDefault("generation.max-loaded-chunks", 1000) + config.addDefault("generation.unloading-period", 50L) config.addDefault("generation.ignore-worldborder", false) config.addDefault("database.filename", "chunkmaster.db") config.addDefault("language", "en") @@ -95,7 +93,7 @@ class Chunkmaster: JavaPlugin() { private fun initDatabase() { logger.info(langManager.getLocalized("DB_INIT")) try { - this.sqliteManager = SqliteManager( this) + this.sqliteManager = SqliteManager(this) sqliteManager.init() logger.info(langManager.getLocalized("DB_INIT_FINISHED")) } catch(e: Exception) { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt index 65e6e47..ec2bb78 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt @@ -1,10 +1,7 @@ package net.trivernis.chunkmaster.commands -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.ComponentBuilder import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand -import net.trivernis.chunkmaster.lib.generation.TaskEntry import org.bukkit.command.Command import org.bukkit.command.CommandSender @@ -39,7 +36,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster): Subcommand { } if (index != null && chunkmaster.generationManager.removeTask(index)) { - sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_CANCELED", index)) + sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_CANCELLED", index)) true } else { sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_NOT_FOUND", args[0])) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt index b763cd3..316d8e8 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt @@ -37,12 +37,6 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { args[0] } } - if (chunkmaster.generationManager.worldCenters.isEmpty()) { - chunkmaster.generationManager.loadWorldCenters() { - sendCenterInfo(sender, worldName) - } - return true - } sendCenterInfo(sender, worldName) return true } @@ -51,15 +45,17 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { * Sends the center information */ private fun sendCenterInfo(sender: CommandSender, worldName: String) { - var center = chunkmaster.generationManager.worldCenters[worldName] - if (center == null) { - val world = sender.server.worlds.find { it.name == worldName } - if (world == null) { - sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)) - return + chunkmaster.generationManager.worldProperties.getWorldCenter(worldName).thenAccept { worldCenter -> + var center = worldCenter + if (center == null) { + val world = sender.server.worlds.find { it.name == worldName } + if (world == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)) + return@thenAccept + } + center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) } - center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_INFO", worldName, center.first, center.second)) } - sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_INFO", worldName, center.first, center.second)) } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt index 98472cb..a4752dc 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt @@ -2,7 +2,7 @@ package net.trivernis.chunkmaster.commands import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand -import net.trivernis.chunkmaster.lib.generation.TaskEntry +import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry import org.bukkit.command.Command import org.bukkit.command.CommandSender @@ -53,6 +53,6 @@ class CmdList(private val chunkmaster: Chunkmaster): Subcommand { else "" return "\n" + chunkmaster.langManager.getLocalized("TASKS_ENTRY", - task.id, genTask.world.name, genTask.count, percentage) + task.id, genTask.world.name, genTask.state.toString(), genTask.count, percentage) } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt index 8714303..9adee80 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt @@ -67,7 +67,7 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster): Subcommand { centerZ = args[2].toInt() } } - chunkmaster.generationManager.updateWorldCenter(world, Pair(centerX, centerZ)) + chunkmaster.generationManager.worldProperties.setWorldCenter(world, Pair(centerX, centerZ)) sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_UPDATED", world, centerX, centerZ)) return true } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt index 4390bf9..bab4b20 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt @@ -41,10 +41,6 @@ class CmdStats(private val chunkmaster: Chunkmaster): Subcommand { ${chunkmaster.langManager.getLocalized("STATS_ENTITY_COUNT", world.entities.size)} ${chunkmaster.langManager.getLocalized("STATS_LOADED_CHUNKS", world.loadedChunks.size)} """.trimIndent() - val task = chunkmaster.generationManager.tasks.find { it.generationTask.world == world } - if (task != null) { - message += "\n" + chunkmaster.langManager.getLocalized("STATS_PLUGIN_LOADED_CHUNKS", task.generationTask.loadedChunksCount) - } return message } @@ -59,6 +55,8 @@ class CmdStats(private val chunkmaster: Chunkmaster): Subcommand { ${chunkmaster.langManager.getLocalized("STATS_PLUGIN_VERSION", chunkmaster.description.version)} ${chunkmaster.langManager.getLocalized("STATS_MEMORY", memUsed/1000000, runtime.maxMemory()/1000000, (memUsed.toFloat()/runtime.maxMemory().toFloat()) * 100)} ${chunkmaster.langManager.getLocalized("STATS_CORES", runtime.availableProcessors())} + + ${chunkmaster.langManager.getLocalized("STATS_PLUGIN_LOADED_CHUNKS", chunkmaster.generationManager.loadedChunkCount)} """.trimIndent() for (world in sender.server.worlds) { message += "\n\n" + getWorldStatsMessage(sender, world) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTaskData.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTaskData.kt new file mode 100644 index 0000000..2ea835c --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTaskData.kt @@ -0,0 +1,14 @@ +package net.trivernis.chunkmaster.lib.database + +import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates +import net.trivernis.chunkmaster.lib.generation.TaskState + +data class GenerationTaskData( + val id: Int, + val world: String, + val radius: Int, + val shape: String, + val state: TaskState, + val center: ChunkCoordinates, + val last: ChunkCoordinates +) \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt new file mode 100644 index 0000000..c8f9948 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt @@ -0,0 +1,103 @@ +package net.trivernis.chunkmaster.lib.database + +import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates +import net.trivernis.chunkmaster.lib.generation.TaskState +import java.util.concurrent.CompletableFuture + +class GenerationTasks(private val sqliteManager: SqliteManager) { + /** + * Returns all stored generation tasks + */ + fun getGenerationTasks(): CompletableFuture> { + val completableFuture = CompletableFuture>() + + sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res -> + val tasks = ArrayList() + + while (res!!.next()) { + val id = res.getInt("id") + val world = res.getString("world") + val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z")) + val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z")) + val radius = res.getInt("radius") + val shape = res.getString("shape") + val state = stringToState(res.getString("state")) + val taskData = GenerationTaskData(id, world, radius, shape, state, center, last) + if (tasks.find { it.id == id } == null) { + tasks.add(taskData) + } + } + completableFuture.complete(tasks) + } + return completableFuture + } + + /** + * Adds a generation task to the database + */ + fun addGenerationTask(world: String, center: ChunkCoordinates, radius: Int, shape: String): CompletableFuture { + val completableFuture = CompletableFuture() + sqliteManager.executeStatement(""" + INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, radius, shape) + values (?, ?, ?, ?, ?, ?, ?)""", + hashMapOf( + 1 to center.x, + 2 to center.z, + 3 to center.x, + 4 to center.z, + 5 to world, + 6 to radius, + 7 to shape + ) + ) { + sqliteManager.executeStatement( + """ + SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1 + """.trimIndent(), HashMap() + ) { + it!!.next() + completableFuture.complete(it.getInt("id")) + } + } + return completableFuture + } + + /** + * Deletes a generationTask from the database + */ + fun deleteGenerationTask(id: Int): CompletableFuture { + val completableFuture = CompletableFuture() + sqliteManager.executeStatement("DELETE FROM generation_tasks WHERE id = ?;", hashMapOf(1 to id)) { + completableFuture.complete(null) + } + return completableFuture + } + + fun updateGenerationTask(id: Int, last: ChunkCoordinates, state: TaskState): CompletableFuture { + val completableFuture = CompletableFuture() + sqliteManager.executeStatement( + """ + UPDATE generation_tasks SET last_x = ?, last_z = ?, state = ? + WHERE id = ? + """.trimIndent(), + hashMapOf(1 to last.x, 2 to last.z, 3 to state.toString(), 4 to id) + ) { + completableFuture.complete(null) + } + return completableFuture + } + + /** + * Converts a string into a task state + */ + private fun stringToState(stringState: String): TaskState { + TaskState.valueOf(stringState) + return when (stringState) { + "GENERATING" -> TaskState.GENERATING + "VALIDATING" -> TaskState.VALIDATING + "PAUSING" -> TaskState.PAUSING + "CORRECTING" -> TaskState.CORRECTING + else -> TaskState.GENERATING + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt new file mode 100644 index 0000000..9b79dfd --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt @@ -0,0 +1,57 @@ +package net.trivernis.chunkmaster.lib.database + +import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates +import java.util.concurrent.CompletableFuture + +class PendingChunks(private val sqliteManager: SqliteManager) { + /** + * Returns a list of pending chunks for a taskId + */ + fun getPendingChunks(taskId: Int): CompletableFuture> { + val completableFuture = CompletableFuture>() + sqliteManager.executeStatement("SELECT * FROM pending_chunks WHERE task_id = ?", hashMapOf(1 to taskId)) { + val pendingChunks = ArrayList() + while (it!!.next()) { + pendingChunks.add(ChunkCoordinates(it.getInt("chunk_x"), it.getInt("chunk_z"))) + } + completableFuture.complete(pendingChunks) + } + return completableFuture + } + + /** + * Clears all pending chunks of a task + */ + fun clearPendingChunks(taskId: Int): CompletableFuture { + val completableFuture = CompletableFuture() + sqliteManager.executeStatement("DELETE FROM pending_chunks WHERE task_id = ?", hashMapOf(1 to taskId)) { + completableFuture.complete(null) + } + return completableFuture + } + + /** + * Adds pending chunks for a taskid + */ + fun addPendingChunks(taskId: Int, pendingChunks: List): CompletableFuture { + val completableFuture = CompletableFuture() + if (pendingChunks.isEmpty()) { + completableFuture.complete(null) + } else { + var sql = "INSERT INTO pending_chunks (task_id, chunk_x, chunk_z) VALUES" + var index = 1 + val valueMap = HashMap() + + for (coordinates in pendingChunks) { + sql += "(?, ?, ?)," + valueMap[index++] = taskId + valueMap[index++] = coordinates.x + valueMap[index++] = coordinates.z + } + sqliteManager.executeStatement(sql.removeSuffix(","), valueMap) { + completableFuture.complete(null) + } + } + return completableFuture + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt similarity index 85% rename from src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt rename to src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt index 47419f5..c65940a 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt @@ -1,12 +1,10 @@ -package net.trivernis.chunkmaster.lib +package net.trivernis.chunkmaster.lib.database import net.trivernis.chunkmaster.Chunkmaster import org.apache.commons.lang.exception.ExceptionUtils -import org.sqlite.SQLiteConnection import java.lang.Exception import java.sql.Connection import java.sql.DriverManager -import java.sql.PreparedStatement import java.sql.ResultSet class SqliteManager(private val chunkmaster: Chunkmaster) { @@ -21,7 +19,8 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { Pair("last_z", "integer NOT NULL DEFAULT 0"), Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), Pair("radius", "integer DEFAULT -1"), - Pair("shape", "text NOT NULL DEFAULT 'square'") + Pair("shape", "text NOT NULL DEFAULT 'square'"), + Pair("state", "text NOT NULL DEFAULT 'GENERATING'") ) ), Pair( @@ -31,6 +30,15 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { Pair("center_x", "integer NOT NULL DEFAULT 0"), Pair("center_z", "integer NOT NULL DEFAULT 0") ) + ), + Pair( + "pending_chunks", + listOf( + Pair("id", "integer PRIMARY KEY AUTOINCREMENT"), + Pair("task_id", "integer NOT NULL"), + Pair("chunk_x", "integer NOT NULL"), + Pair("chunk_z", "integer NOT NULL") + ) ) ) private val needUpdate = HashSet>>() @@ -38,6 +46,10 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { private var connection: Connection? = null private var activeTasks = 0 + val worldProperties = WorldProperties(this) + val pendingChunks = PendingChunks(this) + val generationTasks = GenerationTasks(this) + /** * Returns the connection to the database */ @@ -92,17 +104,18 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { /** * Executes a sql statement on the database. */ - fun executeStatement(sql: String, values: HashMap, callback: ((ResultSet) -> Unit)?) { + fun executeStatement(sql: String, values: HashMap, callback: ((ResultSet?) -> Unit)?) { val connection = getConnection() activeTasks++ if (connection != null) { try { + //println("'$sql' with values $values") val statement = connection.prepareStatement(sql) for (parameterValue in values) { statement.setObject(parameterValue.key, parameterValue.value) } statement.execute() - val res = statement.resultSet + val res: ResultSet? = statement.resultSet if (callback != null) { callback(res) } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt new file mode 100644 index 0000000..fe6dab4 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt @@ -0,0 +1,82 @@ +package net.trivernis.chunkmaster.lib.database + +import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates +import java.util.concurrent.CompletableFuture + +class WorldProperties(private val sqliteManager: SqliteManager) { + + private val properties = HashMap>() + + /** + * Returns the world center for one world + */ + fun getWorldCenter(worldName: String): CompletableFuture?> { + val completableFuture = CompletableFuture?>() + + if (properties[worldName] != null) { + completableFuture.complete(properties[worldName]) + } else { + sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", hashMapOf(1 to worldName)) { + if (it != null && it.next()) { + completableFuture.complete(Pair(it.getInt("center_x"), it.getInt("center_z"))) + } else { + completableFuture.complete(null) + } + } + } + + return completableFuture + } + + /** + * Updates the center of a world + */ + fun setWorldCenter(worldName: String, center: Pair): CompletableFuture { + val completableFuture = CompletableFuture() + + getWorldCenter(worldName).thenAccept { + if (it != null) { + updateWorldProperties(worldName, center).thenAccept {completableFuture.complete(null) } + } else { + insertWorldProperties(worldName, center).thenAccept { completableFuture.complete(null) } + } + } + return completableFuture + } + + /** + * Updates an entry in the world properties + */ + private fun updateWorldProperties(worldName: String, center: Pair): CompletableFuture { + val completableFuture = CompletableFuture() + sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", + hashMapOf( + 1 to center.first, + 2 to center.second, + 3 to worldName + ) + ) { + properties[worldName] = center + completableFuture.complete(null) + } + return completableFuture + } + + /** + * Inserts into the world properties + */ + private fun insertWorldProperties(worldName: String, center: Pair): CompletableFuture { + val completableFuture = CompletableFuture() + sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", + hashMapOf( + 1 to worldName, + 2 to center.first, + 3 to center.second + ) + ) { + properties[worldName] = center + completableFuture.complete(null) + } + return completableFuture + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt index cbb4441..b5c7cbc 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt @@ -7,4 +7,8 @@ class ChunkCoordinates(val x: Int, val z: Int) { fun getCenterLocation(world: World): Location { return Location(world, ((x*16) + 8).toDouble(), 1.0, ((z*16) + 8).toDouble()) } + + override fun toString(): String { + return "($x, $z)" + } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt new file mode 100644 index 0000000..50c1ada --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt @@ -0,0 +1,60 @@ +package net.trivernis.chunkmaster.lib.generation + +import net.trivernis.chunkmaster.Chunkmaster +import org.bukkit.Chunk +import java.lang.Exception +import java.util.* +import java.util.concurrent.* +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.collections.HashSet + +class ChunkUnloader(private val plugin: Chunkmaster): Runnable { + private val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") + private val lock = ReentrantReadWriteLock() + private var unloadingQueue = Vector(maxLoadedChunks) + val isFull: Boolean + get() { + return pendingSize == maxLoadedChunks + } + + val pendingSize: Int + get() { + lock.readLock().lock() + val size = unloadingQueue.size + lock.readLock().unlock() + return size + } + + /** + * Unloads all chunks in the unloading queue with each run + */ + override fun run() { + lock.writeLock().lock() + try { + val chunkToUnload = unloadingQueue.toHashSet() + + for (chunk in chunkToUnload) { + try { + chunk.unload(true) + } catch (e: Exception) { + plugin.logger.severe(e.toString()) + } + } + unloadingQueue.clear() + } finally { + lock.writeLock().unlock() + } + } + + /** + * Adds a chunk to unload to the queue + */ + fun add(chunk: Chunk) { + lock.writeLock().lockInterruptibly() + try { + unloadingQueue.add(chunk) + } finally { + lock.writeLock().unlock() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt new file mode 100644 index 0000000..2c96430 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt @@ -0,0 +1,145 @@ +package net.trivernis.chunkmaster.lib.generation + +import io.papermc.lib.PaperLib +import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.shapes.Shape +import org.bukkit.World +import java.util.concurrent.* + +class DefaultGenerationTask( + private val plugin: Chunkmaster, + unloader: ChunkUnloader, + world: World, + startChunk: ChunkCoordinates, + override val radius: Int = -1, + shape: Shape, + missingChunks: HashSet, + state: TaskState +) : GenerationTask(plugin, world, unloader, startChunk, shape, missingChunks, state) { + + private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks") + val pendingChunks = ArrayBlockingQueue(maxPendingChunks) + + override var count = 0 + override var endReached: Boolean = false + + init { + updateGenerationAreaMarker() + count = shape.count + } + + /** + * Runs the generation task. Every Iteration the next chunks will be generated if + * they haven't been generated already + * After a configured number of chunks chunks have been generated, they will all be unloaded and saved. + */ + override fun generate() { + generateMissing() + seekGenerated() + generateUntilBorder() + } + + /** + * Validates that all chunks have been generated or generates missing ones + */ + override fun validate() { + this.shape.reset() + val missedChunks = HashSet() + + while (!cancelRun && !borderReached()) { + val chunkCoordinates = nextChunkCoordinates + triggerDynmapRender(chunkCoordinates) + if (!PaperLib.isChunkGenerated(world, chunkCoordinates.x, chunkCoordinates.z)) { + missedChunks.add(chunkCoordinates) + } + } + this.missingChunks.addAll(missedChunks) + } + + /** + * Generates chunks that are missing + */ + override fun generateMissing() { + val missing = this.missingChunks.toHashSet() + this.count = 0 + + while (missing.size > 0 && !cancelRun) { + if (plugin.mspt < msptThreshold && !unloader.isFull) { + val chunk = missing.first() + missing.remove(chunk) + this.requestGeneration(chunk) + this.count++ + } else { + Thread.sleep(50L) + } + } + if (!cancelRun) { + this.joinPending() + } + } + + /** + * Seeks until it encounters a chunk that hasn't been generated yet + */ + private fun seekGenerated() { + do { + lastChunkCoords = nextChunkCoordinates + count = shape.count + } while (PaperLib.isChunkGenerated(world, lastChunkCoords.x, lastChunkCoords.z)) + } + + /** + * Generates the world until it encounters the worlds border + */ + private fun generateUntilBorder() { + var chunkCoordinates: ChunkCoordinates + + while (!cancelRun && !borderReached()) { + if (plugin.mspt < msptThreshold && !unloader.isFull) { + chunkCoordinates = nextChunkCoordinates + requestGeneration(chunkCoordinates) + + lastChunkCoords = chunkCoordinates + count = shape.count + } else { + Thread.sleep(50L) + } + } + if (!cancelRun) { + joinPending() + } + } + + private fun joinPending() { + while (!this.pendingChunks.isEmpty()) { + Thread.sleep(msptThreshold) + } + } + + /** + * Request the generation of a chunk + */ + private fun requestGeneration(chunkCoordinates: ChunkCoordinates) { + if (!PaperLib.isChunkGenerated(world, chunkCoordinates.x, chunkCoordinates.z) || PaperLib.isSpigot()) { + val pendingChunkEntry = PendingChunkEntry( + chunkCoordinates, + PaperLib.getChunkAtAsync(world, chunkCoordinates.x, chunkCoordinates.z, true) + ) + this.pendingChunks.put(pendingChunkEntry) + pendingChunkEntry.chunk.thenAccept { + this.unloader.add(it) + this.pendingChunks.remove(pendingChunkEntry) + } + } + } + + /** + * Cancels the generation task. + * This unloads all chunks that were generated but not unloaded yet. + */ + override fun cancel() { + this.cancelRun = true + this.pendingChunks.forEach { it.chunk.cancel(false) } + updateGenerationAreaMarker(true) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index 98e5e61..ed18128 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -1,23 +1,34 @@ package net.trivernis.chunkmaster.lib.generation -import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.generation.taskentry.PausedTaskEntry +import net.trivernis.chunkmaster.lib.generation.taskentry.RunningTaskEntry +import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry import net.trivernis.chunkmaster.lib.shapes.Circle import net.trivernis.chunkmaster.lib.shapes.Spiral import org.bukkit.Server import org.bukkit.World +import java.util.concurrent.CompletableFuture class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) { val tasks: HashSet = HashSet() val pausedTasks: HashSet = HashSet() - val worldCenters: HashMap> = HashMap() + val worldProperties = chunkmaster.sqliteManager.worldProperties + private val pendingChunksTable = chunkmaster.sqliteManager.pendingChunks + private val generationTasks = chunkmaster.sqliteManager.generationTasks + private val unloadingPeriod = chunkmaster.config.getLong("generation.unloading-period") + + val loadedChunkCount: Int + get() { + return unloader.pendingSize + } + private val unloader = ChunkUnloader(chunkmaster) + + val allTasks: HashSet get() { if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) { - if (this.worldCenters.isEmpty()) { - this.loadWorldCenters() - } this.startAll() if (server.onlinePlayers.size >= chunkmaster.config.getInt("generation.pause-on-player-count")) { this.pauseAll() @@ -36,41 +47,17 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server */ fun addTask(world: World, radius: Int = -1, shape: String = "square"): Int { val foundTask = allTasks.find { it.generationTask.world == world } + if (foundTask == null) { - val centerChunk = if (worldCenters[world.name] == null) { + val center = worldProperties.getWorldCenter(world.name).join() + + val centerChunk = if (center == null) { ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) } else { - val center = worldCenters[world.name]!! ChunkCoordinates(center.first, center.second) } - val generationTask = createGenerationTask(world, centerChunk, centerChunk, radius, shape) - - chunkmaster.sqliteManager.executeStatement( - """ - INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, radius, shape) - values (?, ?, ?, ?, ?, ?, ?) - """, - HashMap( - mapOf( - 1 to centerChunk.x, - 2 to centerChunk.z, - 3 to centerChunk.x, - 4 to centerChunk.z, - 5 to world.name, - 6 to radius, - 7 to shape - ) - ), - null - ) - - var id = 0 - chunkmaster.sqliteManager.executeStatement(""" - SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1 - """.trimIndent(), HashMap()) { - it.next() - id = it.getInt("id") - } + val generationTask = createGenerationTask(world, centerChunk, centerChunk, radius, shape, null) + val id = generationTasks.addGenerationTask(world.name, centerChunk, radius, shape).join() generationTask.onEndReached { chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_FINISHED", id, it.count)) @@ -78,13 +65,19 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server } if (!paused) { - val task = server.scheduler.runTaskTimer( - chunkmaster, generationTask, 200, // 10 sec delay - chunkmaster.config.getLong("generation.period") + val taskEntry = RunningTaskEntry( + id, + generationTask ) - tasks.add(RunningTaskEntry(id, task, generationTask)) + taskEntry.start() + tasks.add(taskEntry) } else { - pausedTasks.add(PausedTaskEntry(id, generationTask)) + pausedTasks.add( + PausedTaskEntry( + id, + generationTask + ) + ) } return id @@ -102,17 +95,18 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server last: ChunkCoordinates, id: Int, radius: Int = -1, - delay: Long = 200L, - shape: String = "square" + shape: String = "square", + pendingChunks: List? ) { if (!paused) { chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_FOR_WORLD", world.name)) - val generationTask = createGenerationTask(world, center, last, radius, shape) - val task = server.scheduler.runTaskTimer( - chunkmaster, generationTask, delay, - chunkmaster.config.getLong("generation.period") + val generationTask = createGenerationTask(world, center, last, radius, shape, pendingChunks) + val taskEntry = RunningTaskEntry( + id, + generationTask ) - tasks.add(RunningTaskEntry(id, task, generationTask)) + taskEntry.start() + tasks.add(taskEntry) generationTask.onEndReached { chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_FINISHED", id, generationTask.count)) removeTask(id) @@ -129,22 +123,23 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server } else { this.tasks.find { it.id == id } } - if (taskEntry != null) { - taskEntry.cancel() - chunkmaster.sqliteManager.executeStatement(""" - DELETE FROM generation_tasks WHERE id = ?; - """.trimIndent(), HashMap(mapOf(1 to taskEntry.id)), - null - ) + try { + if (taskEntry != null) { + if (taskEntry.generationTask.isRunning && taskEntry is RunningTaskEntry) { + taskEntry.cancel(chunkmaster.config.getLong("mspt-pause-threshold")) + } + generationTasks.deleteGenerationTask(id) + pendingChunksTable.clearPendingChunks(id) - if (taskEntry is RunningTaskEntry) { - if (taskEntry.task.isCancelled) { + if (taskEntry is RunningTaskEntry) { tasks.remove(taskEntry) + } else if (taskEntry is PausedTaskEntry) { + pausedTasks.remove(taskEntry) } - } else if (taskEntry is PausedTaskEntry) { - pausedTasks.remove(taskEntry) + return true } - return true + } catch (e: Exception) { + chunkmaster.logger.severe(e.toString()) } return false } @@ -159,12 +154,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server saveProgress() // save progress every 30 seconds }, 600, 600) server.scheduler.runTaskLater(chunkmaster, Runnable { - this.loadWorldCenters() this.startAll() if (!server.onlinePlayers.isEmpty()) { this.pauseAll() } }, 20) + server.scheduler.runTaskTimer(chunkmaster, unloader, unloadingPeriod, unloadingPeriod) } /** @@ -173,40 +168,36 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server fun stopAll() { val removalSet = HashSet() for (task in tasks) { - val lastChunk = task.generationTask.lastChunkCoords val id = task.id chunkmaster.logger.info(chunkmaster.langManager.getLocalized("SAVING_TASK_PROGRESS", task.id)) - saveProgressToDatabase(lastChunk, id) - task.task.cancel() - task.generationTask.cancel() - if (task.task.isCancelled) { - removalSet.add(task) + saveProgressToDatabase(task.generationTask, id).join() + if (!task.cancel(chunkmaster.config.getLong("mspt-pause-threshold"))) { + chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("CANCEL_FAIL", task.id)) } - chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_CANCELED", task.id)) + removalSet.add(task) + + chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_CANCELLED", task.id)) } tasks.removeAll(removalSet) + if (unloader.pendingSize > 0) { + chunkmaster.logger.info(chunkmaster.langManager.getLocalized("SAVING_CHUNKS", unloader.pendingSize)) + unloader.run() + } } /** * Starts all generation tasks. */ fun startAll() { - chunkmaster.sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res -> - var count = 0 - while (res.next()) { - count++ - try { - val id = res.getInt("id") - val world = server.getWorld(res.getString("world")) - val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z")) - val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z")) - val radius = res.getInt("radius") - val shape = res.getString("shape") - if (this.tasks.find { it.id == id } == null) { - resumeTask(world!!, center, last, id, radius, 200L + count, shape) + generationTasks.getGenerationTasks().thenAccept { tasks -> + for (task in tasks) { + val world = server.getWorld(task.world) + if (world != null) { + pendingChunksTable.getPendingChunks(task.id).thenAccept { + resumeTask(world, task.center, task.last, task.id, task.radius, task.shape, it) } - } catch (error: NullPointerException) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", res.getInt("id"))) + } else { + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", task.id)) } } } @@ -222,7 +213,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server fun pauseAll() { paused = true for (task in tasks) { - pausedTasks.add(PausedTaskEntry(task.id, task.generationTask)) + pausedTasks.add( + PausedTaskEntry( + task.id, + task.generationTask + ) + ) } stopAll() } @@ -237,98 +233,97 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server } /** - * Overload that doesn't need an argument - */ - private fun loadWorldCenters() { - loadWorldCenters(null) - } - - /** - * Loads the world centers from the database + * Saves the task progress */ - fun loadWorldCenters(cb: (() -> Unit)?) { - chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties", HashMap()) { - while (it.next()) { - worldCenters[it.getString("name")] = Pair(it.getInt("center_x"), it.getInt("center_z")) + private fun saveProgress() { + for (task in tasks) { + try { + if (task.generationTask.state == TaskState.CORRECTING) { + reportCorrectionProgress(task) + } else { + reportGenerationProgress(task) + } + saveProgressToDatabase(task.generationTask, task.id) + } catch (error: Exception) { + chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("TASK_SAVE_FAILED", error.toString())) + error.printStackTrace() } - cb?.invoke() } } /** - * Updates the center of a world + * Reports the progress for correcting tasks */ - fun updateWorldCenter(worldName: String, center: Pair) { - chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", HashMap(mapOf(1 to worldName))) { - if (it.next()) { - chunkmaster.sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", HashMap( - mapOf( - 1 to center.first, - 2 to center.second, - 3 to worldName - ) - ), null) - } else { - chunkmaster.sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", HashMap( - mapOf( - 1 to worldName, - 2 to center.first, - 3 to center.second - ) - ), null) - } + private fun reportCorrectionProgress(task: RunningTaskEntry) { + val genTask = task.generationTask + val progress = if (genTask.missingChunks.size > 0) { + "(${(genTask.count / genTask.missingChunks.size) * 100}%)" + } else { + "" } - worldCenters[worldName] = center + chunkmaster.logger.info( + chunkmaster.langManager.getLocalized( + "TASK_PERIODIC_REPORT_CORRECTING", + task.id, + genTask.world.name, + genTask.count, + progress + ) + ) } /** - * Saves the task progress + * Reports the progress of the chunk generation */ - private fun saveProgress() { - for (task in tasks) { - try { - val genTask = task.generationTask - val (speed, chunkSpeed) = task.generationSpeed - val percentage = if (genTask.radius > 0) "(${"%.2f".format(genTask.shape.progress() * 100)}%)" else "" - val eta = if (genTask.radius > 0 && speed!! > 0) { - val etaSeconds = (genTask.shape.progress())/speed - val hours: Int = (etaSeconds/3600).toInt() - val minutes: Int = ((etaSeconds % 3600) / 60).toInt() - val seconds: Int = (etaSeconds % 60).toInt() - ", ETA: %d:%02d:%02d".format(hours, minutes, seconds) - } else { - "" - } - chunkmaster.logger.info(chunkmaster.langManager.getLocalized( - "TASK_PERIODIC_REPORT", - task.id, - genTask.world.name, - genTask.count, - percentage, - eta, - chunkSpeed!!, - genTask.lastChunkCoords.x, - genTask.lastChunkCoords.z)) - saveProgressToDatabase(genTask.lastChunkCoords, task.id) - genTask.updateLastChunkMarker() - } catch (error: Exception) { - chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("TASK_SAVE_FAILED", error.toString())) - } + private fun reportGenerationProgress(task: RunningTaskEntry) { + val genTask = task.generationTask + val (speed, chunkSpeed) = task.generationSpeed + val percentage = if (genTask.radius > 0) "(${"%.2f".format(genTask.shape.progress() * 100)}%)" else "" + + val eta = if (genTask.radius > 0 && speed!! > 0) { + val remaining = 1 - genTask.shape.progress() + val etaSeconds = remaining / speed + val hours: Int = (etaSeconds / 3600).toInt() + val minutes: Int = ((etaSeconds % 3600) / 60).toInt() + val seconds: Int = (etaSeconds % 60).toInt() + ", ETA: %dh %dmin %ds".format(hours, minutes, seconds) + } else { + "" } + chunkmaster.logger.info( + chunkmaster.langManager.getLocalized( + "TASK_PERIODIC_REPORT", + task.id, + genTask.world.name, + genTask.state.toString(), + genTask.count, + percentage, + eta, + chunkSpeed!!, + genTask.lastChunkCoords.x, + genTask.lastChunkCoords.z + ) + ) } /** * Saves the generation progress to the database */ - private fun saveProgressToDatabase(lastChunk: ChunkCoordinates, id: Int) { - chunkmaster.sqliteManager.executeStatement( - """ - UPDATE generation_tasks SET last_x = ?, last_z = ? - WHERE id = ? - """.trimIndent(), - HashMap(mapOf(1 to lastChunk.x, 2 to lastChunk.z, 3 to id)), - null - ) + private fun saveProgressToDatabase(generationTask: GenerationTask, id: Int): CompletableFuture { + val completableFuture = CompletableFuture() + generationTasks.updateGenerationTask(id, generationTask.lastChunkCoords, generationTask.state).thenAccept { + pendingChunksTable.clearPendingChunks(id).thenAccept { + if (generationTask is DefaultGenerationTask) { + if (generationTask.pendingChunks.size > 0) { + pendingChunksTable.addPendingChunks(id, generationTask.pendingChunks.map { it.coordinates }) + } + } + pendingChunksTable.addPendingChunks(id, generationTask.missingChunks.toList()).thenAccept { + completableFuture.complete(null) + } + } + } + return completableFuture } /** @@ -340,7 +335,8 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server center: ChunkCoordinates, start: ChunkCoordinates, radius: Int, - shapeName: String + shapeName: String, + pendingChunks: List? ): GenerationTask { val shape = when (shapeName) { "circle" -> Circle(Pair(center.x, center.z), Pair(start.x, start.z), radius) @@ -348,10 +344,14 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server else -> Spiral(Pair(center.x, center.z), Pair(start.x, start.z), radius) } - return if (PaperLib.isPaper()) { - GenerationTaskPaper(chunkmaster, world, start, radius, shape) - } else { - GenerationTaskSpigot(chunkmaster, world, start, radius, shape) - } + return DefaultGenerationTask( + chunkmaster, + unloader, + world, + start, + radius, + shape, pendingChunks?.toHashSet() ?: HashSet(), + TaskState.GENERATING + ) } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt index e3e889d..efa1005 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt @@ -3,37 +3,33 @@ package net.trivernis.chunkmaster.lib.generation import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.dynmap.* import net.trivernis.chunkmaster.lib.shapes.Shape -import org.bukkit.Chunk import org.bukkit.World -import java.lang.Exception +import java.util.concurrent.Semaphore +import kotlin.math.ceil /** * Interface for generation tasks. */ abstract class GenerationTask( private val plugin: Chunkmaster, + val world: World, + protected val unloader: ChunkUnloader, startChunk: ChunkCoordinates, - val shape: Shape + val shape: Shape, + val missingChunks: HashSet, + var state: TaskState ) : Runnable { abstract val radius: Int - abstract val world: World abstract var count: Int abstract var endReached: Boolean + var isRunning: Boolean = false - val loadedChunksCount: Int - get() { - return loadedChunks.size - } - - protected val loadedChunks: HashSet = HashSet() var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z) protected set - protected val chunkSkips = plugin.config.getInt("generation.chunk-skips-per-step") protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold") - protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") - protected val chunksPerStep = plugin.config.getInt("generation.chunks-per-step") + protected var cancelRun: Boolean = false private var endReachedCallback: ((GenerationTask) -> Unit)? = null @@ -45,16 +41,49 @@ abstract class GenerationTask( null } private val markerAreaStyle = MarkerStyle(null, LineStyle(2, 1.0, 0x0022FF), FillStyle(.0, 0)) - private val markerAreaId = "chunkmaster_genarea" - private val markerAreaName = "Chunkmaster Generation Area" - private val markerLastStyle = MarkerStyle(null, LineStyle(2, 1.0, 0x0077FF), FillStyle(.5, 0x0077FF)) - private val markerLastId = "chunkmaster_lastchunk" - private val markerLastName = "Chunkmaster Last Chunk" + private val markerAreaId = "chunkmaster_genarea_${world.name}" + private val markerAreaName = "Chunkmaster Generation Area (${ceil(shape.total()).toInt()} chunks)" private val ignoreWorldborder = plugin.config.getBoolean("generation.ignore-worldborder") - abstract override fun run() + abstract fun generate() + abstract fun validate() + abstract fun generateMissing() abstract fun cancel() + override fun run() { + isRunning = true + try { + when (state) { + TaskState.GENERATING -> { + this.generate() + if (!cancelRun) { + this.state = TaskState.VALIDATING + this.validate() + } + if (!cancelRun) { + this.state = TaskState.CORRECTING + this.generateMissing() + } + } + TaskState.VALIDATING -> { + this.validate() + if (!cancelRun) { + this.state = TaskState.CORRECTING + this.generateMissing() + } + } + TaskState.CORRECTING -> { + this.generateMissing() + } + else -> { } + } + if (!cancelRun && this.borderReached()) { + this.setEndReached() + } + } catch (e: InterruptedException){} + isRunning = false + } + val nextChunkCoordinates: ChunkCoordinates get() { val nextChunkCoords = shape.next() @@ -69,26 +98,6 @@ abstract class GenerationTask( || shape.endReached() } - /** - * Unloads all chunks that have been loaded - */ - protected fun unloadLoadedChunks() { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - try { - chunk.unload(true) - } catch (e: Exception) { - plugin.logger.severe(e.toString()) - } - } - if (dynmapIntegration) { - dynmap?.triggerRenderOfVolume(chunk.getBlock(0, 0, 0).location, chunk.getBlock(15, 255, 15).location) - } - } - - loadedChunks.clear() - } - /** * Updates the dynmap marker for the generation radius */ @@ -105,19 +114,11 @@ abstract class GenerationTask( } } - /** - * Updates the dynmap marker for the generation radius - */ - fun updateLastChunkMarker(clear: Boolean = false) { - if (clear) { - markerSet?.deleteAreaMarker(markerLastId) - } else if (dynmapIntegration) { - markerSet?.creUpdateAreMarker( - markerLastId, - markerLastName, - this.lastChunkCoords.getCenterLocation(world).chunk.getBlock(0, 0, 0).location, - this.lastChunkCoords.getCenterLocation(world).chunk.getBlock(15, 0, 15).location, - markerLastStyle + protected fun triggerDynmapRender(chunkCoordinates: ChunkCoordinates) { + if (dynmapIntegration) { + dynmap?.triggerRenderOfVolume( + world.getBlockAt(chunkCoordinates.x * 16, 0, chunkCoordinates.z * 16).location, + world.getBlockAt((chunkCoordinates.x * 16) + 16, 255, (chunkCoordinates.z * 16) + 16).location ) } } @@ -128,21 +129,8 @@ abstract class GenerationTask( private fun setEndReached() { endReached = true count = shape.count - endReachedCallback?.invoke(this) updateGenerationAreaMarker(true) - updateLastChunkMarker(true) - } - - /** - * Performs a check if the border has been reached - */ - protected fun borderReachedCheck(): Boolean { - val done = borderReached() - if (done) { - unloadLoadedChunks() - setEndReached() - } - return done + endReachedCallback?.invoke(this) } /** diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt deleted file mode 100644 index b1cefc3..0000000 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt +++ /dev/null @@ -1,121 +0,0 @@ -package net.trivernis.chunkmaster.lib.generation - -import net.trivernis.chunkmaster.Chunkmaster -import net.trivernis.chunkmaster.lib.shapes.Shape -import org.bukkit.Chunk -import org.bukkit.World -import java.lang.Exception -import java.util.concurrent.CompletableFuture - -class GenerationTaskPaper( - private val plugin: Chunkmaster, - override val world: World, - startChunk: ChunkCoordinates, - override val radius: Int = -1, - shape: Shape -) : GenerationTask(plugin, startChunk, shape) { - - private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks") - - private val pendingChunks = HashSet>() - - override var count = 0 - override var endReached: Boolean = false - - init { - updateGenerationAreaMarker() - count = shape.count - } - - /** - * Runs the generation task. Every Iteration the next chunks will be generated if - * they haven't been generated already - * After a configured number of chunks chunks have been generated, they will all be unloaded and saved. - */ - override fun run() { - if (plugin.mspt < msptThreshold) { - if (loadedChunks.size > maxLoadedChunks) { - unloadLoadedChunks() - } else if (pendingChunks.size < maxPendingChunks) { - if (borderReachedCheck()) return - - var chunk = nextChunkCoordinates - for (i in 0 until chunkSkips) { - if (world.isChunkGenerated(chunk.x, chunk.z)) { - chunk = nextChunkCoordinates - } else { - break - } - } - - if (!world.isChunkGenerated(chunk.x, chunk.z)) { - for (i in 0 until chunksPerStep) { - if (borderReached()) break - if (!world.isChunkGenerated(chunk.x, chunk.z)) { - pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true)) - } - chunk = nextChunkCoordinates - } - if (!world.isChunkGenerated(chunk.x, chunk.z)) { - pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true)) - } - } - lastChunkCoords = chunk - count = shape.count - } - } - checkChunksLoaded() - } - - /** - * Cancels the generation task. - * This unloads all chunks that were generated but not unloaded yet. - */ - override fun cancel() { - updateGenerationAreaMarker(true) - updateLastChunkMarker(true) - unloadAllChunks() - } - - /** - * Cancels all pending chunks and unloads all loaded chunks. - */ - private fun unloadAllChunks() { - for (pendingChunk in pendingChunks) { - if (pendingChunk.isDone) { - loadedChunks.add(pendingChunk.get()) - } else { - pendingChunk.cancel(true) - } - } - pendingChunks.clear() - if (loadedChunks.isNotEmpty()) { - lastChunkCoords = ChunkCoordinates(loadedChunks.last().x, loadedChunks.last().z) - } - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - try { - chunk.unload(true); - } catch (e: Exception){ - plugin.logger.severe(e.toString()) - } - } - } - } - - /** - * Checks if some chunks have been loaded and adds them to the loaded chunk set. - */ - private fun checkChunksLoaded() { - val completedEntries = HashSet>() - for (pendingChunk in pendingChunks) { - if (pendingChunk.isDone) { - completedEntries.add(pendingChunk) - loadedChunks.add(pendingChunk.get()) - } else if (pendingChunk.isCompletedExceptionally || pendingChunk.isCancelled) { - completedEntries.add(pendingChunk) - } - } - pendingChunks.removeAll(completedEntries) - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt deleted file mode 100644 index a812d3f..0000000 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt +++ /dev/null @@ -1,72 +0,0 @@ -package net.trivernis.chunkmaster.lib.generation - -import net.trivernis.chunkmaster.Chunkmaster -import net.trivernis.chunkmaster.lib.shapes.Shape -import org.bukkit.World -import java.lang.Exception - -class GenerationTaskSpigot( - private val plugin: Chunkmaster, - override val world: World, - startChunk: ChunkCoordinates, - override val radius: Int = -1, - shape: Shape -) : GenerationTask(plugin, startChunk, shape) { - - - override var count = 0 - override var endReached: Boolean = false - - init { - updateGenerationAreaMarker() - count = shape.count - } - - /** - * Runs the generation task. Every Iteration the next chunks will be generated if - * they haven't been generated already - * After a configured number of chunks chunks have been generated, they will all be unloaded and saved. - */ - override fun run() { - if (plugin.mspt < msptThreshold) { - if (loadedChunks.size > maxLoadedChunks) { - unloadLoadedChunks() - } else { - if (borderReachedCheck()) return - - var chunk = nextChunkCoordinates - for (i in 0 until chunksPerStep) { - if (borderReached()) break - val chunkInstance = world.getChunkAt(chunk.x, chunk.z) - chunkInstance.load(true) - loadedChunks.add(chunkInstance) - chunk = nextChunkCoordinates - } - val chunkInstance = world.getChunkAt(chunk.x, chunk.z) - chunkInstance.load(true) - loadedChunks.add(chunkInstance) - - lastChunkCoords = chunk - count = shape.count - } - } - } - - /** - * Cancels the generation task. - * This unloads all chunks that were generated but not unloaded yet. - */ - override fun cancel() { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - try { - chunk.unload(true) - } catch (e: Exception) { - plugin.logger.severe(e.toString()) - } - } - } - updateGenerationAreaMarker(true) - updateLastChunkMarker(true) - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PausedTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PausedTaskEntry.kt deleted file mode 100644 index e8a752e..0000000 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PausedTaskEntry.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.trivernis.chunkmaster.lib.generation - -class PausedTaskEntry( - override val id: Int, - override val generationTask: GenerationTask -) : TaskEntry { - override fun cancel() { - generationTask.cancel() - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt new file mode 100644 index 0000000..6aa5b64 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt @@ -0,0 +1,10 @@ +package net.trivernis.chunkmaster.lib.generation + +import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates +import org.bukkit.Chunk +import java.util.concurrent.CompletableFuture + +class PendingChunkEntry(val coordinates: ChunkCoordinates, val chunk: CompletableFuture) { + val isDone: Boolean + get() = chunk.isDone +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskEntry.kt deleted file mode 100644 index 47eda34..0000000 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskEntry.kt +++ /dev/null @@ -1,13 +0,0 @@ -package net.trivernis.chunkmaster.lib.generation - -import org.bukkit.scheduler.BukkitTask - -/** - * Generic task entry - */ -interface TaskEntry { - val id: Int - val generationTask: GenerationTask - - fun cancel() -} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskState.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskState.kt new file mode 100644 index 0000000..bf9117c --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/TaskState.kt @@ -0,0 +1,24 @@ +package net.trivernis.chunkmaster.lib.generation + +enum class TaskState { + GENERATING { + override fun toString(): String { + return "GENERATING" + } + }, + VALIDATING { + override fun toString(): String { + return "VALIDATING" + } + }, + CORRECTING { + override fun toString(): String { + return "CORRECTING" + } + }, + PAUSING { + override fun toString(): String { + return "PAUSING" + } + }, +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt new file mode 100644 index 0000000..57f2405 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt @@ -0,0 +1,9 @@ +package net.trivernis.chunkmaster.lib.generation.taskentry + +import net.trivernis.chunkmaster.lib.generation.GenerationTask + +class PausedTaskEntry( + override val id: Int, + override val generationTask: GenerationTask +) : TaskEntry { +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt similarity index 53% rename from src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt rename to src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt index d28d8bb..0aca508 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt @@ -1,15 +1,16 @@ -package net.trivernis.chunkmaster.lib.generation +package net.trivernis.chunkmaster.lib.generation.taskentry -import org.bukkit.scheduler.BukkitTask +import io.papermc.lib.PaperLib +import net.trivernis.chunkmaster.lib.generation.GenerationTask class RunningTaskEntry( override val id: Int, - val task: BukkitTask, override val generationTask: GenerationTask ) : TaskEntry { private var lastProgress: Pair? = null private var lastChunkCount: Pair? = null + private var thread = Thread(generationTask) /** * Returns the generation Speed @@ -20,13 +21,13 @@ class RunningTaskEntry( var chunkGenerationSpeed: Double? = null if (lastProgress != null) { val progressDiff = generationTask.shape.progress() - lastProgress!!.second - val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble()/1000 - generationSpeed = progressDiff/timeDiff + val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000 + generationSpeed = progressDiff / timeDiff } if (lastChunkCount != null) { val chunkDiff = generationTask.count - lastChunkCount!!.second - val timeDiff = (System.currentTimeMillis() - lastChunkCount!!.first).toDouble()/1000 - chunkGenerationSpeed = chunkDiff/timeDiff + val timeDiff = (System.currentTimeMillis() - lastChunkCount!!.first).toDouble() / 1000 + chunkGenerationSpeed = chunkDiff / timeDiff } lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) @@ -38,9 +39,32 @@ class RunningTaskEntry( lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) } + fun start() { + thread.start() + } + + fun cancel(timeout: Long): Boolean { + if (generationTask.isRunning) { + generationTask.cancel() + thread.interrupt() + } + return try { + joinThread(timeout) + } catch (e: InterruptedException) { + true + } + } + + private fun joinThread(timeout: Long): Boolean { + var threadStopped = false - override fun cancel() { - task.cancel() - generationTask.cancel() + for (i in 0..100) { + if (!thread.isAlive || !generationTask.isRunning) { + threadStopped = true + break + } + Thread.sleep(timeout / 100) + } + return threadStopped } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/TaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/TaskEntry.kt new file mode 100644 index 0000000..869e87b --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/TaskEntry.kt @@ -0,0 +1,11 @@ +package net.trivernis.chunkmaster.lib.generation.taskentry + +import net.trivernis.chunkmaster.lib.generation.GenerationTask + +/** + * Generic task entry + */ +interface TaskEntry { + val id: Int + val generationTask: GenerationTask +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt index 4702635..a410228 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt @@ -10,7 +10,7 @@ import kotlin.math.pow import kotlin.math.sqrt import kotlin.system.exitProcess -class Circle(center: Pair, start: Pair, radius: Int): Shape(center, start, radius) { +class Circle(center: Pair, start: Pair, radius: Int) : Shape(center, start, radius) { private var r = 0 private var coords = Stack>() private var previousCoords = HashSet>() @@ -20,9 +20,13 @@ class Circle(center: Pair, start: Pair, radius: Int): Shape( return radius > 0 && coords.isEmpty() && r >= radius } + override fun total(): Double { + return (PI * radius.toFloat().pow(2)) + } + override fun progress(): Double { // TODO: Radius inner progress - return (count/(PI* radius.toFloat().pow(2))).coerceAtMost(100.0) + return (count / (PI * radius.toFloat().pow(2))).coerceAtMost(1.0) } override fun currentRadius(): Int { @@ -36,7 +40,7 @@ class Circle(center: Pair, start: Pair, radius: Int): Shape( override fun getShapeEdgeLocations(): List> { val locations = this.getCircleCoordinates(this.radius) locations.add(locations.first()) - return locations.map{ Pair(it.first + center.first, it.second + center.second) } + return locations.map { Pair(it.first + center.first, it.second + center.second) } } /** @@ -59,7 +63,7 @@ class Circle(center: Pair, start: Pair, radius: Int): Shape( if (coords.isEmpty()) { r++ val tmpCoords = HashSet>() - tmpCoords.addAll(getCircleCoordinates((r*2)-1).map { Pair(it.first / 2, it.second / 2) }) + tmpCoords.addAll(getCircleCoordinates((r * 2) - 1).map { Pair(it.first / 2, it.second / 2) }) tmpCoords.addAll(getCircleCoordinates(r)) tmpCoords.removeAll(previousCoords) previousCoords.clear() @@ -77,16 +81,18 @@ class Circle(center: Pair, start: Pair, radius: Int): Shape( * Some coordinates might already be present in the list * @param r - the radius */ - private fun getCircleCoordinates(r: Int): ArrayList> { - val coords = ArrayList>() + private fun getCircleCoordinates(r: Int): Vector> { + val coords = Vector>() val segCoords = getSegment(r) coords.addAll(segCoords.reversed()) + for (step in 1..7) { - val tmpSeg = ArrayList>() + val tmpSeg = Vector>() + for (pos in segCoords) { val coord = when (step) { 1 -> Pair(pos.first, -pos.second) - 2 ->Pair(pos.second, -pos.first) + 2 -> Pair(pos.second, -pos.first) 3 -> Pair(-pos.second, -pos.first) 4 -> Pair(-pos.first, -pos.second) 5 -> Pair(-pos.first, pos.second) @@ -128,4 +134,11 @@ class Circle(center: Pair, start: Pair, radius: Int): Shape( } return coords } -} \ No newline at end of file + + override fun reset() { + this.r = 0 + this.currentPos = center + this.previousCoords.clear() + this.count = 0 + } +} diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt index 22d3902..298b603 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt @@ -25,6 +25,11 @@ abstract class Shape(protected val center: Pair, start: Pair */ abstract fun progress(): Double + /** + * The total number of chunks to generate + */ + abstract fun total(): Double + /** * Returns the current radius */ @@ -34,4 +39,9 @@ abstract class Shape(protected val center: Pair, start: Pair * returns a poly marker for the shape */ abstract fun getShapeEdgeLocations(): List> + + /** + * Resets the shape to its center start position + */ + abstract fun reset() } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt index 33b7ac3..7f4e345 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt @@ -10,11 +10,18 @@ class Spiral(center: Pair, start: Pair, radius: Int): Shape( override fun endReached(): Boolean { val distances = getDistances(center, currentPos) - return radius > 0 && (distances.first > radius || distances.second > radius) + return radius > 0 && ((direction == 3 + && abs(distances.first) == abs(distances.second) + && abs(distances.first) == radius) + || (distances.first > radius || distances.second > radius)) + } + + override fun total(): Double { + return (radius * 2).toDouble().pow(2) } override fun progress(): Double { - return (count / (radius * 2).toDouble().pow(2)).coerceAtMost(100.0) + return (count / (radius * 2).toDouble().pow(2)).coerceAtMost(1.0) } override fun currentRadius(): Int { @@ -26,6 +33,9 @@ class Spiral(center: Pair, start: Pair, radius: Int): Shape( * Returns the next value in the spiral */ override fun next(): Pair { + if (endReached()) { + return currentPos + } if (count == 0 && currentPos != center) { // simulate the spiral to get the correct direction and count val simSpiral = Spiral(center, center, radius) @@ -86,4 +96,13 @@ class Spiral(center: Pair, start: Pair, radius: Int): Shape( private fun getDistances(pos1: Pair, pos2: Pair): Pair { return Pair(pos2.first - pos1.first, pos2.second - pos1.second) } + + /** + * Resets the shape to its starting parameters + */ + override fun reset() { + this.currentPos = center + this.count = 0 + this.direction = 0 + } } \ No newline at end of file diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index 283d4c3..84c5b94 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -1,11 +1,12 @@ RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... TASK_FINISHED = Task #%d finished after %d chunks. -TASK_CANCELED = Canceled task #%s. +TASK_CANCELLED = Cancelled task #%s. TASK_LOAD_FAILED = §cFailed to load task #%d. TASK_LOAD_SUCCESS = %d saved tasks loaded. TASK_NOT_FOUND = §cTask %s not found! CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later... -TASK_PERIODIC_REPORT = Task #%d running for '%s'. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d +TASK_PERIODIC_REPORT = Task #%d running for '%s'. State: %s. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d +TASK_PERIODIC_REPORT_CORRECTING = Task #%d generating missing chunks for '%s'. Progress: %d chunks %s TASK_SAVE_FAILED = §cException when saving tasks: %s WORLD_NAME_REQUIRED = §cYou need to provide a world name! @@ -18,7 +19,7 @@ TASK_ID_REQUIRED = §cYou need to provide a task id! INVALID_ARGUMENT = §cInvalid argument at %s: %s! PAUSED_TASKS_HEADER = Currently Paused Generation Tasks -TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d chunks %s§r +TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s chunks %s§r RUNNING_TASKS_HEADER = Currently Running Generation Tasks NO_GENERATION_TASKS = There are no generation tasks. @@ -72,4 +73,7 @@ STATS_CORES = - Cores: §2%d§r STATS_WORLD_NAME = §l%s§r STATS_ENTITY_COUNT = - §2%d§r Entities STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks -STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster \ No newline at end of file +STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster + +SAVING_CHUNKS = Saving %d loaded chunks... +CANCEL_FAIL = Failed to cancel task #%d in the given timeout! \ No newline at end of file diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index ece9aa2..4aae92a 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -1,11 +1,12 @@ RESUME_FOR_WORLD = Setze das Chunk-Generieren für Welt '%s' fort... TASK_FINISHED = Aufgabe #%d wurde nach %d chunks beendet. -TASK_CANCELED = Aufgabe #%s wurde abgebrochen. +TASK_CANCELLED = Aufgabe #%s wurde abgebrochen. TASK_LOAD_FAILED = §cAufgabe #%d konnte nicht geladen werden. TASK_LOAD_SUCCESS = %d gespeicherte Aufgaben wurden geladen. TASK_NOT_FOUND = §cAufgabe %s konnte nicht gefunden werden! CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verzögerten Laden von Aufgaben... -TASK_PERIODIC_REPORT = Aufgabe #%d für Welt '%s'. Fortschritt: %d chunks %s %s, Geschwindigkeit: %.1f ch/s, Letzer Chunk: %d, %d +TASK_PERIODIC_REPORT = Aufgabe #%d für Welt '%s'. Status: %s. Fortschritt: %d chunks %s %s, Geschwindigkeit: %.1f ch/s, Letzer Chunk: %d, %d +TASK_PERIODIC_REPORT_CORRECTING = Aufgabe #%d generiert fehlende Chunks für Welt '%s'. Fortschritt: %d chunks %s TASK_SAVE_FAILED = §cFehler beim Speichern der Aufgaben: %s WORLD_NAME_REQUIRED = §cDu musst einen Weltennamen angeben! @@ -72,4 +73,7 @@ STATS_CORES = - Kerne: §2%d§r STATS_WORLD_NAME = §l%s§r STATS_ENTITY_COUNT = - §2%d§r Entities STATS_LOADED_CHUNKS = - §2%d§r Geladene Chunks -STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r von Chunkmaster geladene Chunks \ No newline at end of file +STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r von Chunkmaster geladene Chunks + +SAVING_CHUNKS = Speichere %d geladene Chunks... +CANCEL_FAIL = Konnte Aufgabe #%d nicht im angegebenen Timeout stoppen! \ No newline at end of file diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index 283d4c3..b381039 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -1,11 +1,11 @@ RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... TASK_FINISHED = Task #%d finished after %d chunks. -TASK_CANCELED = Canceled task #%s. +TASK_CANCELLED = Cancelled task #%s. TASK_LOAD_FAILED = §cFailed to load task #%d. TASK_LOAD_SUCCESS = %d saved tasks loaded. TASK_NOT_FOUND = §cTask %s not found! CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later... -TASK_PERIODIC_REPORT = Task #%d running for '%s'. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d +TASK_PERIODIC_REPORT = Task #%d running for '%s'. State: %s. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d TASK_SAVE_FAILED = §cException when saving tasks: %s WORLD_NAME_REQUIRED = §cYou need to provide a world name! @@ -18,7 +18,6 @@ TASK_ID_REQUIRED = §cYou need to provide a task id! INVALID_ARGUMENT = §cInvalid argument at %s: %s! PAUSED_TASKS_HEADER = Currently Paused Generation Tasks -TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d chunks %s§r RUNNING_TASKS_HEADER = Currently Running Generation Tasks NO_GENERATION_TASKS = There are no generation tasks. @@ -72,4 +71,7 @@ STATS_CORES = - Cores: §2%d§r STATS_WORLD_NAME = §l%s§r STATS_ENTITY_COUNT = - §2%d§r Entities STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks -STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster \ No newline at end of file +STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster + +SAVING_CHUNKS = Saving %d loaded chunks... +CANCEL_FAIL = Failed to cancel task #%d in the given timeout! \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 27e99d1..640c698 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ main: net.trivernis.chunkmaster.Chunkmaster name: Chunkmaster -version: '1.2.3' +version: '1.3.0' description: Automated world pregeneration. author: Trivernis website: trivernis.net From 1f167285b0c8d82aefde64dcbc74c0a43449f4e0 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 26 Jul 2020 16:58:20 +0200 Subject: [PATCH 02/16] Add total chunk count to list command (closes #79) (#82) --- .../kotlin/net/trivernis/chunkmaster/commands/CmdList.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt index a4752dc..54082ce 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt @@ -5,6 +5,7 @@ import net.trivernis.chunkmaster.lib.Subcommand import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry import org.bukkit.command.Command import org.bukkit.command.CommandSender +import kotlin.math.ceil class CmdList(private val chunkmaster: Chunkmaster): Subcommand { override val name = "list" @@ -52,7 +53,12 @@ class CmdList(private val chunkmaster: Chunkmaster): Subcommand { " (%.1f".format(genTask.shape.progress()*100) + "%)." else "" + val count = if (genTask.radius > 0) { + "${genTask.count} / ${ceil(genTask.shape.total()).toInt()}" + } else { + genTask.count.toString() + } return "\n" + chunkmaster.langManager.getLocalized("TASKS_ENTRY", - task.id, genTask.world.name, genTask.state.toString(), genTask.count, percentage) + task.id, genTask.world.name, genTask.state.toString(), count, percentage) } } \ No newline at end of file From af875aaca09df0fa8a70dab0f4a714540764c1a6 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 26 Jul 2020 17:29:59 +0200 Subject: [PATCH 03/16] Fix shape beign stuck (#83) --- .../chunkmaster/lib/generation/DefaultGenerationTask.kt | 3 ++- src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt | 2 +- src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt index 2c96430..4880f7a 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt @@ -85,7 +85,8 @@ class DefaultGenerationTask( do { lastChunkCoords = nextChunkCoordinates count = shape.count - } while (PaperLib.isChunkGenerated(world, lastChunkCoords.x, lastChunkCoords.z)) + println(count) + } while (PaperLib.isChunkGenerated(world, lastChunkCoords.x, lastChunkCoords.z) && !borderReached()) } /** diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt index a410228..2546b04 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt @@ -52,7 +52,7 @@ class Circle(center: Pair, start: Pair, radius: Int) : Shape } if (count == 0 && currentPos != center) { val tmpCircle = Circle(center, center, radius) - while (tmpCircle.next() != currentPos); + while (tmpCircle.next() != currentPos && !tmpCircle.endReached()); this.count = tmpCircle.count this.r = tmpCircle.r } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt index 7f4e345..9564ca6 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt @@ -39,7 +39,7 @@ class Spiral(center: Pair, start: Pair, radius: Int): Shape( if (count == 0 && currentPos != center) { // simulate the spiral to get the correct direction and count val simSpiral = Spiral(center, center, radius) - while (simSpiral.next() != currentPos); + while (simSpiral.next() != currentPos && !simSpiral.endReached()); direction = simSpiral.direction count = simSpiral.count } From ac6a80ed8684f5651ed5345d514cf2b7b178cccf Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 26 Jul 2020 17:42:55 +0200 Subject: [PATCH 04/16] Add autostart config parameter (closes #78) (#84) --- README.md | 4 ++++ .../net/trivernis/chunkmaster/Chunkmaster.kt | 1 + .../lib/generation/DefaultGenerationTask.kt | 1 - .../lib/generation/GenerationManager.kt | 20 ++++++++++++++++--- .../resources/i18n/DEFAULT.i18n.properties | 3 ++- src/main/resources/i18n/de.i18n.properties | 3 ++- src/main/resources/i18n/en.i18n.properties | 3 ++- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 85b37fb..8258307 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ generation: # very laggy and can cause it to crash. # The value should be a posivitve integer > 1. pause-on-player-count: 1 + + # if the generation should automatically start on server startup + # the value should be a boolean + autostart: true ``` ### Spigot and Paper diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index d87b605..6d74308 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -80,6 +80,7 @@ class Chunkmaster: JavaPlugin() { config.addDefault("generation.max-loaded-chunks", 1000) config.addDefault("generation.unloading-period", 50L) config.addDefault("generation.ignore-worldborder", false) + config.addDefault("generation.autostart", true) config.addDefault("database.filename", "chunkmaster.db") config.addDefault("language", "en") config.addDefault("dynmap", true) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt index 4880f7a..8441755 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt @@ -85,7 +85,6 @@ class DefaultGenerationTask( do { lastChunkCoords = nextChunkCoordinates count = shape.count - println(count) } while (PaperLib.isChunkGenerated(world, lastChunkCoords.x, lastChunkCoords.z) && !borderReached()) } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index ed18128..dbe64a6 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -17,7 +17,18 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val worldProperties = chunkmaster.sqliteManager.worldProperties private val pendingChunksTable = chunkmaster.sqliteManager.pendingChunks private val generationTasks = chunkmaster.sqliteManager.generationTasks - private val unloadingPeriod = chunkmaster.config.getLong("generation.unloading-period") + private val unloadingPeriod: Long + get() { + return chunkmaster.config.getLong("generation.unloading-period") + } + private val pauseOnPlayerCount: Int + get () { + return chunkmaster.config.getInt("generation.pause-on-player-count") + } + private val autostart: Boolean + get () { + return chunkmaster.config.getBoolean("generation.autostart") + } val loadedChunkCount: Int get() { @@ -30,7 +41,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server get() { if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) { this.startAll() - if (server.onlinePlayers.size >= chunkmaster.config.getInt("generation.pause-on-player-count")) { + if (server.onlinePlayers.size >= pauseOnPlayerCount) { this.pauseAll() } } @@ -155,7 +166,10 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server }, 600, 600) server.scheduler.runTaskLater(chunkmaster, Runnable { this.startAll() - if (!server.onlinePlayers.isEmpty()) { + if (server.onlinePlayers.count() >= pauseOnPlayerCount || !autostart) { + if (!autostart) { + chunkmaster.logger.info(chunkmaster.langManager.getLocalized("NO_AUTOSTART")) + } this.pauseAll() } }, 20) diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index 84c5b94..b8719ec 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -76,4 +76,5 @@ STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster SAVING_CHUNKS = Saving %d loaded chunks... -CANCEL_FAIL = Failed to cancel task #%d in the given timeout! \ No newline at end of file +CANCEL_FAIL = Failed to cancel task #%d in the given timeout! +NO_AUTOSTART = Autostart set to §2false§r. Pausing... \ No newline at end of file diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index 4aae92a..9eedf03 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -76,4 +76,5 @@ STATS_LOADED_CHUNKS = - §2%d§r Geladene Chunks STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r von Chunkmaster geladene Chunks SAVING_CHUNKS = Speichere %d geladene Chunks... -CANCEL_FAIL = Konnte Aufgabe #%d nicht im angegebenen Timeout stoppen! \ No newline at end of file +CANCEL_FAIL = Konnte Aufgabe #%d nicht im angegebenen Timeout stoppen! +NO_AUTOSTART = Autostart ist auf §2false§r gesetzt. Pausiere... \ No newline at end of file diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index b381039..06a257f 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -74,4 +74,5 @@ STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster SAVING_CHUNKS = Saving %d loaded chunks... -CANCEL_FAIL = Failed to cancel task #%d in the given timeout! \ No newline at end of file +CANCEL_FAIL = Failed to cancel task #%d in the given timeout! +NO_AUTOSTART = Autostart set to §2false§r. Pausing... \ No newline at end of file From cf7ef7d88762d608967c5aa69ac401ba380045b6 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 26 Jul 2020 17:46:37 +0200 Subject: [PATCH 05/16] Add circleci badge to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8258307..c613222 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# chunkmaster ![](https://abstruse.trivernis.net/badge/1) ![](https://img.shields.io/discord/729250668162056313) +# chunkmaster ![](https://circleci.com/gh/Trivernis/spigot-chunkmaster.svg?style=shield) ![](https://img.shields.io/discord/729250668162056313) This plugin can be used to pre-generate the region of a world around the spawn chunk(s). The generation automatically pauses when a player joins the server (assuming the server was empty before) From 7ffada41006d9cf48c919c8b7d8cc8d32f9a9795 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 26 Jul 2020 17:49:04 +0200 Subject: [PATCH 06/16] Add codefactor badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c613222..57b6fce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# chunkmaster ![](https://circleci.com/gh/Trivernis/spigot-chunkmaster.svg?style=shield) ![](https://img.shields.io/discord/729250668162056313) +# chunkmaster [![](https://circleci.com/gh/Trivernis/spigot-chunkmaster.svg?style=shield)](https://app.circleci.com/pipelines/github/Trivernis/spigot-chunkmaster) [![CodeFactor](https://www.codefactor.io/repository/github/trivernis/spigot-chunkmaster/badge)](https://www.codefactor.io/repository/github/trivernis/spigot-chunkmaster) [![](https://img.shields.io/discord/729250668162056313)](https://discord.gg/KZcMAgN) This plugin can be used to pre-generate the region of a world around the spawn chunk(s). The generation automatically pauses when a player joins the server (assuming the server was empty before) From aaa614f651bfa80e74cee32fceac2ec66363389d Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 26 Jul 2020 18:13:47 +0200 Subject: [PATCH 07/16] Cleanup --- .../net/trivernis/chunkmaster/Chunkmaster.kt | 6 ++-- .../chunkmaster/ChunkmasterEvents.kt | 4 +-- .../chunkmaster/commands/CmdCancel.kt | 4 +-- .../chunkmaster/commands/CmdGenerate.kt | 35 ++++++++++--------- .../chunkmaster/commands/CmdGetCenter.kt | 13 +++++-- .../trivernis/chunkmaster/commands/CmdList.kt | 10 +++--- .../chunkmaster/commands/CmdPause.kt | 2 -- .../chunkmaster/commands/CmdReload.kt | 4 +-- .../chunkmaster/commands/CmdResume.kt | 4 +-- .../chunkmaster/commands/CmdSetCenter.kt | 4 +-- .../chunkmaster/commands/CmdStats.kt | 21 +++++++---- .../chunkmaster/commands/CmdTpChunk.kt | 4 +-- .../commands/CommandChunkmaster.kt | 2 -- .../chunkmaster/lib/LanguageManager.kt | 8 +++-- .../lib/database/GenerationTasks.kt | 3 +- .../chunkmaster/lib/database/SqliteManager.kt | 33 +++++++++++++---- .../lib/database/WorldProperties.kt | 9 ++--- .../lib/dynmap/ExtendedMarkerSet.kt | 19 ++++++++-- .../chunkmaster/lib/dynmap/MarkerStyle.kt | 7 +++- .../lib/generation/ChunkCoordinates.kt | 2 +- .../lib/generation/ChunkUnloader.kt | 5 +-- .../lib/generation/DefaultGenerationTask.kt | 2 +- .../lib/generation/GenerationManager.kt | 4 +-- .../lib/generation/GenerationTask.kt | 10 +++--- .../lib/generation/PendingChunkEntry.kt | 1 - .../generation/taskentry/RunningTaskEntry.kt | 1 - .../chunkmaster/lib/shapes/Circle.kt | 10 +++--- .../trivernis/chunkmaster/lib/shapes/Shape.kt | 4 --- .../chunkmaster/lib/shapes/Spiral.kt | 16 ++++----- 29 files changed, 145 insertions(+), 102 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index 6d74308..448b873 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -10,7 +10,7 @@ import org.bukkit.plugin.java.JavaPlugin import org.bukkit.scheduler.BukkitTask import org.dynmap.DynmapAPI -class Chunkmaster: JavaPlugin() { +class Chunkmaster : JavaPlugin() { lateinit var sqliteManager: SqliteManager lateinit var generationManager: GenerationManager lateinit var langManager: LanguageManager @@ -48,7 +48,7 @@ class Chunkmaster: JavaPlugin() { if (PaperLib.isPaper() && PaperLib.getMinecraftPatchVersion() >= 225) { tpsTask = server.scheduler.runTaskTimer(this, Runnable { - mspt = 1000/server.currentTick // use papers exposed tick rather than calculating it + mspt = 1000 / server.currentTick // use papers exposed tick rather than calculating it }, 1, 300) } else { tpsTask = server.scheduler.runTaskTimer(this, Runnable { @@ -97,7 +97,7 @@ class Chunkmaster: JavaPlugin() { this.sqliteManager = SqliteManager(this) sqliteManager.init() logger.info(langManager.getLocalized("DB_INIT_FINISHED")) - } catch(e: Exception) { + } catch (e: Exception) { logger.warning(langManager.getLocalized("DB_INIT_EROR", e.message!!)) } } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt index 650f65c..2ec66fb 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt @@ -9,7 +9,7 @@ import org.bukkit.event.player.PlayerQuitEvent class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server) : Listener { private val pauseOnPlayerCount: Int - get () { + get() { return chunkmaster.config.getInt("generation.pause-on-player-count") } private var playerPaused = false @@ -25,7 +25,7 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_PLAYER_LEAVE")) } chunkmaster.generationManager.resumeAll() - } else if (chunkmaster.generationManager.paused){ + } else if (chunkmaster.generationManager.paused) { chunkmaster.logger.info(chunkmaster.langManager.getLocalized("PAUSE_MANUALLY")) playerPaused = chunkmaster.generationManager.paused } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt index ec2bb78..0af6b7c 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdCancel.kt @@ -5,7 +5,7 @@ import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.command.Command import org.bukkit.command.CommandSender -class CmdCancel(private val chunkmaster: Chunkmaster): Subcommand { +class CmdCancel(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "cancel" /** @@ -19,7 +19,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster): Subcommand { ): MutableList { val genManager = chunkmaster.generationManager val allTasks = genManager.allTasks - return allTasks.filter {it.id.toString().indexOf(args[0]) == 0} + return allTasks.filter { it.id.toString().indexOf(args[0]) == 0 } .map { it.id.toString() }.toMutableList() } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt index a9eccae..0eb74b1 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt @@ -6,7 +6,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player -class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { +class CmdGenerate(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "generate" /** @@ -20,14 +20,14 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { ): MutableList { if (args.size == 1) { return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } - .map {it.name}.toMutableList() + .map { it.name }.toMutableList() } else if (args.size == 2) { if (args[0].toIntOrNull() != null) { - return shapes.filter {it.indexOf(args[1]) == 0}.toMutableList() + return shapes.filter { it.indexOf(args[1]) == 0 }.toMutableList() } } else if (args.size > 2) { if (args[1].toIntOrNull() != null) { - return shapes.filter {it.indexOf(args[2]) == 0}.toMutableList() + return shapes.filter { it.indexOf(args[2]) == 0 }.toMutableList() } } return emptyList().toMutableList() @@ -97,19 +97,22 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { val world = chunkmaster.server.getWorld(worldName) val allTasks = chunkmaster.generationManager.allTasks return if (world != null && (allTasks.find { it.generationTask.world == world }) == null) { - chunkmaster.generationManager.addTask(world, if (blockRadius > 0) blockRadius/16 else -1 , shape) - sender.sendMessage(chunkmaster.langManager - .getLocalized("TASK_CREATION_SUCCESS", - worldName, - if (blockRadius > 0) { - chunkmaster.langManager.getLocalized("TASK_UNIT_RADIUS", blockRadius) - } else{ - chunkmaster.langManager.getLocalized("TASK_UNIT_WORLDBORDER") - }, - shape - )) + chunkmaster.generationManager.addTask(world, if (blockRadius > 0) blockRadius / 16 else -1, shape) + sender.sendMessage( + chunkmaster.langManager + .getLocalized( + "TASK_CREATION_SUCCESS", + worldName, + if (blockRadius > 0) { + chunkmaster.langManager.getLocalized("TASK_UNIT_RADIUS", blockRadius) + } else { + chunkmaster.langManager.getLocalized("TASK_UNIT_WORLDBORDER") + }, + shape + ) + ) true - } else if (world == null){ + } else if (world == null) { sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)); false } else { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt index 316d8e8..a0367dc 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt @@ -6,7 +6,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player -class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { +class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "getCenter"; override fun onTabComplete( @@ -17,7 +17,7 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { ): MutableList { if (args.size == 1) { return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } - .map {it.name}.toMutableList() + .map { it.name }.toMutableList() } return emptyList().toMutableList() } @@ -55,7 +55,14 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { } center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) } - sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_INFO", worldName, center.first, center.second)) + sender.sendMessage( + chunkmaster.langManager.getLocalized( + "CENTER_INFO", + worldName, + center.first, + center.second + ) + ) } } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt index 54082ce..df5b6ed 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt @@ -7,7 +7,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import kotlin.math.ceil -class CmdList(private val chunkmaster: Chunkmaster): Subcommand { +class CmdList(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "list" override fun onTabComplete( @@ -50,7 +50,7 @@ class CmdList(private val chunkmaster: Chunkmaster): Subcommand { private fun getGenerationEntry(task: TaskEntry): String { val genTask = task.generationTask val percentage = if (genTask.radius > 0) - " (%.1f".format(genTask.shape.progress()*100) + "%)." + " (%.1f".format(genTask.shape.progress() * 100) + "%)." else "" val count = if (genTask.radius > 0) { @@ -58,7 +58,9 @@ class CmdList(private val chunkmaster: Chunkmaster): Subcommand { } else { genTask.count.toString() } - return "\n" + chunkmaster.langManager.getLocalized("TASKS_ENTRY", - task.id, genTask.world.name, genTask.state.toString(), count, percentage) + return "\n" + chunkmaster.langManager.getLocalized( + "TASKS_ENTRY", + task.id, genTask.world.name, genTask.state.toString(), count, percentage + ) } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdPause.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdPause.kt index c49cddc..e5ca5f9 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdPause.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdPause.kt @@ -1,7 +1,5 @@ package net.trivernis.chunkmaster.commands -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.ComponentBuilder import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.command.Command diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt index 1503770..6834708 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt @@ -1,13 +1,11 @@ package net.trivernis.chunkmaster.commands -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.ComponentBuilder import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.command.Command import org.bukkit.command.CommandSender -class CmdReload(private val chunkmaster: Chunkmaster): Subcommand { +class CmdReload(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "reload" override fun onTabComplete( diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdResume.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdResume.kt index 2d143f0..5c6ddde 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdResume.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdResume.kt @@ -1,13 +1,11 @@ package net.trivernis.chunkmaster.commands -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.ComponentBuilder import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.command.Command import org.bukkit.command.CommandSender -class CmdResume(private val chunkmaster: Chunkmaster): Subcommand { +class CmdResume(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "resume" override fun onTabComplete( diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt index 9adee80..863c577 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt @@ -6,7 +6,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player -class CmdSetCenter(private val chunkmaster: Chunkmaster): Subcommand { +class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "setCenter"; override fun onTabComplete( @@ -18,7 +18,7 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster): Subcommand { if (args.size == 1) { if (args[0].toIntOrNull() == null) { return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } - .map {it.name}.toMutableList() + .map { it.name }.toMutableList() } } return emptyList().toMutableList(); diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt index bab4b20..e0d7dab 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdStats.kt @@ -5,9 +5,8 @@ import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.World import org.bukkit.command.Command import org.bukkit.command.CommandSender -import org.bukkit.entity.Player -class CmdStats(private val chunkmaster: Chunkmaster): Subcommand { +class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "stats" override fun onTabComplete( @@ -23,8 +22,10 @@ class CmdStats(private val chunkmaster: Chunkmaster): Subcommand { if (args.isNotEmpty()) { val world = sender.server.getWorld(args[0]) if (world == null) { - sender.sendMessage(chunkmaster.langManager.getLocalized("STATS_HEADER") + "\n" + - chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", args[0])) + sender.sendMessage( + chunkmaster.langManager.getLocalized("STATS_HEADER") + "\n" + + chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", args[0]) + ) return false } sender.sendMessage(getWorldStatsMessage(sender, world)) @@ -53,10 +54,18 @@ class CmdStats(private val chunkmaster: Chunkmaster): Subcommand { ${chunkmaster.langManager.getLocalized("STATS_SERVER")} ${chunkmaster.langManager.getLocalized("STATS_SERVER_VERSION", sender.server.version)} ${chunkmaster.langManager.getLocalized("STATS_PLUGIN_VERSION", chunkmaster.description.version)} - ${chunkmaster.langManager.getLocalized("STATS_MEMORY", memUsed/1000000, runtime.maxMemory()/1000000, (memUsed.toFloat()/runtime.maxMemory().toFloat()) * 100)} + ${chunkmaster.langManager.getLocalized( + "STATS_MEMORY", + memUsed / 1000000, + runtime.maxMemory() / 1000000, + (memUsed.toFloat() / runtime.maxMemory().toFloat()) * 100 + )} ${chunkmaster.langManager.getLocalized("STATS_CORES", runtime.availableProcessors())} - ${chunkmaster.langManager.getLocalized("STATS_PLUGIN_LOADED_CHUNKS", chunkmaster.generationManager.loadedChunkCount)} + ${chunkmaster.langManager.getLocalized( + "STATS_PLUGIN_LOADED_CHUNKS", + chunkmaster.generationManager.loadedChunkCount + )} """.trimIndent() for (world in sender.server.worlds) { message += "\n\n" + getWorldStatsMessage(sender, world) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt index 8ffcb6d..3dceb7f 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt @@ -1,8 +1,6 @@ package net.trivernis.chunkmaster.commands import io.papermc.lib.PaperLib -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.ComponentBuilder import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.Material @@ -10,7 +8,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player -class CmdTpChunk(private val chunkmaster: Chunkmaster): Subcommand { +class CmdTpChunk(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "tpchunk" override fun onTabComplete( diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt index e33211d..b8870ea 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt @@ -1,7 +1,5 @@ package net.trivernis.chunkmaster.commands -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.ComponentBuilder import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.Server diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt index 467cdfa..4b19e74 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt @@ -1,8 +1,8 @@ package net.trivernis.chunkmaster.lib + import net.trivernis.chunkmaster.Chunkmaster -import java.lang.Exception -import java.util.Properties import java.io.* +import java.util.* class LanguageManager(private val plugin: Chunkmaster) { private val langProps = Properties() @@ -20,12 +20,14 @@ class LanguageManager(private val plugin: Chunkmaster) { val file = File(langFile) val loader = Thread.currentThread().contextClassLoader val defaultStream = this.javaClass.getResourceAsStream("/i18n/DEFAULT.i18n.properties") + if (defaultStream != null) { langProps.load(getReaderForProperties(defaultStream)) defaultStream.close() } else { plugin.logger.severe("Couldn't load default language properties.") } + if (file.exists()) { try { val inputStream = loader.getResourceAsStream(langFile) @@ -34,7 +36,7 @@ class LanguageManager(private val plugin: Chunkmaster) { langFileLoaded = true inputStream.close() } - } catch (e: Exception) { + } catch (e: Exception) { plugin.logger.warning("Language file $langFile could not be loaded!") plugin.logger.fine(e.toString()) } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt index c8f9948..7f90b21 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/GenerationTasks.kt @@ -37,7 +37,8 @@ class GenerationTasks(private val sqliteManager: SqliteManager) { */ fun addGenerationTask(world: String, center: ChunkCoordinates, radius: Int, shape: String): CompletableFuture { val completableFuture = CompletableFuture() - sqliteManager.executeStatement(""" + sqliteManager.executeStatement( + """ INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, radius, shape) values (?, ?, ?, ?, ?, ?, ?)""", hashMapOf( diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt index c65940a..bd16d7b 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt @@ -2,7 +2,6 @@ package net.trivernis.chunkmaster.lib.database import net.trivernis.chunkmaster.Chunkmaster import org.apache.commons.lang.exception.ExceptionUtils -import java.lang.Exception import java.sql.Connection import java.sql.DriverManager import java.sql.ResultSet @@ -59,8 +58,10 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { } try { Class.forName("org.sqlite.JDBC") - this.connection = DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + - "${chunkmaster.config.getString("database.filename")}") + this.connection = DriverManager.getConnection( + "jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + + "${chunkmaster.config.getString("database.filename")}" + ) return this.connection } catch (e: Exception) { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) @@ -143,11 +144,17 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { try { var tableDef = "CREATE TABLE IF NOT EXISTS $table (" - for (column in tables.find{it.first == table}!!.second) { + for (column in tables.find { it.first == table }!!.second) { tableDef += "${column.first} ${column.second}," } tableDef = tableDef.substringBeforeLast(",") + ");" - chunkmaster.logger.finest(chunkmaster.langManager.getLocalized("CREATE_TABLE_DEFINITION", table, tableDef)) + chunkmaster.logger.finest( + chunkmaster.langManager.getLocalized( + "CREATE_TABLE_DEFINITION", + table, + tableDef + ) + ) executeStatement(tableDef, HashMap(), null) } catch (e: Exception) { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TABLE_CREATE_ERROR", table)) @@ -159,9 +166,21 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}" try { executeStatement(updateSql, HashMap(), null) - chunkmaster.logger.finest(chunkmaster.langManager.getLocalized("UPDATE_TABLE_DEFINITION", table.first, updateSql)) + chunkmaster.logger.finest( + chunkmaster.langManager.getLocalized( + "UPDATE_TABLE_DEFINITION", + table.first, + updateSql + ) + ) } catch (e: Exception) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("UPDATE_TABLE_FAILED", table.first, updateSql)) + chunkmaster.logger.severe( + chunkmaster.langManager.getLocalized( + "UPDATE_TABLE_FAILED", + table.first, + updateSql + ) + ) chunkmaster.logger.severe(e.message) chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt index fe6dab4..9853afe 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/WorldProperties.kt @@ -1,6 +1,5 @@ package net.trivernis.chunkmaster.lib.database -import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates import java.util.concurrent.CompletableFuture class WorldProperties(private val sqliteManager: SqliteManager) { @@ -36,7 +35,7 @@ class WorldProperties(private val sqliteManager: SqliteManager) { getWorldCenter(worldName).thenAccept { if (it != null) { - updateWorldProperties(worldName, center).thenAccept {completableFuture.complete(null) } + updateWorldProperties(worldName, center).thenAccept { completableFuture.complete(null) } } else { insertWorldProperties(worldName, center).thenAccept { completableFuture.complete(null) } } @@ -49,7 +48,8 @@ class WorldProperties(private val sqliteManager: SqliteManager) { */ private fun updateWorldProperties(worldName: String, center: Pair): CompletableFuture { val completableFuture = CompletableFuture() - sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", + sqliteManager.executeStatement( + "UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", hashMapOf( 1 to center.first, 2 to center.second, @@ -67,7 +67,8 @@ class WorldProperties(private val sqliteManager: SqliteManager) { */ private fun insertWorldProperties(worldName: String, center: Pair): CompletableFuture { val completableFuture = CompletableFuture() - sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", + sqliteManager.executeStatement( + "INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", hashMapOf( 1 to worldName, 2 to center.first, diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/ExtendedMarkerSet.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/ExtendedMarkerSet.kt index c5dab40..284f282 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/ExtendedMarkerSet.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/ExtendedMarkerSet.kt @@ -2,7 +2,6 @@ package net.trivernis.chunkmaster.lib.dynmap import org.bukkit.Location import org.dynmap.markers.AreaMarker -import org.dynmap.markers.Marker import org.dynmap.markers.MarkerSet import org.dynmap.markers.PolyLineMarker @@ -46,7 +45,12 @@ class ExtendedMarkerSet(private val markerSet: MarkerSet) { } - fun creUpdatePolyLineMarker(id: String, label: String, edges: List, style: MarkerStyle?): PolyLineMarker? { + fun creUpdatePolyLineMarker( + id: String, + label: String, + edges: List, + style: MarkerStyle? + ): PolyLineMarker? { var marker = markerSet.findPolyLineMarker(id) val xList = edges.map { it.x } val yList = edges.map { it.y } @@ -54,7 +58,16 @@ class ExtendedMarkerSet(private val markerSet: MarkerSet) { if (marker != null) { marker.setCornerLocations(xList.toDoubleArray(), yList.toDoubleArray(), zList.toDoubleArray()) } else { - marker = markerSet.createPolyLineMarker(id, label, false, edges.first().world.name, xList.toDoubleArray(), yList.toDoubleArray(), zList.toDoubleArray(), true) + marker = markerSet.createPolyLineMarker( + id, + label, + false, + edges.first().world.name, + xList.toDoubleArray(), + yList.toDoubleArray(), + zList.toDoubleArray(), + true + ) } if (style != null) { if (style.lineStyle != null) { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/MarkerStyle.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/MarkerStyle.kt index 4260002..9ceb736 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/MarkerStyle.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/dynmap/MarkerStyle.kt @@ -2,4 +2,9 @@ package net.trivernis.chunkmaster.lib.dynmap import org.dynmap.markers.MarkerIcon -data class MarkerStyle(val icon: MarkerIcon?, val lineStyle: LineStyle?, val fillStyle: FillStyle?, val boostFlag: Boolean = false) \ No newline at end of file +data class MarkerStyle( + val icon: MarkerIcon?, + val lineStyle: LineStyle?, + val fillStyle: FillStyle?, + val boostFlag: Boolean = false +) \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt index b5c7cbc..0844272 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkCoordinates.kt @@ -5,7 +5,7 @@ import org.bukkit.World class ChunkCoordinates(val x: Int, val z: Int) { fun getCenterLocation(world: World): Location { - return Location(world, ((x*16) + 8).toDouble(), 1.0, ((z*16) + 8).toDouble()) + return Location(world, ((x * 16) + 8).toDouble(), 1.0, ((z * 16) + 8).toDouble()) } override fun toString(): String { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt index 50c1ada..044a744 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/ChunkUnloader.kt @@ -2,13 +2,10 @@ package net.trivernis.chunkmaster.lib.generation import net.trivernis.chunkmaster.Chunkmaster import org.bukkit.Chunk -import java.lang.Exception import java.util.* -import java.util.concurrent.* import java.util.concurrent.locks.ReentrantReadWriteLock -import kotlin.collections.HashSet -class ChunkUnloader(private val plugin: Chunkmaster): Runnable { +class ChunkUnloader(private val plugin: Chunkmaster) : Runnable { private val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") private val lock = ReentrantReadWriteLock() private var unloadingQueue = Vector(maxLoadedChunks) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt index 8441755..1d650b5 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/DefaultGenerationTask.kt @@ -4,7 +4,7 @@ import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.shapes.Shape import org.bukkit.World -import java.util.concurrent.* +import java.util.concurrent.ArrayBlockingQueue class DefaultGenerationTask( private val plugin: Chunkmaster, diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index dbe64a6..fbc4d41 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -22,11 +22,11 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server return chunkmaster.config.getLong("generation.unloading-period") } private val pauseOnPlayerCount: Int - get () { + get() { return chunkmaster.config.getInt("generation.pause-on-player-count") } private val autostart: Boolean - get () { + get() { return chunkmaster.config.getBoolean("generation.autostart") } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt index efa1005..63344a7 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt @@ -4,7 +4,6 @@ import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.dynmap.* import net.trivernis.chunkmaster.lib.shapes.Shape import org.bukkit.World -import java.util.concurrent.Semaphore import kotlin.math.ceil /** @@ -75,12 +74,14 @@ abstract class GenerationTask( TaskState.CORRECTING -> { this.generateMissing() } - else -> { } + else -> { + } } if (!cancelRun && this.borderReached()) { this.setEndReached() } - } catch (e: InterruptedException){} + } catch (e: InterruptedException) { + } isRunning = false } @@ -108,7 +109,8 @@ abstract class GenerationTask( markerSet?.creUpdatePolyLineMarker( markerAreaId, markerAreaName, - this.shape.getShapeEdgeLocations().map { ChunkCoordinates(it.first, it.second).getCenterLocation(this.world) }, + this.shape.getShapeEdgeLocations() + .map { ChunkCoordinates(it.first, it.second).getCenterLocation(this.world) }, markerAreaStyle ) } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt index 6aa5b64..abbe1b1 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/PendingChunkEntry.kt @@ -1,6 +1,5 @@ package net.trivernis.chunkmaster.lib.generation -import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates import org.bukkit.Chunk import java.util.concurrent.CompletableFuture diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt index 0aca508..684155c 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt @@ -1,6 +1,5 @@ package net.trivernis.chunkmaster.lib.generation.taskentry -import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.lib.generation.GenerationTask class RunningTaskEntry( diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt index 2546b04..4ece528 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt @@ -1,14 +1,10 @@ package net.trivernis.chunkmaster.lib.shapes -import net.trivernis.chunkmaster.lib.dynmap.ExtendedMarkerSet -import net.trivernis.chunkmaster.lib.dynmap.MarkerStyle import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashSet import kotlin.math.PI import kotlin.math.pow -import kotlin.math.sqrt -import kotlin.system.exitProcess class Circle(center: Pair, start: Pair, radius: Int) : Shape(center, start, radius) { private var r = 0 @@ -21,7 +17,7 @@ class Circle(center: Pair, start: Pair, radius: Int) : Shape } override fun total(): Double { - return (PI * radius.toFloat().pow(2)) + return (PI * radius.toFloat().pow(2)) } override fun progress(): Double { @@ -50,16 +46,19 @@ class Circle(center: Pair, start: Pair, radius: Int) : Shape if (endReached()) { return currentPos } + if (count == 0 && currentPos != center) { val tmpCircle = Circle(center, center, radius) while (tmpCircle.next() != currentPos && !tmpCircle.endReached()); this.count = tmpCircle.count this.r = tmpCircle.r } + if (count == 0) { count++ return center } + if (coords.isEmpty()) { r++ val tmpCoords = HashSet>() @@ -70,6 +69,7 @@ class Circle(center: Pair, start: Pair, radius: Int) : Shape coords.addAll(tmpCoords) previousCoords.addAll(tmpCoords) } + count++ val coord = coords.pop() currentPos = Pair(coord.first + center.first, coord.second + center.second) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt index 298b603..f4071ff 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt @@ -1,9 +1,5 @@ package net.trivernis.chunkmaster.lib.shapes -import net.trivernis.chunkmaster.lib.dynmap.ExtendedMarkerSet -import net.trivernis.chunkmaster.lib.dynmap.MarkerStyle -import javax.xml.stream.Location - abstract class Shape(protected val center: Pair, start: Pair, radius: Int) { protected var currentPos = start protected var radius = radius diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt index 9564ca6..931c48d 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt @@ -1,11 +1,9 @@ package net.trivernis.chunkmaster.lib.shapes -import kotlin.math.PI import kotlin.math.abs import kotlin.math.pow -import kotlin.math.sqrt -class Spiral(center: Pair, start: Pair, radius: Int): Shape(center, start, radius) { +class Spiral(center: Pair, start: Pair, radius: Int) : Shape(center, start, radius) { private var direction = 0 override fun endReached(): Boolean { @@ -44,20 +42,20 @@ class Spiral(center: Pair, start: Pair, radius: Int): Shape( count = simSpiral.count } if (count == 1) { // because of the center behaviour - count ++ + count++ return currentPos } if (currentPos == center) { // the center has to be handled exclusively currentPos = Pair(center.first, center.second + 1) - count ++ + count++ return center } else { val distances = getDistances(center, currentPos) if (abs(distances.first) == abs(distances.second)) { - direction = (direction + 1)%5 - } + direction = (direction + 1) % 5 } - when(direction) { + } + when (direction) { 0 -> { currentPos = Pair(currentPos.first + 1, currentPos.second) } @@ -75,7 +73,7 @@ class Spiral(center: Pair, start: Pair, radius: Int): Shape( direction = 0 } } - count ++ + count++ return currentPos } From 441b13ec69fd9dd48828914cd81f2d31e7b8335c Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 27 Jul 2020 21:01:41 +0200 Subject: [PATCH 08/16] Fix/merge conflict (#87) * Version 1.3.0 (#85) * Feature/async chunkmaster (#81) * Change generation to be asynchronous At this stage it will most likely crash the server at a certain point. Pausing and resuming isn't stable. Saving the progress isn't stable as well. Chunks are being unloaded in the main thread by an unloader class. * Switch to native threads - Use thread instead of async tasks - Store pending paper chunks in the database - Interrupt the thread when it should be stopped * Fix insertion of pending chunks Fix an error that is thrown when the sql for inserting pending chunks doesn't have any chunks to insert. * Add task states Add states to differentiate between generating and validating tasks as well as a field in the database to store this information. A task will first generate a world until the required radius or the worldborder is reached. Then it validates that each chunk has been generated. * Add object representation of world_properties table * Add DAO for pending_chunks table * Add DAO for generation_tasks table * Add state updating to periodic save * Fix loading of world properties * Add states to tasks and fix completion handling * Fix progress report and spiral shape * Modify the paper generation task so it works with spigot This change is being made because normal chunk generation doesn't allow chunks to be requested from a different thread. With PaperLib this issue can be solved. * Add workarounds for spigot problems * Fix some blocking issues and update README * Add locking to ChunkUnloader class * Add total chunk count to list command (closes #79) (#82) * Fix shape beign stuck (#83) * Add autostart config parameter (closes #78) (#84) * Add circleci badge to readme * Add codefactor badge * Update Translation to 1.3.0 (#86) * Update Translation to 1.3.0 * Tweak SQL_ERROR word order Co-authored-by: NPBeta --- .../chunkmaster/lib/database/SqliteManager.kt | 376 +++++++++--------- .../generation/taskentry/RunningTaskEntry.kt | 136 +++---- src/main/resources/i18n/zh.i18n.properties | 13 +- 3 files changed, 265 insertions(+), 260 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt index bd16d7b..f2ed67c 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/SqliteManager.kt @@ -1,189 +1,189 @@ -package net.trivernis.chunkmaster.lib.database - -import net.trivernis.chunkmaster.Chunkmaster -import org.apache.commons.lang.exception.ExceptionUtils -import java.sql.Connection -import java.sql.DriverManager -import java.sql.ResultSet - -class SqliteManager(private val chunkmaster: Chunkmaster) { - private val tables = listOf( - Pair( - "generation_tasks", - listOf( - Pair("id", "integer PRIMARY KEY AUTOINCREMENT"), - Pair("center_x", "integer NOT NULL DEFAULT 0"), - Pair("center_z", "integer NOT NULL DEFAULT 0"), - Pair("last_x", "integer NOT NULL DEFAULT 0"), - Pair("last_z", "integer NOT NULL DEFAULT 0"), - Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), - Pair("radius", "integer DEFAULT -1"), - Pair("shape", "text NOT NULL DEFAULT 'square'"), - Pair("state", "text NOT NULL DEFAULT 'GENERATING'") - ) - ), - Pair( - "world_properties", - listOf( - Pair("name", "text PRIMARY KEY"), - Pair("center_x", "integer NOT NULL DEFAULT 0"), - Pair("center_z", "integer NOT NULL DEFAULT 0") - ) - ), - Pair( - "pending_chunks", - listOf( - Pair("id", "integer PRIMARY KEY AUTOINCREMENT"), - Pair("task_id", "integer NOT NULL"), - Pair("chunk_x", "integer NOT NULL"), - Pair("chunk_z", "integer NOT NULL") - ) - ) - ) - private val needUpdate = HashSet>>() - private val needCreation = HashSet() - private var connection: Connection? = null - private var activeTasks = 0 - - val worldProperties = WorldProperties(this) - val pendingChunks = PendingChunks(this) - val generationTasks = GenerationTasks(this) - - /** - * Returns the connection to the database - */ - fun getConnection(): Connection? { - if (this.connection != null) { - return this.connection - } - try { - Class.forName("org.sqlite.JDBC") - this.connection = DriverManager.getConnection( - "jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + - "${chunkmaster.config.getString("database.filename")}" - ) - return this.connection - } catch (e: Exception) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) - chunkmaster.logger.severe(e.message) - } - return null - } - - /** - * Checks for and performs an update - */ - fun init() { - this.checkUpdate() - this.performUpdate() - } - - /** - * Checks which tables need an update or creation. - */ - private fun checkUpdate() { - val meta = getConnection()!!.metaData - - for (table in tables) { - val resTables = meta.getTables(null, null, table.first, null) - - if (resTables.next()) { // table exists - for (column in table.second) { - val resColumn = meta.getColumns(null, null, table.first, column.first) - if (!resColumn.next()) { - needUpdate.add(Pair(table.first, column)) - } - resColumn.close() - } - } else { - needCreation.add(table.first) - } - resTables.close() - } - } - - /** - * Executes a sql statement on the database. - */ - fun executeStatement(sql: String, values: HashMap, callback: ((ResultSet?) -> Unit)?) { - val connection = getConnection() - activeTasks++ - if (connection != null) { - try { - //println("'$sql' with values $values") - val statement = connection.prepareStatement(sql) - for (parameterValue in values) { - statement.setObject(parameterValue.key, parameterValue.value) - } - statement.execute() - val res: ResultSet? = statement.resultSet - if (callback != null) { - callback(res) - } - statement.close() - } catch (e: Exception) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("SQL_ERROR", e.toString())) - chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) - } finally { - activeTasks-- - if (activeTasks == 0) { - connection.close() - this.connection = null - } - } - } else { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("NO_DATABASE_CONNECTION")) - } - } - - /** - * Creates or updates tables that needed an update. - */ - private fun performUpdate() { - for (table in needCreation) { - try { - var tableDef = "CREATE TABLE IF NOT EXISTS $table (" - - for (column in tables.find { it.first == table }!!.second) { - tableDef += "${column.first} ${column.second}," - } - tableDef = tableDef.substringBeforeLast(",") + ");" - chunkmaster.logger.finest( - chunkmaster.langManager.getLocalized( - "CREATE_TABLE_DEFINITION", - table, - tableDef - ) - ) - executeStatement(tableDef, HashMap(), null) - } catch (e: Exception) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TABLE_CREATE_ERROR", table)) - chunkmaster.logger.severe(e.message) - chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) - } - } - for (table in needUpdate) { - val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}" - try { - executeStatement(updateSql, HashMap(), null) - chunkmaster.logger.finest( - chunkmaster.langManager.getLocalized( - "UPDATE_TABLE_DEFINITION", - table.first, - updateSql - ) - ) - } catch (e: Exception) { - chunkmaster.logger.severe( - chunkmaster.langManager.getLocalized( - "UPDATE_TABLE_FAILED", - table.first, - updateSql - ) - ) - chunkmaster.logger.severe(e.message) - chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) - } - } - } +package net.trivernis.chunkmaster.lib.database + +import net.trivernis.chunkmaster.Chunkmaster +import org.apache.commons.lang.exception.ExceptionUtils +import java.sql.Connection +import java.sql.DriverManager +import java.sql.ResultSet + +class SqliteManager(private val chunkmaster: Chunkmaster) { + private val tables = listOf( + Pair( + "generation_tasks", + listOf( + Pair("id", "integer PRIMARY KEY AUTOINCREMENT"), + Pair("center_x", "integer NOT NULL DEFAULT 0"), + Pair("center_z", "integer NOT NULL DEFAULT 0"), + Pair("last_x", "integer NOT NULL DEFAULT 0"), + Pair("last_z", "integer NOT NULL DEFAULT 0"), + Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), + Pair("radius", "integer DEFAULT -1"), + Pair("shape", "text NOT NULL DEFAULT 'square'"), + Pair("state", "text NOT NULL DEFAULT 'GENERATING'") + ) + ), + Pair( + "world_properties", + listOf( + Pair("name", "text PRIMARY KEY"), + Pair("center_x", "integer NOT NULL DEFAULT 0"), + Pair("center_z", "integer NOT NULL DEFAULT 0") + ) + ), + Pair( + "pending_chunks", + listOf( + Pair("id", "integer PRIMARY KEY AUTOINCREMENT"), + Pair("task_id", "integer NOT NULL"), + Pair("chunk_x", "integer NOT NULL"), + Pair("chunk_z", "integer NOT NULL") + ) + ) + ) + private val needUpdate = HashSet>>() + private val needCreation = HashSet() + private var connection: Connection? = null + private var activeTasks = 0 + + val worldProperties = WorldProperties(this) + val pendingChunks = PendingChunks(this) + val generationTasks = GenerationTasks(this) + + /** + * Returns the connection to the database + */ + fun getConnection(): Connection? { + if (this.connection != null) { + return this.connection + } + try { + Class.forName("org.sqlite.JDBC") + this.connection = DriverManager.getConnection( + "jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + + "${chunkmaster.config.getString("database.filename")}" + ) + return this.connection + } catch (e: Exception) { + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) + chunkmaster.logger.severe(e.message) + } + return null + } + + /** + * Checks for and performs an update + */ + fun init() { + this.checkUpdate() + this.performUpdate() + } + + /** + * Checks which tables need an update or creation. + */ + private fun checkUpdate() { + val meta = getConnection()!!.metaData + + for (table in tables) { + val resTables = meta.getTables(null, null, table.first, null) + + if (resTables.next()) { // table exists + for (column in table.second) { + val resColumn = meta.getColumns(null, null, table.first, column.first) + if (!resColumn.next()) { + needUpdate.add(Pair(table.first, column)) + } + resColumn.close() + } + } else { + needCreation.add(table.first) + } + resTables.close() + } + } + + /** + * Executes a sql statement on the database. + */ + fun executeStatement(sql: String, values: HashMap, callback: ((ResultSet?) -> Unit)?) { + val connection = getConnection() + activeTasks++ + if (connection != null) { + try { + //println("'$sql' with values $values") + val statement = connection.prepareStatement(sql) + for (parameterValue in values) { + statement.setObject(parameterValue.key, parameterValue.value) + } + statement.execute() + val res: ResultSet? = statement.resultSet + if (callback != null) { + callback(res) + } + statement.close() + } catch (e: Exception) { + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("SQL_ERROR", e.toString())) + chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) + } finally { + activeTasks-- + if (activeTasks == 0) { + connection.close() + this.connection = null + } + } + } else { + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("NO_DATABASE_CONNECTION")) + } + } + + /** + * Creates or updates tables that needed an update. + */ + private fun performUpdate() { + for (table in needCreation) { + try { + var tableDef = "CREATE TABLE IF NOT EXISTS $table (" + + for (column in tables.find { it.first == table }!!.second) { + tableDef += "${column.first} ${column.second}," + } + tableDef = tableDef.substringBeforeLast(",") + ");" + chunkmaster.logger.finest( + chunkmaster.langManager.getLocalized( + "CREATE_TABLE_DEFINITION", + table, + tableDef + ) + ) + executeStatement(tableDef, HashMap(), null) + } catch (e: Exception) { + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TABLE_CREATE_ERROR", table)) + chunkmaster.logger.severe(e.message) + chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) + } + } + for (table in needUpdate) { + val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}" + try { + executeStatement(updateSql, HashMap(), null) + chunkmaster.logger.finest( + chunkmaster.langManager.getLocalized( + "UPDATE_TABLE_DEFINITION", + table.first, + updateSql + ) + ) + } catch (e: Exception) { + chunkmaster.logger.severe( + chunkmaster.langManager.getLocalized( + "UPDATE_TABLE_FAILED", + table.first, + updateSql + ) + ) + chunkmaster.logger.severe(e.message) + chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) + } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt index 684155c..6db86d3 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt @@ -1,69 +1,69 @@ -package net.trivernis.chunkmaster.lib.generation.taskentry - -import net.trivernis.chunkmaster.lib.generation.GenerationTask - -class RunningTaskEntry( - override val id: Int, - override val generationTask: GenerationTask -) : TaskEntry { - - private var lastProgress: Pair? = null - private var lastChunkCount: Pair? = null - private var thread = Thread(generationTask) - - /** - * Returns the generation Speed - */ - val generationSpeed: Pair - get() { - var generationSpeed: Double? = null - var chunkGenerationSpeed: Double? = null - if (lastProgress != null) { - val progressDiff = generationTask.shape.progress() - lastProgress!!.second - val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000 - generationSpeed = progressDiff / timeDiff - } - if (lastChunkCount != null) { - val chunkDiff = generationTask.count - lastChunkCount!!.second - val timeDiff = (System.currentTimeMillis() - lastChunkCount!!.first).toDouble() / 1000 - chunkGenerationSpeed = chunkDiff / timeDiff - } - lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) - lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) - return Pair(generationSpeed, chunkGenerationSpeed) - } - - init { - lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) - lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) - } - - fun start() { - thread.start() - } - - fun cancel(timeout: Long): Boolean { - if (generationTask.isRunning) { - generationTask.cancel() - thread.interrupt() - } - return try { - joinThread(timeout) - } catch (e: InterruptedException) { - true - } - } - - private fun joinThread(timeout: Long): Boolean { - var threadStopped = false - - for (i in 0..100) { - if (!thread.isAlive || !generationTask.isRunning) { - threadStopped = true - break - } - Thread.sleep(timeout / 100) - } - return threadStopped - } +package net.trivernis.chunkmaster.lib.generation.taskentry + +import net.trivernis.chunkmaster.lib.generation.GenerationTask + +class RunningTaskEntry( + override val id: Int, + override val generationTask: GenerationTask +) : TaskEntry { + + private var lastProgress: Pair? = null + private var lastChunkCount: Pair? = null + private var thread = Thread(generationTask) + + /** + * Returns the generation Speed + */ + val generationSpeed: Pair + get() { + var generationSpeed: Double? = null + var chunkGenerationSpeed: Double? = null + if (lastProgress != null) { + val progressDiff = generationTask.shape.progress() - lastProgress!!.second + val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000 + generationSpeed = progressDiff / timeDiff + } + if (lastChunkCount != null) { + val chunkDiff = generationTask.count - lastChunkCount!!.second + val timeDiff = (System.currentTimeMillis() - lastChunkCount!!.first).toDouble() / 1000 + chunkGenerationSpeed = chunkDiff / timeDiff + } + lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) + lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) + return Pair(generationSpeed, chunkGenerationSpeed) + } + + init { + lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) + lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) + } + + fun start() { + thread.start() + } + + fun cancel(timeout: Long): Boolean { + if (generationTask.isRunning) { + generationTask.cancel() + thread.interrupt() + } + return try { + joinThread(timeout) + } catch (e: InterruptedException) { + true + } + } + + private fun joinThread(timeout: Long): Boolean { + var threadStopped = false + + for (i in 0..100) { + if (!thread.isAlive || !generationTask.isRunning) { + threadStopped = true + break + } + Thread.sleep(timeout / 100) + } + return threadStopped + } } \ No newline at end of file diff --git a/src/main/resources/i18n/zh.i18n.properties b/src/main/resources/i18n/zh.i18n.properties index 18a6d97..17fa4a0 100644 --- a/src/main/resources/i18n/zh.i18n.properties +++ b/src/main/resources/i18n/zh.i18n.properties @@ -1,11 +1,12 @@ RESUME_FOR_WORLD = 正在恢复执行 '%s' 世界的区块生成任务... TASK_FINISHED = 任务 #%d 在生成 %d 个区块后完成. -TASK_CANCELED = 已取消任务 #%s. +TASK_CANCELLED = 已取消任务 #%s. TASK_LOAD_FAILED = §c加载任务 #%d 失败. TASK_LOAD_SUCCESS = %d 个已保存的任务加载完成. TASK_NOT_FOUND = §c任务 %s 未找到! CREATE_DELAYED_LOAD = 正在创建延迟执行的区块生成任务... -TASK_PERIODIC_REPORT = 任务 #%d 正在 '%s' 世界执行. 进度: %d 区块 %s %s, 速度: %.1f 区块 / 秒, 最新生成的区块: %d, %d +TASK_PERIODIC_REPORT = 任务 #%d 正在 '%s' 世界执行. 状态: %s. 进度: %d 区块 %s %s, 速度: %.1f 区块 / 秒, 最新生成的区块: %d, %d +TASK_PERIODIC_REPORT_CORRECTING = 任务 #%d 正在为世界 '%s' 生成缺失的区块. 进度: %d 区块 %s TASK_SAVE_FAILED = §c保存任务时发生错误: %s WORLD_NAME_REQUIRED = §c你需要提供世界名称! @@ -18,7 +19,7 @@ TASK_ID_REQUIRED = §c你需要提供任务 ID! INVALID_ARGUMENT = §c在 %s: %s 存在无效的变量! PAUSED_TASKS_HEADER = 当前暂停的区块生成任务 -TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d 区块 %s§r +TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s 区块 %s§r RUNNING_TASKS_HEADER = 当前运行的区块生成任务 NO_GENERATION_TASKS = 无区块生成任务. @@ -44,7 +45,7 @@ DB_INIT_FINISHED = 数据库初始化完成. DB_INIT_EROR = 初始化数据库时发生错误: %s. DATABASE_CONNECTION_ERROR = §c连接数据库失败! -SQL_ERROR = §cSQL %s 发生错误! +SQL_ERROR = §cSQL 发生错误: %s ! NO_DATABASE_CONNECTION = §c无法执行 SQL 语句: 无数据库连接. CREATE_TABLE_DEFINITION = 已创建表 %s ,定义 %s. TABLE_CREATE_ERROR = §c创建表 %s 失败. @@ -73,3 +74,7 @@ STATS_WORLD_NAME = §l%s§r STATS_ENTITY_COUNT = - §2%d§r 实体 STATS_LOADED_CHUNKS = - §2%d§r 已载入区块 STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r 被 Chunk Master 载入的区块 + +SAVING_CHUNKS = 正在保存 %d 已载入的区块... +CANCEL_FAIL = 取消任务 #%d 操作超时! +NO_AUTOSTART = 自动启动被设置为 §2关闭§r. 正在暂停... From a91e26652dcfcd8616987fb19ddcc2090cc15ed3 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 27 Jul 2020 21:16:04 +0200 Subject: [PATCH 09/16] Update version to 1.3.1 --- build.gradle | 2 +- src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index eb3994f..592a5c2 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ idea { } group "net.trivernis" -version "1.3.0" +version "1.3.1" sourceCompatibility = 1.8 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 640c698..32868aa 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ main: net.trivernis.chunkmaster.Chunkmaster name: Chunkmaster -version: '1.3.0' +version: '1.3.1' description: Automated world pregeneration. author: Trivernis website: trivernis.net From 733996e6a95b13528dd284ab79f5df12902ed3c6 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 27 Jul 2020 21:18:03 +0200 Subject: [PATCH 10/16] Remove empty body of PausedTaskEntry --- .../chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt index 57f2405..c1808ed 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/PausedTaskEntry.kt @@ -5,5 +5,4 @@ import net.trivernis.chunkmaster.lib.generation.GenerationTask class PausedTaskEntry( override val id: Int, override val generationTask: GenerationTask -) : TaskEntry { -} \ No newline at end of file +) : TaskEntry \ No newline at end of file From f0ea063026630002feb2fda7818eb217f4f7cefa Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 11 Aug 2020 09:09:19 +0200 Subject: [PATCH 11/16] Fix setCenter command throwing an error when only one argument is provided - Fixes #94 --- build.gradle | 2 +- .../net/trivernis/chunkmaster/commands/CmdSetCenter.kt | 5 +++++ src/main/resources/plugin.yml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 592a5c2..24a1ac7 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ idea { } group "net.trivernis" -version "1.3.1" +version "1.3.2" sourceCompatibility = 1.8 diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt index 863c577..20cb4ba 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt @@ -36,6 +36,11 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand { centerX = sender.location.chunk.x centerZ = sender.location.chunk.z } + args.size == 1 -> { + world = args[0] + centerX = sender.location.chunk.x + centerZ = sender.location.chunk.z + } args.size == 2 -> { world = sender.world.name if (args[0].toIntOrNull() == null || args[1].toIntOrNull() == null) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 32868aa..41222da 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ main: net.trivernis.chunkmaster.Chunkmaster name: Chunkmaster -version: '1.3.1' +version: '1.3.2' description: Automated world pregeneration. author: Trivernis website: trivernis.net From 65e3e090bb006dea937fe519e94db6944a9d6821 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 11 Aug 2020 09:54:25 +0200 Subject: [PATCH 12/16] Fix insertion of pending chunks using too many parameters - Fixes #91 --- .../chunkmaster/lib/database/PendingChunks.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt index 9b79dfd..9c61671 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt @@ -2,6 +2,7 @@ package net.trivernis.chunkmaster.lib.database import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates import java.util.concurrent.CompletableFuture +import kotlin.math.ceil class PendingChunks(private val sqliteManager: SqliteManager) { /** @@ -30,10 +31,25 @@ class PendingChunks(private val sqliteManager: SqliteManager) { return completableFuture } + fun addPendingChunks(taskId: Int, pendingChunks: List): CompletableFuture { + val futures = ArrayList>() + val statementCount = ceil(pendingChunks.size.toDouble() / 100.0).toInt() + + for (i in 0 until statementCount) { + futures.add(insertPendingChunks(taskId, pendingChunks.subList(i * 100, ((i * 100) + 100).coerceAtMost(pendingChunks.size)))) + } + + if (futures.size > 0) { + return CompletableFuture.allOf(*futures.toTypedArray()) + } else { + return CompletableFuture.supplyAsync { null } + } + } + /** * Adds pending chunks for a taskid */ - fun addPendingChunks(taskId: Int, pendingChunks: List): CompletableFuture { + private fun insertPendingChunks(taskId: Int, pendingChunks: List): CompletableFuture { val completableFuture = CompletableFuture() if (pendingChunks.isEmpty()) { completableFuture.complete(null) From 9b618eccc246524048b5c6b5a079702edea26040 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 11 Aug 2020 09:57:47 +0200 Subject: [PATCH 13/16] Fix dynmap version being null when dynmap doesn't support the minecraft versio - Fixes #69 --- .../net/trivernis/chunkmaster/Chunkmaster.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index 448b873..442e549 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -9,6 +9,8 @@ import org.bstats.bukkit.Metrics import org.bukkit.plugin.java.JavaPlugin import org.bukkit.scheduler.BukkitTask import org.dynmap.DynmapAPI +import java.lang.IllegalStateException +import java.lang.NullPointerException class Chunkmaster : JavaPlugin() { lateinit var sqliteManager: SqliteManager @@ -103,11 +105,15 @@ class Chunkmaster : JavaPlugin() { } private fun getDynmap(): DynmapAPI? { - val dynmap = server.pluginManager.getPlugin("dynmap") - return if (dynmap != null && dynmap is DynmapAPI) { - logger.info(langManager.getLocalized("PLUGIN_DETECTED", "dynmap", dynmap.dynmapVersion)) - dynmap - } else { + return try { + val dynmap = server.pluginManager.getPlugin("dynmap") + if (dynmap != null && dynmap is DynmapAPI) { + logger.info(langManager.getLocalized("PLUGIN_DETECTED", "dynmap", dynmap.dynmapVersion)) + dynmap + } else { + null + } + } catch (e: IllegalStateException) { null } } From 3cfc49baa851e3123c2c75e9aae1eb5f94c0d08c Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 11 Aug 2020 10:11:37 +0200 Subject: [PATCH 14/16] Fix player count togglable pausing - Fixes #89 --- .../kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt index 2ec66fb..4c28eb3 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt @@ -41,8 +41,10 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server if (chunkmaster.generationManager.tasks.isNotEmpty()) { chunkmaster.logger.info(chunkmaster.langManager.getLocalized("PAUSE_PLAYER_JOIN")) } - playerPaused = chunkmaster.generationManager.paused - chunkmaster.generationManager.pauseAll() + if (!chunkmaster.generationManager.paused) { + playerPaused = chunkmaster.generationManager.paused + chunkmaster.generationManager.pauseAll() + } } } } \ No newline at end of file From 6df01dd13727841ef782dbcfe1aa92efb4663ddd Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 11 Aug 2020 10:42:42 +0200 Subject: [PATCH 15/16] Add progress report to tasks generating to the worldborder - Closes #90 --- .../kotlin/net/trivernis/chunkmaster/commands/CmdList.kt | 9 ++++----- .../trivernis/chunkmaster/lib/database/PendingChunks.kt | 6 ++++-- .../chunkmaster/lib/generation/GenerationManager.kt | 8 +++++--- .../lib/generation/taskentry/RunningTaskEntry.kt | 7 ++++--- .../net/trivernis/chunkmaster/lib/shapes/Circle.kt | 8 ++++++-- .../kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt | 2 +- .../net/trivernis/chunkmaster/lib/shapes/Spiral.kt | 8 ++++++-- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt index df5b6ed..370d21b 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt @@ -6,6 +6,7 @@ import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry import org.bukkit.command.Command import org.bukkit.command.CommandSender import kotlin.math.ceil +import kotlin.math.pow class CmdList(private val chunkmaster: Chunkmaster) : Subcommand { override val name = "list" @@ -49,14 +50,12 @@ class CmdList(private val chunkmaster: Chunkmaster) : Subcommand { */ private fun getGenerationEntry(task: TaskEntry): String { val genTask = task.generationTask - val percentage = if (genTask.radius > 0) - " (%.1f".format(genTask.shape.progress() * 100) + "%)." - else - "" + val progress = genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null) + val percentage = " (%.1f".format(progress * 100) + "%)." val count = if (genTask.radius > 0) { "${genTask.count} / ${ceil(genTask.shape.total()).toInt()}" } else { - genTask.count.toString() + "${genTask.count} / worldborder ${(genTask.world.worldBorder.size / 16).pow(2).toInt()}" } return "\n" + chunkmaster.langManager.getLocalized( "TASKS_ENTRY", diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt index 9c61671..614ae48 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/database/PendingChunks.kt @@ -5,6 +5,8 @@ import java.util.concurrent.CompletableFuture import kotlin.math.ceil class PendingChunks(private val sqliteManager: SqliteManager) { + + private val insertionCount = 300 /** * Returns a list of pending chunks for a taskId */ @@ -33,10 +35,10 @@ class PendingChunks(private val sqliteManager: SqliteManager) { fun addPendingChunks(taskId: Int, pendingChunks: List): CompletableFuture { val futures = ArrayList>() - val statementCount = ceil(pendingChunks.size.toDouble() / 100.0).toInt() + val statementCount = ceil(pendingChunks.size.toDouble() / insertionCount).toInt() for (i in 0 until statementCount) { - futures.add(insertPendingChunks(taskId, pendingChunks.subList(i * 100, ((i * 100) + 100).coerceAtMost(pendingChunks.size)))) + futures.add(insertPendingChunks(taskId, pendingChunks.subList(i * insertionCount, ((i * insertionCount) + insertionCount).coerceAtMost(pendingChunks.size)))) } if (futures.size > 0) { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index fbc4d41..2b9bc8c 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -292,10 +292,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server private fun reportGenerationProgress(task: RunningTaskEntry) { val genTask = task.generationTask val (speed, chunkSpeed) = task.generationSpeed - val percentage = if (genTask.radius > 0) "(${"%.2f".format(genTask.shape.progress() * 100)}%)" else "" + val progress = genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null) + val percentage = + "(${"%.2f".format(progress * 100)}%)" - val eta = if (genTask.radius > 0 && speed!! > 0) { - val remaining = 1 - genTask.shape.progress() + val eta = if (speed!! > 0) { + val remaining = 1 - progress val etaSeconds = remaining / speed val hours: Int = (etaSeconds / 3600).toInt() val minutes: Int = ((etaSeconds % 3600) / 60).toInt() diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt index 6db86d3..3594bc0 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/taskentry/RunningTaskEntry.kt @@ -18,8 +18,9 @@ class RunningTaskEntry( get() { var generationSpeed: Double? = null var chunkGenerationSpeed: Double? = null + val progress = generationTask.shape.progress(if (generationTask.radius < 0) (generationTask.world.worldBorder.size / 32).toInt() else null) if (lastProgress != null) { - val progressDiff = generationTask.shape.progress() - lastProgress!!.second + val progressDiff = progress - lastProgress!!.second val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000 generationSpeed = progressDiff / timeDiff } @@ -28,13 +29,13 @@ class RunningTaskEntry( val timeDiff = (System.currentTimeMillis() - lastChunkCount!!.first).toDouble() / 1000 chunkGenerationSpeed = chunkDiff / timeDiff } - lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) + lastProgress = Pair(System.currentTimeMillis(), progress) lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) return Pair(generationSpeed, chunkGenerationSpeed) } init { - lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress()) + lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress(null)) lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count) } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt index 4ece528..4a6ca57 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Circle.kt @@ -20,9 +20,13 @@ class Circle(center: Pair, start: Pair, radius: Int) : Shape return (PI * radius.toFloat().pow(2)) } - override fun progress(): Double { + override fun progress(maxRadius: Int?): Double { // TODO: Radius inner progress - return (count / (PI * radius.toFloat().pow(2))).coerceAtMost(1.0) + return if (maxRadius != null) { + (count / (PI * maxRadius.toFloat().pow(2))).coerceAtMost(1.0) + } else { + (count / (PI * radius.toFloat().pow(2))).coerceAtMost(1.0) + } } override fun currentRadius(): Int { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt index f4071ff..b79b750 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Shape.kt @@ -19,7 +19,7 @@ abstract class Shape(protected val center: Pair, start: Pair /** * Returns the progress of the shape */ - abstract fun progress(): Double + abstract fun progress(maxRadius: Int?): Double /** * The total number of chunks to generate diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt index 931c48d..15b5f35 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/shapes/Spiral.kt @@ -18,8 +18,12 @@ class Spiral(center: Pair, start: Pair, radius: Int) : Shape return (radius * 2).toDouble().pow(2) } - override fun progress(): Double { - return (count / (radius * 2).toDouble().pow(2)).coerceAtMost(1.0) + override fun progress(maxRadius: Int?): Double { + return if (maxRadius != null) { + (count / (maxRadius * 2).toDouble().pow(2)).coerceAtMost(1.0) + } else { + (count / (radius * 2).toDouble().pow(2)).coerceAtMost(1.0) + } } override fun currentRadius(): Int { From 4947731718ced2a9004e5f7167abfb21ae9f9bb0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 11 Aug 2020 11:01:06 +0200 Subject: [PATCH 16/16] Remove inaccurate chunk count from worldborder chm list --- src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt index 370d21b..067198e 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdList.kt @@ -55,7 +55,7 @@ class CmdList(private val chunkmaster: Chunkmaster) : Subcommand { val count = if (genTask.radius > 0) { "${genTask.count} / ${ceil(genTask.shape.total()).toInt()}" } else { - "${genTask.count} / worldborder ${(genTask.world.worldBorder.size / 16).pow(2).toInt()}" + "${genTask.count} / worldborder" } return "\n" + chunkmaster.langManager.getLocalized( "TASKS_ENTRY",