Initial exposed backing implementation
This commit is contained in:
@@ -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>
|
||||
|
||||
val id get() = "${pos.x}:${pos.z}"
|
||||
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
|
||||
}
|
||||
|
||||
class ParcelData {
|
||||
private val added = mutableMapOf<UUID, AddedStatus>()
|
||||
var owner: ParcelOwner? = null
|
||||
/**
|
||||
* 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}"
|
||||
|
||||
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)
|
||||
|
||||
fun isBanned(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.BANNED
|
||||
fun isAllowed(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.ALLOWED
|
||||
fun canBuild(player: Player) = isAllowed(player.uniqueId)
|
||||
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 ParcelDataHolder : ParcelData {
|
||||
override var added = mutableMapOf<UUID, AddedStatus>()
|
||||
override var owner: ParcelOwner? = null
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
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() }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
fun parcelByID(x: Int, z: Int): Parcel? {
|
||||
TODO("not implemented")
|
||||
override fun parcelByID(x: Int, z: Int): Parcel? {
|
||||
return container.parcelByID(x, z)
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 setParcelAllowsInteractInventory(plot: Parcel, value: Boolean)
|
||||
suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?)
|
||||
|
||||
suspend fun setParcelAllowsInteractInputs(plot: Parcel, value: Boolean)
|
||||
suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?)
|
||||
|
||||
suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean)
|
||||
|
||||
suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean)
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
281
src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
Normal file
281
src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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 setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = future { backing.setParcelPlayerState(parcelFor, player, state) }
|
||||
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
|
||||
|
||||
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = future { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = defer { backing.setParcelOwner(parcelFor, owner) }
|
||||
|
||||
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = future { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = defer { backing.setParcelPlayerState(parcelFor, player, state) }
|
||||
|
||||
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||
|
||||
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
Reference in New Issue
Block a user