Archived
0

Improve database abstractions, add GlobalAddedData, some other things

This commit is contained in:
Dico Karssiens
2018-08-01 16:45:27 +01:00
parent 1ec6dd136b
commit 472e700e04
26 changed files with 726 additions and 487 deletions

View File

@@ -0,0 +1,47 @@
package io.dico.parcels2
import io.dico.parcels2.util.uuid
import org.bukkit.OfflinePlayer
import java.util.*
interface AddedData {
val added: Map<UUID, AddedStatus>
fun getAddedStatus(uuid: UUID): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean =
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) }
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED)
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED)
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid)
fun allow(player: OfflinePlayer) = allow(player.uuid)
fun disallow(player: OfflinePlayer) = disallow(player.uuid)
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
fun ban(player: OfflinePlayer) = ban(player.uuid)
fun unban(player: OfflinePlayer) = unban(player.uuid)
}
open class AddedDataHolder(override var added: MutableMap<UUID, AddedStatus>
= mutableMapOf<UUID, AddedStatus>()) : AddedData {
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
}
enum class AddedStatus {
DEFAULT,
ALLOWED,
BANNED;
val isDefault get() = this == DEFAULT
val isAllowed get() = this == ALLOWED
val isBanned get() = this == BANNED
}

View File

@@ -1,38 +1,25 @@
@file:Suppress("UNCHECKED_CAST")
package io.dico.parcels2
import io.dico.parcels2.util.uuid
import kotlinx.coroutines.experimental.CompletableDeferred
import kotlinx.coroutines.experimental.Deferred
import org.bukkit.OfflinePlayer
import java.util.*
interface GlobalAddedData : AddedData {
val uuid: UUID
val owner: ParcelOwner
}
class GlobalAddedDataManager(val plugin: ParcelsPlugin) {
private val map = mutableMapOf<UUID, GlobalAddedData?>()
interface GlobalAddedDataManager {
operator fun get(owner: ParcelOwner): GlobalAddedData
}
operator fun get(player: OfflinePlayer) = get(player.uuid)
operator fun get(uuid: UUID): GlobalAddedData? {
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
private val map = mutableMapOf<ParcelOwner, GlobalAddedData>()
override fun get(owner: ParcelOwner): GlobalAddedData {
return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it }
}
fun getDeferred(uuid: UUID): Deferred<AddedData> {
get(uuid)?.let { return CompletableDeferred(it) }
}
private suspend fun getAsync(uuid: UUID): GlobalAddedData {
val data = plugin.storage.readGlobalAddedData(ParcelOwner(uuid = uuid)).await()
?: return GlobalAddedDataImpl(uuid)
val result = GlobalAddedDataImpl(uuid, data)
map[uuid] = result
return result
}
private inner class GlobalAddedDataImpl(override val uuid: UUID,
private inner class GlobalAddedDataImpl(override val owner: ParcelOwner,
data: MutableMap<UUID, AddedStatus> = emptyData)
: AddedDataHolder(data), GlobalAddedData {
@@ -45,7 +32,7 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) {
data = mutableMapOf()
}
return super.setAddedStatus(uuid, status).also {
if (it) plugin.storage.setGlobalAddedStatus(ParcelOwner(uuid = this.uuid), uuid, status)
if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status)
}
}
@@ -59,5 +46,3 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) {

View File

@@ -1,42 +1,16 @@
package io.dico.parcels2
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.hasBuildAnywhere
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import org.joda.time.DateTime
import java.util.*
interface AddedData {
val added: Map<UUID, AddedStatus>
fun getAddedStatus(uuid: UUID): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean =
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) }
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED)
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED)
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid)
fun allow(player: OfflinePlayer) = allow(player.uuid)
fun disallow(player: OfflinePlayer) = disallow(player.uuid)
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
fun ban(player: OfflinePlayer) = ban(player.uuid)
fun unban(player: OfflinePlayer) = unban(player.uuid)
}
interface ParcelData : AddedData {
var owner: ParcelOwner?
val since: DateTime?
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
@@ -83,6 +57,8 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData {
override fun isAllowed(uuid: UUID) = data.isAllowed(uuid)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = data.canBuild(player)
override val since: DateTime? get() = data.since
override var owner: ParcelOwner?
get() = data.owner
set(value) {
@@ -94,7 +70,7 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData {
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
return data.setAddedStatus(uuid, status).also {
if (it) world.storage.setParcelPlayerState(this, uuid, status.asBoolean)
if (it) world.storage.setParcelPlayerStatus(this, uuid, status)
}
}
@@ -117,16 +93,9 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData {
var hasBlockVisitors: Boolean = false; private set
}
open class AddedDataHolder(override var added: MutableMap<UUID, AddedStatus>
= mutableMapOf<UUID, AddedStatus>()) : AddedData {
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
}
class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var owner: ParcelOwner? = null
override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -135,55 +104,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var allowInteractInventory = true
}
enum class AddedStatus {
DEFAULT,
ALLOWED,
BANNED;
val asBoolean
get() = when (this) {
DEFAULT -> null
ALLOWED -> true
BANNED -> false
}
}
@Suppress("UsePropertyAccessSyntax")
class ParcelOwner(val uuid: UUID? = null,
name: String? = null,
val since: DateTime? = null) {
companion object {
fun create(uuid: UUID?, name: String?, time: DateTime? = null): ParcelOwner? {
return uuid?.let { ParcelOwner(uuid, name, time) }
?: name?.let { ParcelOwner(uuid, name, time) }
}
}
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.isValid }
this.name = offlinePlayer?.name
}
}
val playerName get() = getPlayerName(uuid, name)
fun matches(player: OfflinePlayer, 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.isValid }
}

View File

@@ -0,0 +1,53 @@
package io.dico.parcels2
import io.dico.parcels2.util.getPlayerNameOrDefault
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import java.util.*
@Suppress("UsePropertyAccessSyntax")
class ParcelOwner private constructor(val uuid: UUID?,
name: String?) {
var name: String? = name
get() = field ?: getPlayerNameOrDefault(uuid!!).also { field = it }
private set
constructor(name: String) : this(null, name)
constructor(uuid: UUID) : this(uuid, null)
constructor(player: OfflinePlayer) : this(player.uuid, player.name)
companion object {
fun nameless(player: OfflinePlayer) = ParcelOwner(player.uuid, null)
}
inline val hasUUID: Boolean get() = uuid != null
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.isValid }
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
fun equals(other: ParcelOwner): Boolean {
return if (hasUUID) other.hasUUID && uuid == other.uuid
else !other.hasUUID && name == other.name
}
override fun equals(other: Any?): Boolean {
return other is ParcelOwner && equals(other)
}
override fun hashCode(): Int {
return if (hasUUID) uuid!!.hashCode() else name!!.hashCode()
}
}

View File

@@ -219,7 +219,7 @@ class DefaultParcelContainer(private val world: ParcelWorld,
}
fun loadAllData() {
val channel = storage.readParcelData(allParcels(), 100)
val channel = storage.readParcelData(allParcels())
launch(storage.asyncDispatcher) {
for ((parcel, data) in channel) {
data?.let { parcel.copyDataIgnoringDatabase(it) }

View File

@@ -10,7 +10,9 @@ import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.FunctionHelper
import io.dico.parcels2.util.tryCreate
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.bukkit.Bukkit
import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.LoggerFactory
@@ -25,20 +27,15 @@ class ParcelsPlugin : JavaPlugin() {
lateinit var options: Options; private set
lateinit var worlds: Worlds; private set
lateinit var storage: Storage; private set
lateinit var globalAddedData: GlobalAddedDataManager; private set
val registrator = Registrator(this)
lateinit var entityTracker: ParcelEntityTracker; private set
private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
val mainThreadDispatcher = object : Executor {
private val mainThread = Thread.currentThread()
override fun execute(command: Runnable) {
if (Thread.currentThread() === mainThread) command.run()
else server.scheduler.runTask(this@ParcelsPlugin, command)
}
}
val functionHelper: FunctionHelper = FunctionHelper(this)
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
override fun onEnable() {
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
@@ -73,6 +70,7 @@ class ParcelsPlugin : JavaPlugin() {
return false
}
globalAddedData = GlobalAddedDataManagerImpl(this)
entityTracker = ParcelEntityTracker(worlds)
registerListeners()
registerCommands()

View File

@@ -218,7 +218,7 @@ class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o
val sign = signBlock.state as org.bukkit.block.Sign
sign.setLine(0, parcel.id)
sign.setLine(2, owner.playerName)
sign.setLine(2, owner.name)
sign.update()
skullBlock.type = Material.PLAYER_HEAD

View File

@@ -64,4 +64,4 @@ val attachables: Set<Material> = EnumSet.of(
WALL_SIGN,
LILY_PAD,
DANDELION
);
)

View File

@@ -6,6 +6,7 @@ import io.dico.parcels2.util.get
import org.bukkit.World
import org.bukkit.block.data.BlockData
// TODO order paste such that attachables are placed after the block they depend on
class Schematic {
val size: Vec3i get() = _size!!
private var _size: Vec3i? = null

View File

@@ -1,11 +1,12 @@
package io.dico.parcels2.blockvisitor
import kotlinx.coroutines.experimental.*
import org.bukkit.plugin.Plugin
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.FunctionHelper
import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.Job
import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis
import java.util.*
import java.util.concurrent.Executor
import java.util.logging.Level
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
@@ -101,9 +102,7 @@ private interface WorkerContinuation : Worker, WorkerScope {
* There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick
* This object attempts to split that maximum amount of milliseconds equally between all jobs
*/
class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// Coroutine dispatcher for jobs
private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher()
class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null
// The workers.
@@ -111,9 +110,9 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO
override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker {
val worker: WorkerContinuation = WorkerImpl(plugin, dispatcher, task)
val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task)
_workers.addFirst(worker)
if (bukkitTask == null) bukkitTask = plugin.server.scheduler.runTaskTimer(plugin, ::tickJobs, 0, options.tickInterval.toLong())
if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() }
return worker
}
@@ -146,8 +145,7 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO
}
private class WorkerImpl(val plugin: Plugin,
val dispatcher: CoroutineDispatcher,
private class WorkerImpl(val functionHelper: FunctionHelper,
val task: TimeLimitedTask) : WorkerContinuation {
override var job: Job? = null; private set
@@ -179,7 +177,7 @@ private class WorkerImpl(val plugin: Plugin,
// report any error that occurred
completionException = exception?.also {
if (it !is CancellationException)
plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${plugin.name} generated an exception", it)
functionHelper.plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${functionHelper.plugin.name} generated an exception", it)
}
// convert to elapsed time here
@@ -236,10 +234,9 @@ private class WorkerImpl(val plugin: Plugin,
}
try {
launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) {
initJob(job = kotlin.coroutines.experimental.coroutineContext[Job]!!)
task()
}
val job = functionHelper.launchLazilyOnMainThread { task() }
initJob(job = job)
job.start()
} catch (t: Throwable) {
// do nothing: handled by job.invokeOnCompletion()
}

View File

@@ -4,6 +4,7 @@ import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin
@@ -84,12 +85,22 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("clear")
@ParcelRequire(owner = true)
fun ParcelScope.cmdClear(context: ExecutionContext) {
fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
if (!sure) return "Are you sure? You cannot undo this action!\n" +
"Type ${context.rawInput} -sure if you want to go through with this."
world.generator.clearParcel(parcel)
.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0))
}
return null
}
@Cmd("swap")
fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
}
@Cmd("make_mess")

View File

@@ -0,0 +1,23 @@
package io.dico.parcels2.command
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
import kotlinx.coroutines.experimental.Deferred
interface ParcelTarget {
val world: ParcelWorld
val isByID: Boolean
val isByOwner: Boolean get() = !isByID
suspend fun ParcelsPlugin.await(): Parcel?
fun ParcelsPlugin.get(): Deferred<Parcel?> =
}
class ParcelTargetByOwner : ParcelTarget {
override val isByID get() = false
}
class ParcelTargetByID : ParcelTarget {
override val isByID get() = true
}

View File

@@ -7,6 +7,7 @@ import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.Worlds
import io.dico.parcels2.util.isValid
import org.bukkit.Bukkit

View File

@@ -0,0 +1,23 @@
package io.dico.parcels2.listener
import io.dico.dicore.RegistratorListener
import io.dico.parcels2.ParcelsPlugin
import org.bukkit.event.Event
interface HasPlugin {
val plugin: ParcelsPlugin
}
inline fun <reified T: Event> HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener<T> { event ->
}

View File

@@ -37,15 +37,17 @@ interface Backing {
suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?)
suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?)
suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus)
suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean)
suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean)
suspend fun ProducerScope<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>.produceAllGlobalAddedData()
suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus>
suspend fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)
suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)
}

View File

@@ -1,322 +0,0 @@
package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
import io.dico.parcels2.util.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 org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.joda.time.DateTime
import java.util.*
import javax.sql.DataSource
object WorldsT : Table("worlds") {
val id = integer("world_id").autoIncrement().primaryKey()
val name = varchar("name", 50)
val uid = binary("uid", 16)
val index_uid = uniqueIndexR("index_uid", uid)
}
object ParcelsT : Table("parcels") {
val id = integer("parcel_id").autoIncrement().primaryKey()
val px = integer("px")
val pz = integer("pz")
val world_id = integer("world_id").references(WorldsT.id)
val owner_uuid = binary("owner_uuid", 16).nullable()
val owner_name = varchar("owner_name", 16).nullable()
val claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz)
}
object AddedLocalT : Table("parcels_added_local") {
val parcel_id = integer("parcel_id").references(ParcelsT.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", parcel_id, player_uuid)
}
object AddedGlobalT : Table("parcels_added_global") {
val owner_uuid = binary("owner_uuid", 16)
val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", owner_uuid, player_uuid)
}
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().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(private val dataSourceFactory: () -> DataSource) : Backing {
override val name get() = "Exposed"
private var dataSource: DataSource? = null
private var database: Database? = null
private var isShutdown: Boolean = false
override val isConnected get() = database != null
companion object {
init {
Database.registerDialect("mariadb") {
Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
}
}
}
override suspend fun init() {
if (isShutdown) throw IllegalStateException()
dataSource = dataSourceFactory()
database = Database.connect(dataSource!!)
transaction(database) {
create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT)
}
}
override suspend fun shutdown() {
if (isShutdown) throw IllegalStateException()
dataSource?.let {
if (it is HikariDataSource) it.close()
}
database = null
isShutdown = true
}
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.insert /*Ignore*/ { 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.insert /*Ignore*/ { 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 ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() {
ParcelsT.selectAll().forEach { row ->
val parcel = rowToSerializableParcel(row) ?: return@forEach
val data = rowToParcelData(row)
channel.send(parcel to data)
}
channel.close()
}
override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction {
val row = getParcelRow(parcelFor) ?: return@transaction null
rowToParcelData(row)
}
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)
.orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(::rowToSerializableParcel)
.toList()
}
override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) {
if (data == null) {
transaction {
getParcelId(parcelFor)?.let { id ->
ParcelsT.deleteIgnoreWhere() { 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 time = owner?.let { DateTime.now() }
val id = if (owner == null)
getParcelId(parcelFor) ?: return@transaction
else
getOrInitParcelId(parcelFor)
ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_uuid] = binaryUuid
it[ParcelsT.owner_name] = name
it[ParcelsT.claim_time] = time
}
}
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.upsert(AddedLocalT.parcel_id) {
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.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inventory] = value
}*/
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
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.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inputs] = value
}
}
override suspend fun readGlobalAddedData(owner: ParcelOwner): AddedData? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override suspend fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: Boolean?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private fun rowToSerializableParcel(row: ResultRow): SerializableParcel? {
val worldId = row[ParcelsT.world_id]
val worldRow = WorldsT.select { WorldsT.id eq worldId }.firstOrNull()
?: return null
val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID())
return SerializableParcel(world, Vec2i(row[ParcelsT.px], row[ParcelsT.pz]))
}
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = ParcelOwner.create(
uuid = row[ParcelsT.owner_uuid]?.toUUID(),
name = row[ParcelsT.owner_name],
time = row[ParcelsT.claim_time]
)
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]
}
}
}

View File

@@ -24,9 +24,9 @@ interface Storage {
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
fun readParcelData(parcelsFor: Sequence<Parcel>): ReceiveChannel<Pair<Parcel, ParcelData?>>
fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>>
fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>>
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
@@ -37,13 +37,15 @@ interface Storage {
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Job
fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus): Job
fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job
fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job
fun readAllGlobalAddedData(): ReceiveChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>
fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?>
fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job
@@ -55,6 +57,7 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
override val isConnected get() = backing.isConnected
val channelCapacity = 16
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
@@ -73,10 +76,10 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) =
override fun readParcelData(parcelsFor: Sequence<Parcel>) =
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } }
override fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> =
override fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> =
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
@@ -87,14 +90,17 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) }
override fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcelFor, player, status) }
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
override fun readAllGlobalAddedData(): ReceiveChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>> =
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllGlobalAddedData() } }
override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?> = defer { backing.readGlobalAddedData(owner) }
override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalAddedStatus(owner, player, status) }
override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) }
}

View File

@@ -2,6 +2,7 @@ package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions
import io.dico.parcels2.storage.exposed.ExposedBacking
import kotlin.reflect.KClass
interface StorageFactory {
@@ -35,7 +36,7 @@ class ConnectionStorageFactory : StorageFactory {
override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = { HikariDataSource(hikariConfig) }
val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}

View File

@@ -0,0 +1,186 @@
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName")
package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
import io.dico.parcels2.storage.*
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 org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.joda.time.DateTime
import java.util.*
import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message)
class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : Backing {
override val name get() = "Exposed"
private var dataSource: DataSource? = null
private var database: Database? = null
private var isShutdown: Boolean = false
override val isConnected get() = database != null
companion object {
init {
Database.registerDialect("mariadb") {
Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
}
}
}
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
override suspend fun init() {
if (isShutdown) throw IllegalStateException()
dataSource = dataSourceFactory()
database = Database.connect(dataSource!!)
transaction(database) {
create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
}
}
override suspend fun shutdown() {
if (isShutdown) throw IllegalStateException()
dataSource?.let {
(it as? HikariDataSource)?.close()
}
database = null
isShutdown = true
}
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 ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() {
ParcelsT.selectAll().forEach { row ->
val parcel = ParcelsT.getSerializable(row) ?: return@forEach
val data = rowToParcelData(row)
channel.send(parcel to data)
}
channel.close()
}
override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction {
val row = ParcelsT.getRow(parcelFor) ?: return@transaction null
rowToParcelData(row)
}
override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> = transaction {
val user_id = OwnersT.getId(user) ?: return@transaction emptyList()
ParcelsT.select { ParcelsT.owner_id eq user_id }
.orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(ParcelsT::getSerializable)
.toList()
}
override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) {
if (data == null) {
transaction {
ParcelsT.getId(parcelFor)?.let { id ->
ParcelsT.deleteIgnoreWhere { 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
}
transaction {
val id = ParcelsT.getOrInitId(parcelFor)
AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id }
}
setParcelOwner(parcelFor, data.owner)
for ((uuid, status) in data.added) {
setLocalPlayerStatus(parcelFor, uuid, status)
}
setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs)
setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory)
}
override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction {
val id = if (owner == null)
ParcelsT.getId(parcelFor) ?: return@transaction
else
ParcelsT.getOrInitId(parcelFor)
val owner_id = owner?.let { OwnersT.getOrInitId(it) }
val time = owner?.let { DateTime.now() }
ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_id] = owner_id
it[claim_time] = time
}
}
override suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = transaction {
AddedLocalT.setPlayerStatus(parcelFor, player, status)
}
override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction {
val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inventory] = value
}
}
override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction {
val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inputs] = value
}
}
override suspend fun ProducerScope<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>.produceAllGlobalAddedData() {
AddedGlobalT.sendAllAddedData(channel)
channel.close()
}
override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> {
return AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return hashMapOf())
}
override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction {
AddedGlobalT.setPlayerStatus(owner, player, status)
}
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { OwnersT.getSerializable(it) }
since = row[ParcelsT.claim_time]
val parcelId = row[ParcelsT.id]
added = AddedLocalT.readAddedData(parcelId)
AddedLocalT.select { AddedLocalT.attach_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]
}
}
}

View File

@@ -0,0 +1,121 @@
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate")
package io.dico.parcels2.storage.exposed
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.storage.SerializableParcel
import io.dico.parcels2.storage.SerializableWorld
import io.dico.parcels2.storage.uniqueIndexR
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import java.util.*
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj, SerializableObj>,
QueryObj, SerializableObj>(tableName: String, columnName: String)
: Table(tableName) {
val id = integer(columnName).autoIncrement().primaryKey()
@Suppress("UNCHECKED_CAST")
inline val table: TableT
get() = this as TableT
internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op<Boolean>): Int? {
return select { where(table) }.firstOrNull()?.let { it[id] }
}
internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement<Number>) -> Unit): Int {
return table.insert(body)[id] ?: insertError(objName)
}
private inline fun insertError(obj: String): Nothing = throw ExposedDatabaseException("This should not happen - failed to insert $obj and get its id")
abstract fun getId(obj: QueryObj): Int?
abstract fun getOrInitId(obj: QueryObj): Int
fun getSerializable(id: Int): SerializableObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getSerializable(it) }
abstract fun getSerializable(row: ResultRow): SerializableObj?
}
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorld, SerializableWorld>("parcel_worlds", "world_id") {
val name = varchar("name", 50)
val uid = binary("uid", 2)
val index_uid = uniqueIndexR("index_uid", uid)
internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid }
internal inline fun getId(uid: UUID): Int? = getId(uid.toByteArray())
internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid ->
getId(binaryUid)
?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName }
}
override fun getId(world: ParcelWorld): Int? = getId(world.world.uid)
override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) }
override fun getSerializable(row: ResultRow): SerializableWorld {
return SerializableWorld(row[name], row[uid].toUUID())
}
}
object ParcelsT : IdTransactionsTable<ParcelsT, Parcel, SerializableParcel>("parcels", "parcel_id") {
val world_id = integer("world_id").references(WorldsT.id)
val px = integer("px")
val pz = integer("pz")
val owner_id = integer("owner_id").references(OwnersT.id).nullable()
val claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz)
private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) }
private inline fun getId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldUid)?.let { getId(it, parcelX, parcelZ) }
private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int {
val worldId = WorldsT.getOrInitId(worldUid, worldName)
return getId(worldId, parcelX, parcelZ)
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }
}
override fun getId(parcel: Parcel): Int? = getId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z)
override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.z) }
private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
fun getRow(parcel: Parcel): ResultRow? = getId(parcel)?.let { getRow(it) }
override fun getSerializable(row: ResultRow): SerializableParcel? {
val worldId = row[world_id]
val world = WorldsT.getSerializable(worldId) ?: return null
return SerializableParcel(world, Vec2i(row[px], row[pz]))
}
}
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner, ParcelOwner>("parcel_owners", "owner_id") {
val uuid = binary("uuid", 2).nullable()
val name = varchar("name", 32).nullable()
val index_pair = uniqueIndexR("index_pair", uuid, name)
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
private inline fun getId(name: String) = getId { OwnersT.name eq name }
private inline fun getOrInitId(uuid: UUID) = uuid.toByteArray().let { binaryUuid ->
getId(binaryUuid)
?: insertAndGetId("owner(uuid = $uuid)") { it[OwnersT.uuid] = binaryUuid }
}
private inline fun getOrInitId(name: String) =
getId(name)
?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name }
override fun getId(owner: ParcelOwner): Int? =
if (owner.hasUUID) getId(owner.uuid!!)
else getId(owner.name!!)
override fun getOrInitId(owner: ParcelOwner): Int =
if (owner.hasUUID) getOrInitId(owner.uuid!!)
else getOrInitId(owner.name!!)
override fun getSerializable(row: ResultRow): ParcelOwner {
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]!!)
}
}

View File

@@ -0,0 +1,104 @@
@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE")
package io.dico.parcels2.storage.exposed
import io.dico.parcels2.AddedStatus
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.storage.SerializableParcel
import io.dico.parcels2.storage.uniqueIndexR
import io.dico.parcels2.storage.upsert
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.SendChannel
import org.jetbrains.exposed.sql.*
import java.util.*
object AddedLocalT : AddedTable<Parcel, SerializableParcel>("parcels_added_local", ParcelsT)
object AddedGlobalT : AddedTable<ParcelOwner, ParcelOwner>("parcels_added_global", OwnersT)
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_inventory = bool("interact_inventory").default(false)
val interact_inputs = bool("interact_inputs").default(false)
}
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableMap<UUID, AddedStatus>>>
sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTransactionsTable<*, AttachT, SerializableT>) : Table(name) {
val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", attach_id, player_uuid)
fun setPlayerStatus(attachedOn: AttachT, player: UUID, status: AddedStatus) {
val binaryUuid = player.toByteArray()
if (status.isDefault) {
idTable.getId(attachedOn)?.let { id ->
deleteWhere { (attach_id eq id) and (player_uuid eq binaryUuid) }
}
return
}
val id = idTable.getOrInitId(attachedOn)
upsert(conflictIndex = index_pair) {
it[attach_id] = id
it[player_uuid] = binaryUuid
it[allowed_flag] = status.isAllowed
}
}
fun readAddedData(id: Int): MutableMap<UUID, AddedStatus> {
return slice(player_uuid, allowed_flag).select { attach_id eq id }
.associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() })
}
suspend fun sendAllAddedData(channel: AddedStatusSendChannel<SerializableT>) {
val iterator = selectAll().orderBy(attach_id).iterator()
if (iterator.hasNext()) {
val firstRow = iterator.next()
var id: Int = firstRow[attach_id]
var attach: SerializableT? = null
var map: MutableMap<UUID, AddedStatus>? = null
fun initAttachAndMap() {
attach = idTable.getSerializable(id)
map = attach?.let { mutableMapOf() }
}
suspend fun sendIfPresent() {
if (attach != null && map != null && map!!.isNotEmpty()) {
channel.send(attach!! to map!!)
}
attach = null
map = null
}
initAttachAndMap()
for (row in iterator) {
val rowId = row[attach_id]
if (rowId != id) {
sendIfPresent()
id = rowId
initAttachAndMap()
}
if (attach == null) {
continue // owner not found for this owner id
}
val player_uuid = row[player_uuid].toUUID()
val status = row[allowed_flag].asAddedStatus()
map!![player_uuid] = status
}
sendIfPresent()
}
}
private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED
}

View File

@@ -49,8 +49,6 @@ class AwaitTask : Runnable {
onSuccess!!.invoke()
}
elapsedChecks++
if (maxChecks in 1 until elapsedChecks) {
cancel()
onFailure?.invoke()

View File

@@ -0,0 +1,53 @@
package io.dico.parcels2.util
import io.dico.parcels2.ParcelsPlugin
import kotlinx.coroutines.experimental.*
import org.bukkit.scheduler.BukkitTask
import kotlin.coroutines.experimental.CoroutineContext
@Suppress("NOTHING_TO_INLINE")
class FunctionHelper(val plugin: ParcelsPlugin) {
val mainThreadDispatcher: MainThreadDispatcher = MainThreadDispatcherImpl()
fun <T> deferLazilyOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
}
fun <T> deferUndispatchedOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = mainThreadDispatcher, start = CoroutineStart.UNDISPATCHED, block = block)
}
fun launchLazilyOnMainThread(block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
}
inline fun schedule(noinline task: () -> Unit) = schedule(0, task)
fun schedule(delay: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
}
fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
}
abstract class MainThreadDispatcher : CoroutineDispatcher() {
abstract val mainThread: Thread
abstract fun runOnMainThread(task: Runnable)
}
private inner class MainThreadDispatcherImpl : MainThreadDispatcher() {
override val mainThread: Thread = Thread.currentThread()
override fun dispatch(context: CoroutineContext, block: Runnable) {
runOnMainThread(block)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun runOnMainThread(task: Runnable) {
if (Thread.currentThread() === mainThread) task.run()
else plugin.server.scheduler.runTaskLater(plugin, task, 0)
}
}
}

View File

@@ -6,21 +6,21 @@ import java.nio.ByteBuffer
import java.util.*
@Suppress("UsePropertyAccessSyntax")
fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name }
fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {
return uuid
?.let { getPlayerName(it) }
?: ifUnknown
?: ":unknown_name:"
}
@Contract("null -> null; !null -> !null", pure = true)
fun UUID?.toByteArray(): ByteArray? = this?.let {
fun getPlayerName(uuid: UUID): String? {
return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name
}
fun UUID.toByteArray(): ByteArray =
ByteBuffer.allocate(16).apply {
putLong(mostSignificantBits)
putLong(leastSignificantBits)
}.array()
}
@Contract("null -> null; !null -> !null", pure = true)
fun ByteArray?.toUUID(): UUID? = this?.let {
ByteBuffer.wrap(it).run { UUID(long, long) }
}
fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) }

View File

@@ -16,4 +16,36 @@ data class Vec3i(
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
/*
inline class IVec3i(private val data: Long) {
private companion object {
const val mask = 0x001F_FFFF
const val max: Int = 0x000F_FFFF // +1048575
const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000
@Suppress("NOTHING_TO_INLINE")
inline fun Int.compressIntoLong(offset: Int): Long {
if (this !in min..max) throw IllegalArgumentException()
return and(mask).toLong().shl(offset)
}
@Suppress("NOTHING_TO_INLINE")
inline fun Long.extractInt(offset: Int): Int {
return ushr(offset).toInt().and(mask)
}
}
constructor(x: Int, y: Int, z: Int) : this(
x.compressIntoLong(42)
or y.compressIntoLong(21)
or z.compressIntoLong(0))
val x: Int get() = data.extractInt(42)
val y: Int get() = data.extractInt(21)
val z: Int get() = data.extractInt(0)
}
*/