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 <shanhang007@gmail.com>pull/88/head
parent
aaa614f651
commit
441b13ec69
@ -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<Pair<String, Pair<String, String>>>()
|
||||
private val needCreation = HashSet<String>()
|
||||
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<Int, Any>, 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<Pair<String, Pair<String, String>>>()
|
||||
private val needCreation = HashSet<String>()
|
||||
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<Int, Any>, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Long, Double>? = null
|
||||
private var lastChunkCount: Pair<Long, Int>? = null
|
||||
private var thread = Thread(generationTask)
|
||||
|
||||
/**
|
||||
* Returns the generation Speed
|
||||
*/
|
||||
val generationSpeed: Pair<Double?, Double?>
|
||||
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<Long, Double>? = null
|
||||
private var lastChunkCount: Pair<Long, Int>? = null
|
||||
private var thread = Thread(generationTask)
|
||||
|
||||
/**
|
||||
* Returns the generation Speed
|
||||
*/
|
||||
val generationSpeed: Pair<Double?, Double?>
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue