Initial exposed backing implementation
This commit is contained in:
@@ -7,52 +7,148 @@ import org.bukkit.Bukkit
|
|||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Parcel(val world: ParcelWorld,
|
interface ParcelData {
|
||||||
val pos: Vec2i,
|
var owner: ParcelOwner?
|
||||||
var data: ParcelData = ParcelData()) {
|
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>()
|
* Parcel implementation of ParcelData will update the database when changes are made.
|
||||||
var owner: ParcelOwner? = null
|
* 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
|
var data: ParcelData = ParcelDataHolder(); private set
|
||||||
fun isAllowed(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.ALLOWED
|
|
||||||
fun canBuild(player: Player) = isAllowed(player.uniqueId)
|
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
|
|| owner?.matches(player, allowNameMatch = false) ?: false
|
||||||
|| player.hasBuildAnywhere
|
|| player.hasBuildAnywhere
|
||||||
|
|
||||||
|
override var allowInteractInputs = true
|
||||||
|
override var allowInteractInventory = true
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class AddedStatus {
|
enum class AddedStatus {
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
ALLOWED,
|
ALLOWED,
|
||||||
BANNED
|
BANNED;
|
||||||
|
|
||||||
|
val asBoolean
|
||||||
|
get() = when (this) {
|
||||||
|
DEFAULT -> null
|
||||||
|
ALLOWED -> true
|
||||||
|
BANNED -> false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ParcelOwner(val uuid: UUID? = null,
|
@Suppress("UsePropertyAccessSyntax")
|
||||||
val name: String? = null) {
|
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 {
|
init {
|
||||||
uuid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present")
|
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)
|
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 {
|
fun matches(player: Player, allowNameMatch: Boolean = false): Boolean {
|
||||||
return uuid?.let { it == player.uniqueId } ?: false
|
return uuid?.let { it == player.uniqueId } ?: false
|
||||||
|| (allowNameMatch && name?.let { it == player.name } ?: 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.Vec2i
|
||||||
import io.dico.parcels2.math.floor
|
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.storage.Storage
|
||||||
import io.dico.parcels2.util.doAwait
|
import io.dico.parcels2.util.doAwait
|
||||||
import kotlinx.coroutines.experimental.launch
|
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) {
|
fun loadWorlds(options: Options) {
|
||||||
for ((worldName, worldOptions) in options.worlds.entries) {
|
for ((worldName, worldOptions) in options.worlds.entries) {
|
||||||
val world: ParcelWorld
|
val world: ParcelWorld
|
||||||
try {
|
try {
|
||||||
val containerFactory: ParcelContainerFactory = { parcelWorld ->
|
|
||||||
DefaultParcelContainer(parcelWorld, plugin.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
world = ParcelWorld(
|
world = ParcelWorld(
|
||||||
worldName,
|
worldName,
|
||||||
worldOptions,
|
worldOptions,
|
||||||
worldOptions.generator.getGenerator(this, worldName),
|
worldOptions.generator.getGenerator(this, worldName),
|
||||||
containerFactory)
|
plugin.storage)
|
||||||
|
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
@@ -102,12 +109,16 @@ interface ParcelProvider {
|
|||||||
class ParcelWorld constructor(val name: String,
|
class ParcelWorld constructor(val name: String,
|
||||||
val options: WorldOptions,
|
val options: WorldOptions,
|
||||||
val generator: ParcelGenerator,
|
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 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? {
|
override fun parcelByID(x: Int, z: Int): Parcel? {
|
||||||
TODO("not implemented")
|
return container.parcelByID(x, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nextEmptyParcel(): Parcel? {
|
||||||
|
return container.nextEmptyParcel()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z)
|
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
|
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
|
||||||
|
|
||||||
class DefaultParcelContainer(private val world: ParcelWorld,
|
class DefaultParcelContainer(private val world: ParcelWorld,
|
||||||
private val storage: Storage) : ParcelContainer() {
|
private val storage: Storage) : ParcelContainer {
|
||||||
private var parcels: Array<Array<Parcel>>
|
private var parcels: Array<Array<Parcel>>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -165,12 +176,12 @@ class DefaultParcelContainer(private val world: ParcelWorld,
|
|||||||
val x = it - axisLimit
|
val x = it - axisLimit
|
||||||
Array(arraySize) {
|
Array(arraySize) {
|
||||||
val z = it - axisLimit
|
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]
|
return parcels[x][z]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +199,7 @@ class DefaultParcelContainer(private val world: ParcelWorld,
|
|||||||
val channel = storage.readParcelData(allParcels(), 100)
|
val channel = storage.readParcelData(allParcels(), 100)
|
||||||
launch(storage.asyncDispatcher) {
|
launch(storage.asyncDispatcher) {
|
||||||
for ((parcel, data) in channel) {
|
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.Parcel
|
||||||
import io.dico.parcels2.ParcelData
|
import io.dico.parcels2.ParcelData
|
||||||
import io.dico.parcels2.ParcelOwner
|
import io.dico.parcels2.ParcelOwner
|
||||||
import io.dico.parcels2.storage.SerializableParcel
|
|
||||||
import kotlinx.coroutines.experimental.channels.ProducerScope
|
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -15,23 +14,26 @@ interface Backing {
|
|||||||
|
|
||||||
suspend fun shutdown()
|
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 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 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) {
|
with(kotlinModule) {
|
||||||
setSerializerModifier(object : BeanSerializerModifier() {
|
setSerializerModifier(object : BeanSerializerModifier() {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription?, serializer: JsonSerializer<*>?): JsonSerializer<*> {
|
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>)
|
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
|
* Used by storage backing options to encompass the location of a parcel
|
||||||
*/
|
*/
|
||||||
data class SerializableParcel(val world: SerializableWorld,
|
data class SerializableParcel(val world: SerializableWorld,
|
||||||
val coord: Vec2i) {
|
val pos: Vec2i) {
|
||||||
|
|
||||||
val parcel: Parcel? by lazy { TODO() }
|
val parcel: Parcel? by lazy { TODO() }
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,16 @@ interface Storage {
|
|||||||
|
|
||||||
fun shutdown(): Deferred<Unit>
|
fun shutdown(): Deferred<Unit>
|
||||||
|
|
||||||
|
|
||||||
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
|
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
|
||||||
|
|
||||||
fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
|
fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
|
||||||
|
|
||||||
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
|
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
|
||||||
|
|
||||||
|
|
||||||
|
fun setParcelData(parcelFor: Parcel, data: ParcelData?): Deferred<Unit>
|
||||||
|
|
||||||
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Deferred<Unit>
|
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Deferred<Unit>
|
||||||
|
|
||||||
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): 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
|
val poolSize: Int get() = 4
|
||||||
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
|
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) {
|
override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) = produce(asyncDispatcher, capacity = channelCapacity) {
|
||||||
with(backing) { produceParcelData(parcelsFor) }
|
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
|
package io.dico.parcels2.util
|
||||||
|
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
|
|||||||
?: ":unknown_name:"
|
?: ":unknown_name:"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("null -> null; !null -> !null", pure = true)
|
||||||
fun UUID?.toByteArray(): ByteArray? = this?.let {
|
fun UUID?.toByteArray(): ByteArray? = this?.let {
|
||||||
ByteBuffer.allocate(16).apply {
|
ByteBuffer.allocate(16).apply {
|
||||||
putLong(mostSignificantBits)
|
putLong(mostSignificantBits)
|
||||||
@@ -18,6 +20,7 @@ fun UUID?.toByteArray(): ByteArray? = this?.let {
|
|||||||
}.array()
|
}.array()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("null -> null; !null -> !null", pure = true)
|
||||||
fun ByteArray?.toUUID(): UUID? = this?.let {
|
fun ByteArray?.toUUID(): UUID? = this?.let {
|
||||||
ByteBuffer.wrap(it).run { UUID(long, long) }
|
ByteBuffer.wrap(it).run { UUID(long, long) }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user