Archived
0

Initial exposed backing implementation

This commit is contained in:
Dico200
2018-07-23 02:23:46 +02:00
parent 13b73dad61
commit 42026191ec
10 changed files with 504 additions and 151 deletions

View File

@@ -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() }
}

View File

@@ -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) }
}
}
}

View File

@@ -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)
}

View File

@@ -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()
}
}

View 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
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
})

View File

@@ -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() }
}

View File

@@ -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) }
}

View File

@@ -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) }
}