From ecf7f10f52dcc513246f54e05577f6c93a6e05ce Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 17 Sep 2019 11:23:42 +0200 Subject: [PATCH] Fixes and SQL auto Update - fixed removegentask command - added autostop of generation task after a certain amout of chunks or at the world border - added SqlUpdateManager to check if tables need to be created or updated --- .../net/trivernis/chunkmaster/Chunkmaster.kt | 20 ++--- .../chunkmaster/commands/CommandGenerate.kt | 22 ++++- .../commands/CommandRemoveGenTask.kt | 5 +- .../chunkmaster/lib/GenerationManager.kt | 22 +++-- .../chunkmaster/lib/GenerationTask.kt | 18 ++++- .../chunkmaster/lib/SqlUpdateManager.kt | 80 +++++++++++++++++++ src/main/resources/plugin.yml | 8 +- 7 files changed, 142 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index 982e103..ef8a8c8 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -5,6 +5,7 @@ import net.trivernis.chunkmaster.commands.CommandListGenTasks import net.trivernis.chunkmaster.commands.CommandRemoveGenTask import net.trivernis.chunkmaster.lib.GenerationManager import net.trivernis.chunkmaster.lib.Spiral +import net.trivernis.chunkmaster.lib.SqlUpdateManager import org.bukkit.plugin.java.JavaPlugin import org.bukkit.scheduler.BukkitTask import java.lang.Exception @@ -66,20 +67,11 @@ class Chunkmaster: JavaPlugin() { Class.forName("org.sqlite.JDBC") sqliteConnection = DriverManager.getConnection("jdbc:sqlite:${dataFolder.absolutePath}/chunkmaster.db") logger.info("Database connection established.") - val createTableStatement = sqliteConnection.prepareStatement(""" - CREATE TABLE IF NOT EXISTS generation_tasks ( - id integer PRIMARY KEY AUTOINCREMENT, - center_x integer NOT NULL DEFAULT 0, - center_z integer NOT NULL DEFAULT 0, - last_x integer NOT NULL DEFAULT 0, - last_z integer NOT NULL DEFAULT 0, - world text UNIQUE NOT NULL DEFAULT 'world', - autostart integer DEFAULT 1 - ); - """.trimIndent()) - createTableStatement.execute() - createTableStatement.close() - logger.info("Database tables created.") + + val updateManager = SqlUpdateManager(sqliteConnection, this) + updateManager.checkUpdate() + updateManager.performUpdate() + logger.info("Database fully initialized.") } catch(e: Exception) { logger.warning("Failed to init database: ${e.message}") } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandGenerate.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandGenerate.kt index eabbad5..512572c 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandGenerate.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandGenerate.kt @@ -13,18 +13,32 @@ class CommandGenerate(private val chunkmaster: Chunkmaster): CommandExecutor { override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { if (sender is Player) { return if (command.testPermission(sender)) { - chunkmaster.generationManager.addTask(sender.world) - sender.sendMessage("Added generation task for world \"${sender.world.name}\"") + var stopAfter = -1 + + if (args.isNotEmpty() && args[0].toIntOrNull() != null) { + stopAfter = args[0].toInt() + } + chunkmaster.generationManager.addTask(sender.world, stopAfter) + sender.sendMessage("Added generation task for world \"${sender.world.name}\"" + + "Stopping after $stopAfter chunks (-1 for world border).") true } else { sender.sendMessage("You do not have permission.") true } } else { - return if (args.size == 1) { + return if (args.size >= 1) { val world = sender.server.getWorld(args[0]) if (world != null) { - chunkmaster.generationManager.addTask(world) + var stopAfter = -1 + + if (args.size >=2 && args[1].toIntOrNull() != null) { + stopAfter = args[1].toInt() + } + chunkmaster.generationManager.addTask(world, stopAfter) + + sender.sendMessage("Added generation task for world \"${world.name}\"" + + "Stopping after $stopAfter chunks (-1 for world border).") true } else { sender.sendMessage("World \"${args[0]}\" not found") diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt index f9dd25b..90672b8 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt @@ -6,9 +6,12 @@ import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender class CommandRemoveGenTask(private val chunkmaster: Chunkmaster): CommandExecutor { + /** + * Stops the specified generation task and removes it from the autostart. + */ override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { return if (command.testPermission(sender) && args.size == 1) { - chunkmaster.generationManager.removeTask(args[1].toInt()) + chunkmaster.generationManager.removeTask(args[0].toInt()) sender.sendMessage("Task ${args[1].toInt()} canceled.") true } else { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt index 901f3da..67f7352 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt @@ -12,24 +12,24 @@ import java.lang.NullPointerException class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) { val tasks: HashSet = HashSet() - get() = field /** * Adds a generation task */ - fun addTask(world: World): Int { + fun addTask(world: World, stopAfter: Int = -1): Int { val centerChunk = world.getChunkAt(world.spawnLocation) val generationTask = GenerationTask(chunkmaster, world, centerChunk, centerChunk) val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, 2) val insertStatement = chunkmaster.sqliteConnection.prepareStatement(""" - INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world) - values (?, ?, ?, ?, ?) + INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after) + values (?, ?, ?, ?, ?, ?) """) insertStatement.setInt(1, centerChunk.x) insertStatement.setInt(2, centerChunk.z) insertStatement.setInt(3, centerChunk.x) insertStatement.setInt(4, centerChunk.z) insertStatement.setString(5, world.name) + insertStatement.setInt(6, stopAfter) insertStatement.execute() val getIdStatement = chunkmaster.sqliteConnection.prepareStatement(""" SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1 @@ -47,9 +47,9 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server /** * Resumes a generation task */ - private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int) { + private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int, stopAfter: Int = -1) { chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"") - val generationTask = GenerationTask(chunkmaster, world, center, last) + val generationTask = GenerationTask(chunkmaster, world, center, last, stopAfter) val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, 2) tasks.add(TaskEntry(id, task, generationTask)) } @@ -66,7 +66,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server tasks.remove(taskEntry) } val setAutostart = chunkmaster.sqliteConnection.prepareStatement(""" - UPDATE TABLE generation_tasks SET autostart = 0 WHERE id = ? + DELETE FROM generation_tasks WHERE id = ? """.trimIndent()) setAutostart.setInt(1, id) setAutostart.execute() @@ -117,8 +117,9 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val world = server.getWorld(res.getString("world")) val center = world!!.getChunkAt(res.getInt("center_x"), res.getInt("center_z")) val last = world.getChunkAt(res.getInt("last_x"), res.getInt("last_z")) + val stopAfter = res.getInt("stop_after") if (this.tasks.find {it.id == id} == null) { - resumeTask(world, center, last, id) + resumeTask(world, center, last, id, stopAfter) } } } catch (error: NullPointerException) { @@ -150,6 +151,11 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server updateStatement.setInt(3, task.id) updateStatement.execute() updateStatement.close() + + if (genTask.endReached) { // remove the task if it is finished after the progress has been saved + server.consoleSender.sendMessage("Task #${task.id} finished after ${genTask.count} chunks.") + removeTask(task.id) + } } catch (error: Exception) { server.consoleSender.sendMessage("Exception when saving task progress ${error.message}") } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt index 1908f84..a923bbd 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt @@ -7,17 +7,22 @@ import org.bukkit.scheduler.BukkitRunnable import org.bukkit.scheduler.BukkitTask class GenerationTask(private val plugin: Chunkmaster, val world: World, - private val centerChunk: Chunk, val startChunk: Chunk): Runnable { + private val centerChunk: Chunk, private val startChunk: Chunk, + val stopAfter: Int = -1): Runnable { private val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) private val loadedChunks: HashSet = HashSet() + var count = 0 - get() = field + private set var lastChunk: Chunk = startChunk - get() = field + private set + var endReached: Boolean = false + private set /** * Runs the generation task. Every Iteration the next chunk will be generated if * it hasn't been generated already. + * After 10 chunks have been generated, they will all be unloaded and saved. */ override fun run() { if (plugin.mspt < 500L) { // pause when tps < 2 @@ -31,12 +36,17 @@ class GenerationTask(private val plugin: Chunkmaster, val world: World, val nextChunkCoords = spiral.next() val chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second) + if (!world.worldBorder.isInside(chunk.getBlock(8, 0, 8).location) || (stopAfter in 1..count)) { + endReached = true + return + } + if (!world.isChunkGenerated(chunk.x, chunk.z)) { chunk.load(true) loadedChunks.add(chunk) } lastChunk = chunk - count++ + count = spiral.count // set the count to the more accurate spiral count } } } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt new file mode 100644 index 0000000..5c4fdf2 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt @@ -0,0 +1,80 @@ +package net.trivernis.chunkmaster.lib + +import net.trivernis.chunkmaster.Chunkmaster +import java.lang.Exception +import java.sql.Connection + +class SqlUpdateManager(private val connnection: Connection, 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("stop_after", "integer DEFAULT -1"), + Pair("autostart", "integer DEFAULT 1") + ) + ) + ) + private val needUpdate = HashSet>>() + private val needCreation = HashSet() + + /** + * Checks which tables need an update or creation. + */ + fun checkUpdate() { + val meta = connnection.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)) + } + } + } else { + needCreation.add(table.first) + } + } + } + + /** + * Creates or updates tables that needed an update. + */ + 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.info("Creating table $table with definition $tableDef") + val stmt = connnection.prepareStatement(tableDef) + stmt.execute() + stmt.close() + } catch (err: Exception) { + chunkmaster.logger.severe("Error creating table $table."); + } + } + for (table in needUpdate) { + val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}" + try { + val stmt = connnection.prepareStatement(updateSql) + stmt.execute() + stmt.close() + chunkmaster.logger.info("Updated table ${table.first} with sql $updateSql") + } catch (e: Exception) { + chunkmaster.logger.severe("Failed to update table ${table.first} with sql $updateSql") + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c8bb5b6..6b4c685 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -7,9 +7,13 @@ website: trivernis.net api-version: '1.14' commands: generate: - description: Generates chunks starting from the world spawnpoint + description: > + Generates chunks starting from the world spawnpoint. + StopAfter indicates the number of chunks to generate + (-1 meaning to stop at the world border). If a player + executes the command, the world argument will be ignored. permission: chumkmaster.generate - usage: /generate [world] + usage: /generate [world] [stopAfter] listgentasks: description: Lists all generation tasks permission: chunkmaster.listgentasks