Work on commands. Implemented helper functions, among others to handle asynchronous commands
This commit is contained in:
@@ -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?)
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user