summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico200 <dico.karssiens@gmail.com>2018-07-23 02:23:46 +0200
committerDico200 <dico.karssiens@gmail.com>2018-07-23 02:23:46 +0200
commit42026191ec3a1f6468d8a46304d6ce5cd2d0689c (patch)
tree9af249ea52a7485e665828ca8654f846d55ec204
parent13b73dad61e8624322df7fb9ddf9bab90db9cc95 (diff)
Initial exposed backing implementation
-rw-r--r--src/main/kotlin/io/dico/parcels2/Parcel.kt138
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelWorld.kt41
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Backing.kt20
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Exposed.kt90
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt281
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt36
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Jackson.kt11
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Storage.kt29
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt3
10 files changed, 502 insertions, 149 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt
index 0ec2c0b..046d6f5 100644
--- a/src/main/kotlin/io/dico/parcels2/Parcel.kt
+++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt
@@ -7,52 +7,148 @@ import org.bukkit.Bukkit
import org.bukkit.entity.Player
import java.util.*
-class Parcel(val world: ParcelWorld,
- val pos: Vec2i,
- var data: ParcelData = ParcelData()) {
+interface ParcelData {
+ var owner: ParcelOwner?
+ val added: Map<UUID, AddedStatus>
+ fun getAddedStatus(uuid: UUID): AddedStatus
+ fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
+ fun isBanned(uuid: UUID): Boolean
+ fun isAllowed(uuid: UUID): Boolean
+ fun canBuild(player: Player): Boolean
+
+ var allowInteractInputs: Boolean
+ var allowInteractInventory: Boolean
+}
+
+/**
+ * Parcel implementation of ParcelData will update the database when changes are made.
+ * To change the data without updating the database, defer to the data delegate instance.
+ *
+ * This should be used for example in database query callbacks.
+ * However, this implementation is intentionally not thread-safe.
+ * Therefore, database query callbacks should schedule their updates using the bukkit scheduler.
+ */
+class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData {
val id get() = "${pos.x}:${pos.z}"
+
+ var data: ParcelData = ParcelDataHolder(); private set
+
+ fun copyDataIgnoringDatabase(data: ParcelData) {
+ this.data = data
+ }
+
+ fun copyData(data: ParcelData) {
+ world.storage.setParcelData(this, data)
+ this.data = data
+ }
+
+ override val added: Map<UUID, AddedStatus> get() = data.added
+ override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid)
+ override fun isBanned(uuid: UUID) = data.isBanned(uuid)
+ override fun isAllowed(uuid: UUID) = data.isAllowed(uuid)
+ override fun canBuild(player: Player) = data.canBuild(player)
+
+ override var owner: ParcelOwner?
+ get() = data.owner
+ set(value) {
+ if (data.owner != value) {
+ world.storage.setParcelOwner(this, value)
+ data.owner = value
+ }
+ }
+
+ override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
+ return data.setAddedStatus(uuid, status).also {
+ if (it) world.storage.setParcelPlayerState(this, uuid, status.asBoolean)
+ }
+ }
+
+ override var allowInteractInputs: Boolean
+ get() = data.allowInteractInputs
+ set(value) {
+ if (data.allowInteractInputs == value) return
+ world.storage.setParcelAllowsInteractInputs(this, value)
+ data.allowInteractInputs = value
+ }
+
+ override var allowInteractInventory: Boolean
+ get() = data.allowInteractInventory
+ set(value) {
+ if (data.allowInteractInventory == value) return
+ world.storage.setParcelAllowsInteractInventory(this, value)
+ data.allowInteractInventory = value
+ }
}
-class ParcelData {
- private val added = mutableMapOf<UUID, AddedStatus>()
- var owner: ParcelOwner? = null
+class ParcelDataHolder : ParcelData {
+ override var added = mutableMapOf<UUID, AddedStatus>()
+ override var owner: ParcelOwner? = null
- fun setAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT)
- fun setAddedStatus(uuid: UUID, state: AddedStatus) = state.takeIf { it != AddedStatus.DEFAULT }?.let { added[uuid] = it }
- ?: added.remove(uuid)
+ override fun getAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT)
+ override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
+ ?.let { added.put(uuid, it) != it }
+ ?: added.remove(uuid) != null
- fun isBanned(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.BANNED
- fun isAllowed(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.ALLOWED
- fun canBuild(player: Player) = isAllowed(player.uniqueId)
+ override fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
+ override fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
+ override fun canBuild(player: Player) = isAllowed(player.uniqueId)
|| owner?.matches(player, allowNameMatch = false) ?: false
|| player.hasBuildAnywhere
+
+ override var allowInteractInputs = true
+ override var allowInteractInventory = true
}
enum class AddedStatus {
DEFAULT,
ALLOWED,
- BANNED
+ BANNED;
+
+ val asBoolean
+ get() = when (this) {
+ DEFAULT -> null
+ ALLOWED -> true
+ BANNED -> false
+ }
}
-data class ParcelOwner(val uuid: UUID? = null,
- val name: String? = null) {
+@Suppress("UsePropertyAccessSyntax")
+class ParcelOwner(val uuid: UUID? = null,
+ name: String? = null) {
+
+ companion object {
+ fun create(uuid: UUID?, name: String?): ParcelOwner? {
+ return uuid?.let { ParcelOwner(uuid, name) }
+ ?: name?.let { ParcelOwner(uuid, name) }
+ }
+ }
+
+ val name: String?
init {
uuid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present")
+
+ if (name != null) this.name = name
+ else {
+ val offlinePlayer = Bukkit.getOfflinePlayer(uuid).takeIf { it.isOnline() || it.hasPlayedBefore() }
+ this.name = offlinePlayer?.name
+ }
}
val playerName get() = getPlayerName(uuid, name)
- @Suppress("DEPRECATION")
- val offlinePlayer
- get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
- ?.takeIf { it.isOnline() || it.hasPlayedBefore() }
-
fun matches(player: Player, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
-} \ No newline at end of file
+ val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
+ val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) }
+
+ @Suppress("DEPRECATION")
+ val offlinePlayer
+ get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
+ ?.takeIf { it.isOnline() || it.hasPlayedBefore() }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
index 3ae9536..376b2ab 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
@@ -2,6 +2,8 @@ package io.dico.parcels2
import io.dico.parcels2.math.Vec2i
import io.dico.parcels2.math.floor
+import io.dico.parcels2.storage.SerializableParcel
+import io.dico.parcels2.storage.SerializableWorld
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.doAwait
import kotlinx.coroutines.experimental.launch
@@ -37,19 +39,24 @@ class Worlds(private val plugin: ParcelsPlugin) {
}
}
+ operator fun SerializableParcel.invoke(): Parcel? {
+ return world()?.parcelByID(pos)
+ }
+
+ operator fun SerializableWorld.invoke(): ParcelWorld? {
+ return world?.let { getWorld(it) }
+ }
+
fun loadWorlds(options: Options) {
for ((worldName, worldOptions) in options.worlds.entries) {
val world: ParcelWorld
try {
- val containerFactory: ParcelContainerFactory = { parcelWorld ->
- DefaultParcelContainer(parcelWorld, plugin.storage)
- }
world = ParcelWorld(
worldName,
worldOptions,
worldOptions.generator.getGenerator(this, worldName),
- containerFactory)
+ plugin.storage)
} catch (ex: Exception) {
ex.printStackTrace()
@@ -102,12 +109,16 @@ interface ParcelProvider {
class ParcelWorld constructor(val name: String,
val options: WorldOptions,
val generator: ParcelGenerator,
- containerFactory: ParcelContainerFactory) : ParcelProvider by generator {
+ val storage: Storage) : ParcelProvider by generator, ParcelContainer {
val world: World by lazy { Bukkit.getWorld(name) ?: throw NullPointerException("World $name does not appear to be loaded") }
- val container: ParcelContainer = containerFactory(this)
+ val container: ParcelContainer = DefaultParcelContainer(this, storage)
+
+ override fun parcelByID(x: Int, z: Int): Parcel? {
+ return container.parcelByID(x, z)
+ }
- fun parcelByID(x: Int, z: Int): Parcel? {
- TODO("not implemented")
+ override fun nextEmptyParcel(): Parcel? {
+ return container.nextEmptyParcel()
}
fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z)
@@ -131,18 +142,18 @@ class ParcelWorld constructor(val name: String,
}
-abstract class ParcelContainer {
+interface ParcelContainer {
- abstract fun ployByID(x: Int, z: Int): Parcel?
+ fun parcelByID(x: Int, z: Int): Parcel?
- abstract fun nextEmptyParcel(): Parcel?
+ fun nextEmptyParcel(): Parcel?
}
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
class DefaultParcelContainer(private val world: ParcelWorld,
- private val storage: Storage) : ParcelContainer() {
+ private val storage: Storage) : ParcelContainer {
private var parcels: Array<Array<Parcel>>
init {
@@ -165,12 +176,12 @@ class DefaultParcelContainer(private val world: ParcelWorld,
val x = it - axisLimit
Array(arraySize) {
val z = it - axisLimit
- cur?.ployByID(x, z) ?: Parcel(world, Vec2i(x, z))
+ cur?.parcelByID(x, z) ?: Parcel(world, Vec2i(x, z))
}
}
}
- override fun ployByID(x: Int, z: Int): Parcel? {
+ override fun parcelByID(x: Int, z: Int): Parcel? {
return parcels[x][z]
}
@@ -188,7 +199,7 @@ class DefaultParcelContainer(private val world: ParcelWorld,
val channel = storage.readParcelData(allParcels(), 100)
launch(storage.asyncDispatcher) {
for ((parcel, data) in channel) {
- data?.let { parcel.data = it }
+ data?.let { parcel.copyDataIgnoringDatabase(it) }
}
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
index cd33b3d..0f8829d 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
@@ -3,7 +3,6 @@ package io.dico.parcels2.storage
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelData
import io.dico.parcels2.ParcelOwner
-import io.dico.parcels2.storage.SerializableParcel
import kotlinx.coroutines.experimental.channels.ProducerScope
import java.util.*
@@ -15,23 +14,26 @@ interface Backing {
suspend fun shutdown()
+
/**
- * This producer function is capable of constantly reading plots from a potentially infinite sequence,
- * and provide plotdata for it as read from the database.
+ * This producer function is capable of constantly reading parcels from a potentially infinite sequence,
+ * and provide parcel data for it as read from the database.
*/
-
suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>)
- suspend fun readParcelData(plotFor: Parcel): ParcelData?
+ suspend fun readParcelData(parcelFor: Parcel): ParcelData?
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
- suspend fun setParcelOwner(plotFor: Parcel, owner: ParcelOwner?)
- suspend fun setParcelPlayerState(plotFor: Parcel, player: UUID, state: Boolean?)
+ suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?)
+
+ suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?)
+
+ suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?)
- suspend fun setParcelAllowsInteractInventory(plot: Parcel, value: Boolean)
+ suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean)
- suspend fun setParcelAllowsInteractInputs(plot: Parcel, value: Boolean)
+ suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean)
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Exposed.kt b/src/main/kotlin/io/dico/parcels2/storage/Exposed.kt
deleted file mode 100644
index cbb5887..0000000
--- a/src/main/kotlin/io/dico/parcels2/storage/Exposed.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package io.dico.parcels2.storage
-
-import com.zaxxer.hikari.HikariDataSource
-import io.dico.parcels2.Parcel
-import io.dico.parcels2.ParcelData
-import io.dico.parcels2.ParcelOwner
-import kotlinx.coroutines.experimental.channels.ProducerScope
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.ReferenceOption
-import org.jetbrains.exposed.sql.Table
-import org.jetbrains.exposed.sql.transactions.transaction
-import java.util.*
-import javax.sql.DataSource
-import org.jetbrains.exposed.sql.SchemaUtils.create
-
-object ParcelsTable : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val px = integer("px")
- val pz = integer("pz")
- val world_uuid = binary("world_uuid", 16).also { uniqueIndex("location", it, px, pz) }
- val world = varchar("world", 32).nullable()
- val owner_uuid = binary("owner_uuid", 16).nullable()
- val owner = varchar("owner", 16).nullable()
-}
-
-object ParcelsAddedTable : Table() {
- val id = integer("id").references(ParcelsTable.id, ReferenceOption.CASCADE)
- val player_uuid = binary("player_uuid", 16).also { uniqueIndex("pair", id, it) }
- val allowed_flag = bool("allowed_flag")
-}
-
-object PlayerAddedTable : Table() {
- val owner_uuid = binary("owner_uuid", 16)
- val player_uuid = binary("player_uuid", 16).also { uniqueIndex("pair", owner_uuid, it) }
- val allowed_flag = bool("allowed_flag")
-}
-
-class ExposedBacking(val dataSource: DataSource) : Backing {
- override val name get() = "Exposed"
- lateinit var database: Database
-
- override suspend fun init() {
- database = Database.connect(dataSource)
- transaction(database) {
- create(ParcelsTable, ParcelsAddedTable)
- }
- }
-
- override suspend fun shutdown() {
- if (dataSource is HikariDataSource) {
- dataSource.close()
- }
- }
-
- override suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) {
- TODO()
- }
-
- override suspend fun readParcelData(plotFor: Parcel): ParcelData? {
- TODO()
- }
-
- override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> {
- TODO()
- }
-
- override suspend fun setParcelOwner(plotFor: Parcel, owner: ParcelOwner?) {
- TODO()
- }
-
- override suspend fun setParcelPlayerState(plotFor: Parcel, player: UUID, state: Boolean?) {
- TODO()
- }
-
- override suspend fun setParcelAllowsInteractInventory(plot: Parcel, value: Boolean) {
- TODO()
- }
-
- override suspend fun setParcelAllowsInteractInputs(plot: Parcel, value: Boolean) {
- TODO()
- }
-
-}
-
-
-
-
-
-
-
diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
new file mode 100644
index 0000000..e79c7e0
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
@@ -0,0 +1,281 @@
+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.toByteArray
+import io.dico.parcels2.util.toUUID
+import kotlinx.coroutines.experimental.channels.ProducerScope
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.SchemaUtils.create
+import org.jetbrains.exposed.sql.transactions.transaction
+import java.util.*
+import javax.sql.DataSource
+
+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) }
+}
+
+object ParcelsT : Table("parcels") {
+ val id = integer("id").autoIncrement().primaryKey()
+ val px = integer("px")
+ val pz = integer("pz")
+ val world_id = integer("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)
+ val player_uuid = binary("player_uuid", 16)
+ .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) }
+ 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)
+ val interact_inventory = bool("interact_inventory").default(false)
+ val interact_inputs = bool("interact_inputs").default(false)
+}
+
+private class ExposedDatabaseException(message: String? = null) : Exception(message)
+
+@Suppress("NOTHING_TO_INLINE")
+class ExposedBacking(val dataSource: DataSource) : Backing {
+ override val name get() = "Exposed"
+ lateinit var database: Database
+
+ override suspend fun init() {
+ database = Database.connect(dataSource)
+ transaction(database) {
+ create(ParcelsT, AddedLocalT)
+ }
+ }
+
+ override suspend fun shutdown() {
+ if (dataSource is HikariDataSource) {
+ dataSource.close()
+ }
+ }
+
+ private fun <T> transaction(statement: Transaction.() -> T) = transaction(database, statement)
+
+ private inline fun Transaction.getWorldId(binaryUid: ByteArray): Int? {
+ return WorldsT.select { WorldsT.uid eq binaryUid }.firstOrNull()?.let { it[WorldsT.id] }
+ }
+
+ private inline fun Transaction.getWorldId(worldUid: UUID): Int? {
+ return getWorldId(worldUid.toByteArray()!!)
+ }
+
+ 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")
+ }
+
+ 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] }
+ }
+
+ private inline fun Transaction.getParcelId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? {
+ return getWorldId(worldUid)?.let { getParcelId(it, parcelX, parcelZ) }
+ }
+
+ 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)")
+ }
+
+ private inline fun Transaction.getParcelRow(id: Int): ResultRow? {
+ return ParcelsT.select { ParcelsT.id eq id }.firstOrNull()
+ }
+
+ fun Transaction.getWorldId(world: ParcelWorld): Int? {
+ return getWorldId(world.world.uid)
+ }
+
+ fun Transaction.getOrInitWorldId(world: ParcelWorld): Int {
+ return world.world.let { getOrInitWorldId(it.uid, it.name) }
+ }
+
+ fun Transaction.getParcelId(parcel: Parcel): Int? {
+ return getParcelId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z)
+ }
+
+ fun Transaction.getOrInitParcelId(parcel: Parcel): Int {
+ return parcel.world.world.let { getOrInitParcelId(it.uid, it.name, parcel.pos.x, parcel.pos.z) }
+ }
+
+ fun Transaction.getParcelRow(parcel: Parcel): ResultRow? {
+ return getParcelId(parcel)?.let { getParcelRow(it) }
+ }
+
+ override suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) {
+ for (parcel in parcels) {
+ val data = readParcelData(parcel)
+ channel.send(parcel to data)
+ }
+ channel.close()
+ }
+
+ override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction {
+ val row = getParcelRow(parcelFor) ?: return@transaction null
+
+ ParcelDataHolder().apply {
+
+ owner = ParcelOwner.create(
+ uuid = row[ParcelsT.owner_uuid]?.toUUID(),
+ name = row[ParcelsT.owner_name]
+ )
+
+ val parcelId = row[ParcelsT.id]
+ AddedLocalT.select { AddedLocalT.parcel_id eq parcelId }.forEach {
+ val uuid = it[AddedLocalT.player_uuid].toUUID()!!
+ val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED
+ setAddedStatus(uuid, status)
+ }
+
+ ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let {
+ allowInteractInputs = it[ParcelOptionsT.interact_inputs]
+ allowInteractInventory = it[ParcelOptionsT.interact_inventory]
+ }
+
+ }
+
+ }
+
+ override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> = transaction {
+ val where: SqlExpressionBuilder.() -> Op<Boolean>
+
+ if (user.uuid != null) {
+ val binaryUuid = user.uuid.toByteArray()
+ where = { ParcelsT.owner_uuid eq binaryUuid }
+ } else {
+ val name = user.name
+ where = { ParcelsT.owner_name eq name }
+ }
+
+ ParcelsT.select(where)
+ .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()
+ }
+
+
+ override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) {
+ if (data == null) {
+ transaction {
+ getParcelId(parcelFor)?.let { id ->
+ ParcelsT.deleteIgnoreWhere(limit = 1) { ParcelsT.id eq id }
+
+ // Below should cascade automatically
+ /*
+ AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id }
+ ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
+ */
+ }
+
+ }
+ return
+ }
+
+ val id = transaction {
+ val id = getOrInitParcelId(parcelFor)
+ AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id }
+ id
+ }
+
+ setParcelOwner(parcelFor, data.owner)
+
+ for ((uuid, status) in data.added) {
+ val state = status.asBoolean
+ setParcelPlayerState(parcelFor, uuid, state)
+ }
+
+ setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs)
+ setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory)
+ }
+
+ override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction {
+ val binaryUuid = owner?.uuid?.toByteArray()
+ val name = owner?.name
+
+ val id = if (owner == null)
+ getParcelId(parcelFor) ?: return@transaction
+ else
+ getOrInitParcelId(parcelFor)
+
+ ParcelsT.update({ ParcelsT.id eq id }, limit = 1) {
+ it[ParcelsT.owner_uuid] = binaryUuid
+ it[ParcelsT.owner_name] = name
+ }
+ }
+
+ override suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = transaction {
+ val binaryUuid = player.toByteArray()!!
+
+ if (state == null) {
+ getParcelId(parcelFor)?.let { id ->
+ AddedLocalT.deleteWhere { (AddedLocalT.parcel_id eq id) and (AddedLocalT.player_uuid eq binaryUuid) }
+ }
+ return@transaction
+ }
+
+ val id = getOrInitParcelId(parcelFor)
+ AddedLocalT.insertOrUpdate(AddedLocalT.allowed_flag) {
+ it[AddedLocalT.parcel_id] = id
+ it[AddedLocalT.player_uuid] = binaryUuid
+ }
+ }
+
+ override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction {
+ val id = getOrInitParcelId(parcel)
+ ParcelOptionsT.insertOrUpdate(ParcelOptionsT.interact_inventory) {
+ it[ParcelOptionsT.parcel_id] = id
+ it[ParcelOptionsT.interact_inventory] = value
+ }
+ }
+
+ override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction {
+ val id = getOrInitParcelId(parcel)
+ ParcelOptionsT.insertOrUpdate(ParcelOptionsT.interact_inputs) {
+ it[ParcelOptionsT.parcel_id] = id
+ it[ParcelOptionsT.interact_inputs] = value
+ }
+ }
+
+}
+
+
+
+
+
+
+
diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt
new file mode 100644
index 0000000..f429d7e
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt
@@ -0,0 +1,36 @@
+package io.dico.parcels2.storage
+
+import org.jetbrains.exposed.sql.Column
+import org.jetbrains.exposed.sql.Table
+import org.jetbrains.exposed.sql.Transaction
+import org.jetbrains.exposed.sql.statements.InsertStatement
+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())
+ }
+
+class InsertOrUpdate<Key : Any>(
+ 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()) {
+ " ON DUPLICATE KEY UPDATE " + onDuplicateUpdateKeys.joinToString { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
+ } else ""
+ return super.prepareSQL(transaction) + onUpdateSQL
+ }
+}
+
+
+
+
+
+
+
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
index b5bdbeb..08ca810 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
@@ -22,12 +22,15 @@ val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply {
with(kotlinModule) {
setSerializerModifier(object : BeanSerializerModifier() {
@Suppress("UNCHECKED_CAST")
- override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription?, serializer: JsonSerializer<*>?): JsonSerializer<*> {
- if (GeneratorOptions::class.isSuperclassOf(beanDesc?.beanClass?.kotlin as KClass<*>)) {
- return GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
+ override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
+
+ val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) {
+ GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
+ } else {
+ serializer
}
- return super.modifySerializer(config, beanDesc, serializer)
+ return super.modifySerializer(config, beanDesc, newSerializer)
}
})
diff --git a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt
index 4e467b1..121e251 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt
@@ -22,7 +22,7 @@ data class SerializableWorld(val name: String? = null,
* Used by storage backing options to encompass the location of a parcel
*/
data class SerializableParcel(val world: SerializableWorld,
- val coord: Vec2i) {
+ val pos: Vec2i) {
val parcel: Parcel? by lazy { TODO() }
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
index 36f5400..67c4b05 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
@@ -19,12 +19,16 @@ interface Storage {
fun shutdown(): Deferred<Unit>
+
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
+
+ fun setParcelData(parcelFor: Parcel, data: ParcelData?): Deferred<Unit>
+
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Deferred<Unit>
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Deferred<Unit>
@@ -41,25 +45,32 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
- private fun <T> future(block: suspend CoroutineScope.() -> T) = async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
+ @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() = future { backing.init() }
+ override fun init() = defer { backing.init() }
- override fun shutdown() = future { backing.shutdown() }
+ override fun shutdown() = defer { backing.shutdown() }
- override fun readParcelData(parcelFor: Parcel) = future { backing.readParcelData(parcelFor) }
+
+ override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) = produce(asyncDispatcher, capacity = channelCapacity) {
with(backing) { produceParcelData(parcelsFor) }
}
- override fun getOwnedParcels(user: ParcelOwner) = future { backing.getOwnedParcels(user) }
- override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = future { backing.setParcelOwner(parcelFor, owner) }
+ 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 setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = future { backing.setParcelPlayerState(parcelFor, player, state) }
+ override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = defer { backing.setParcelPlayerState(parcelFor, player, state) }
- override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = future { backing.setParcelAllowsInteractInventory(parcel, value) }
+ override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInventory(parcel, value) }
- override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = future { backing.setParcelAllowsInteractInputs(parcel, value) }
+ override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInputs(parcel, value) }
}
diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
index 5bd7f92..7cd298f 100644
--- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
@@ -1,6 +1,7 @@
package io.dico.parcels2.util
import org.bukkit.Bukkit
+import org.jetbrains.annotations.Contract
import java.nio.ByteBuffer
import java.util.*
@@ -11,6 +12,7 @@ fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
?: ":unknown_name:"
}
+@Contract("null -> null; !null -> !null", pure = true)
fun UUID?.toByteArray(): ByteArray? = this?.let {
ByteBuffer.allocate(16).apply {
putLong(mostSignificantBits)
@@ -18,6 +20,7 @@ fun UUID?.toByteArray(): ByteArray? = this?.let {
}.array()
}
+@Contract("null -> null; !null -> !null", pure = true)
fun ByteArray?.toUUID(): UUID? = this?.let {
ByteBuffer.wrap(it).run { UUID(long, long) }
} \ No newline at end of file