diff options
author | Dico200 <dico.karssiens@gmail.com> | 2018-07-23 02:23:46 +0200 |
---|---|---|
committer | Dico200 <dico.karssiens@gmail.com> | 2018-07-23 02:23:46 +0200 |
commit | 42026191ec3a1f6468d8a46304d6ce5cd2d0689c (patch) | |
tree | 9af249ea52a7485e665828ca8654f846d55ec204 | |
parent | 13b73dad61e8624322df7fb9ddf9bab90db9cc95 (diff) |
Initial exposed backing implementation
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 |