Archived
0

Work on commands. Implemented helper functions, among others to handle asynchronous commands

This commit is contained in:
Dico200
2018-07-24 01:14:23 +01:00
parent 42026191ec
commit d15d1b767b
24 changed files with 426 additions and 104 deletions

View File

@@ -10,6 +10,8 @@ interface Backing {
val name: String
val isConnected: Boolean
suspend fun init()
suspend fun shutdown()
@@ -25,6 +27,8 @@ interface Backing {
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?)

View File

@@ -3,6 +3,7 @@ package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
import io.dico.parcels2.math.Vec2i
import io.dico.parcels2.util.synchronized
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.ProducerScope
@@ -16,7 +17,7 @@ object WorldsT : Table("worlds") {
val id = integer("id").autoIncrement().primaryKey()
val name = varchar("name", 50)
val uid = binary("uid", 16)
.also { uniqueIndex("index_uid", it) }
.also { uniqueIndex("index_uid", it) }
}
object ParcelsT : Table("parcels") {
@@ -24,31 +25,31 @@ object ParcelsT : Table("parcels") {
val px = integer("px")
val pz = integer("pz")
val world_id = integer("id")
.also { uniqueIndex("index_location", it, px, pz) }
.references(WorldsT.id)
.also { uniqueIndex("index_location", it, px, pz) }
.references(WorldsT.id)
val owner_uuid = binary("owner_uuid", 16).nullable()
val owner_name = varchar("owner_name", 16).nullable()
}
object AddedLocalT : Table("parcels_added_local") {
val parcel_id = integer("parcel_id")
.references(ParcelsT.id, ReferenceOption.CASCADE)
.references(ParcelsT.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16)
.also { uniqueIndex("index_pair", parcel_id, it) }
.also { uniqueIndex("index_pair", parcel_id, it) }
val allowed_flag = bool("allowed_flag")
}
object AddedGlobalT : Table("parcels_added_global") {
val owner_uuid = binary("owner_uuid", 16)
val player_uuid = binary("player_uuid", 16)
.also { uniqueIndex("index_pair", owner_uuid, it) }
.also { uniqueIndex("index_pair", owner_uuid, it) }
val allowed_flag = bool("allowed_flag")
}
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id")
.also { uniqueIndex("index_parcel_id", it) }
.references(ParcelsT.id, ReferenceOption.CASCADE)
.also { uniqueIndex("index_parcel_id", it) }
.references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_inventory = bool("interact_inventory").default(false)
val interact_inputs = bool("interact_inputs").default(false)
}
@@ -58,18 +59,29 @@ private class ExposedDatabaseException(message: String? = null) : Exception(mess
@Suppress("NOTHING_TO_INLINE")
class ExposedBacking(val dataSource: DataSource) : Backing {
override val name get() = "Exposed"
lateinit var database: Database
private var database: Database? = null
private var isShutdown: Boolean = false
override val isConnected get() = database != null
override suspend fun init() {
database = Database.connect(dataSource)
transaction(database) {
create(ParcelsT, AddedLocalT)
synchronized {
if (isShutdown) throw IllegalStateException()
database = Database.connect(dataSource)
transaction(database) {
create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT)
}
}
}
override suspend fun shutdown() {
if (dataSource is HikariDataSource) {
dataSource.close()
synchronized {
if (isShutdown) throw IllegalStateException()
if (dataSource is HikariDataSource) {
dataSource.close()
}
database = null
isShutdown = true
}
}
@@ -86,13 +98,13 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
private inline fun Transaction.getOrInitWorldId(worldUid: UUID, worldName: String): Int {
val binaryUid = worldUid.toByteArray()!!
return getWorldId(binaryUid)
?: WorldsT.insertIgnore { it[uid] = binaryUid; it[name] = worldName }.get(WorldsT.id)
?: throw ExposedDatabaseException("This should not happen - failed to insert world named $worldName and get its id")
?: WorldsT.insertIgnore { it[uid] = binaryUid; it[name] = worldName }.get(WorldsT.id)
?: throw ExposedDatabaseException("This should not happen - failed to insert world named $worldName and get its id")
}
private inline fun Transaction.getParcelId(worldId: Int, parcelX: Int, parcelZ: Int): Int? {
return ParcelsT.select { (ParcelsT.world_id eq worldId) and (ParcelsT.px eq parcelX) and (ParcelsT.pz eq parcelZ) }
.firstOrNull()?.let { it[ParcelsT.id] }
.firstOrNull()?.let { it[ParcelsT.id] }
}
private inline fun Transaction.getParcelId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? {
@@ -102,8 +114,8 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
private inline fun Transaction.getOrInitParcelId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int {
val worldId = getOrInitWorldId(worldUid, worldName)
return getParcelId(worldId, parcelX, parcelZ)
?: ParcelsT.insertIgnore { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }.get(ParcelsT.id)
?: throw ExposedDatabaseException("This should not happen - failed to insert parcel at $worldName($parcelX, $parcelZ)")
?: ParcelsT.insertIgnore { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }.get(ParcelsT.id)
?: throw ExposedDatabaseException("This should not happen - failed to insert parcel at $worldName($parcelX, $parcelZ)")
}
private inline fun Transaction.getParcelRow(id: Int): ResultRow? {
@@ -144,8 +156,8 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
ParcelDataHolder().apply {
owner = ParcelOwner.create(
uuid = row[ParcelsT.owner_uuid]?.toUUID(),
name = row[ParcelsT.owner_name]
uuid = row[ParcelsT.owner_uuid]?.toUUID(),
name = row[ParcelsT.owner_name]
)
val parcelId = row[ParcelsT.id]
@@ -176,16 +188,16 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
}
ParcelsT.select(where)
.map { parcelRow ->
val worldId = parcelRow[ParcelsT.world_id]
val worldRow = WorldsT.select({ WorldsT.id eq worldId }).firstOrNull()
?: return@map null
.map { parcelRow ->
val worldId = parcelRow[ParcelsT.world_id]
val worldRow = WorldsT.select({ WorldsT.id eq worldId }).firstOrNull()
?: return@map null
val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID())
SerializableParcel(world, Vec2i(parcelRow[ParcelsT.px], parcelRow[ParcelsT.pz]))
}
.filterNotNull()
.toList()
val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID())
SerializableParcel(world, Vec2i(parcelRow[ParcelsT.px], parcelRow[ParcelsT.pz]))
}
.filterNotNull()
.toList()
}

View File

@@ -10,15 +10,15 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager
* insertOrUpdate from https://github.com/JetBrains/Exposed/issues/167#issuecomment-403837917
*/
inline fun <T : Table> T.insertOrUpdate(vararg onDuplicateUpdateKeys: Column<*>, body: T.(InsertStatement<Number>) -> Unit) =
InsertOrUpdate<Number>(onDuplicateUpdateKeys, this).apply {
body(this)
execute(TransactionManager.current())
}
InsertOrUpdate<Number>(onDuplicateUpdateKeys, this).apply {
body(this)
execute(TransactionManager.current())
}
class InsertOrUpdate<Key : Any>(
private val onDuplicateUpdateKeys: Array<out Column<*>>,
table: Table,
isIgnore: Boolean = false
private val onDuplicateUpdateKeys: Array<out Column<*>>,
table: Table,
isIgnore: Boolean = false
) : InsertStatement<Key>(table, isIgnore) {
override fun prepareSQL(transaction: Transaction): String {
val onUpdateSQL = if (onDuplicateUpdateKeys.isNotEmpty()) {

View File

@@ -31,7 +31,7 @@ fun getHikariDataSource(dialectName: String,
dataSourceProperties.remove("serverName")
dataSourceProperties.remove("port")
dataSourceProperties.remove("databaseName")
addDataSourceProperty("url", "jdbc:h2:tcp://$address/~/${dco.database}")
addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}")
} else {
// doesn't exist on the MariaDB driver
addDataSourceProperty("cachePrepStmts", "true")

View File

@@ -8,10 +8,11 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import io.dico.parcels2.*
import io.dico.parcels2.GeneratorFactory
import io.dico.parcels2.GeneratorOptions
import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit
import org.bukkit.block.data.BlockData
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply {

View File

@@ -14,10 +14,11 @@ interface Storage {
val name: String
val syncDispatcher: CoroutineDispatcher
val asyncDispatcher: CoroutineDispatcher
val isConnected: Boolean
fun init(): Deferred<Unit>
fun init(): Job
fun shutdown(): Deferred<Unit>
fun shutdown(): Job
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
@@ -26,16 +27,18 @@ interface Storage {
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
fun getNumParcels(user: ParcelOwner): Deferred<Int>
fun setParcelData(parcelFor: Parcel, data: ParcelData?): Deferred<Unit>
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Deferred<Unit>
fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Deferred<Unit>
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job
fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Deferred<Unit>
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Job
fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Deferred<Unit>
fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job
fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job
}
@@ -44,15 +47,21 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
override val syncDispatcher = Executor { it.run() }.asCoroutineDispatcher()
val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
override val isConnected get() = backing.isConnected
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
override fun init() = defer { backing.init() }
@Suppress("NOTHING_TO_INLINE")
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
override fun shutdown() = defer { backing.shutdown() }
override fun init() = job { backing.init() }
override fun shutdown() = job { backing.shutdown() }
override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
@@ -61,16 +70,17 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
with(backing) { produceParcelData(parcelsFor) }
}
override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = defer { backing.setParcelData(parcelFor, data) }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = defer { backing.setParcelOwner(parcelFor, owner) }
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = defer { backing.setParcelPlayerState(parcelFor, player, state) }
override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) }
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInventory(parcel, value) }
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInputs(parcel, value) }
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) }
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
}

View File

@@ -1,7 +1,6 @@
package io.dico.parcels2.storage
import io.dico.parcels2.DataConnectionOptions
import net.minecraft.server.v1_13_R1.WorldType.types
import kotlin.reflect.KClass
interface StorageFactory {
@@ -29,8 +28,8 @@ class ConnectionStorageFactory : StorageFactory {
override val optionsClass = DataConnectionOptions::class
private val types: Map<String, String> = mutableMapOf(
"mysql" to "com.mysql.jdbc.jdbc2.optional.MysqlDataSource",
"h2" to "org.h2.jdbcx.JdbcDataSource"
"mysql" to "com.mysql.jdbc.jdbc2.optional.MysqlDataSource",
"h2" to "org.h2.jdbcx.JdbcDataSource"
)
fun register(companion: StorageFactory.StorageFactories) {