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

@@ -51,7 +51,7 @@ project(":dicore3:dicore3-command") {
dependencies { dependencies {
c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.23.4")) c.kotlinStd(kotlinx("coroutines-core:0.24.0"))
compile(project(":dicore3:dicore3-core")) compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8") compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -86,6 +86,8 @@ tasks {
val compileKotlin by getting(KotlinCompile::class) { val compileKotlin by getting(KotlinCompile::class) {
kotlinOptions { kotlinOptions {
javaParameters = true javaParameters = true
suppressWarnings = true
//freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental")
} }
} }

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 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.* import java.util.*
interface GlobalAddedData : AddedData { interface GlobalAddedData : AddedData {
val uuid: UUID val owner: ParcelOwner
} }
class GlobalAddedDataManager(val plugin: ParcelsPlugin) { interface GlobalAddedDataManager {
private val map = mutableMapOf<UUID, GlobalAddedData?>() operator fun get(owner: ParcelOwner): GlobalAddedData
}
operator fun get(player: OfflinePlayer) = get(player.uuid) class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
private val map = mutableMapOf<ParcelOwner, GlobalAddedData>()
operator fun get(uuid: UUID): GlobalAddedData? {
override fun get(owner: ParcelOwner): GlobalAddedData {
return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it }
} }
fun getDeferred(uuid: UUID): Deferred<AddedData> { private inner class GlobalAddedDataImpl(override val owner: ParcelOwner,
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,
data: MutableMap<UUID, AddedStatus> = emptyData) data: MutableMap<UUID, AddedStatus> = emptyData)
: AddedDataHolder(data), GlobalAddedData { : AddedDataHolder(data), GlobalAddedData {
@@ -45,7 +32,7 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) {
data = mutableMapOf() data = mutableMapOf()
} }
return super.setAddedStatus(uuid, status).also { 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 package io.dico.parcels2
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.hasBuildAnywhere 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.Bukkit
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.* 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 { interface ParcelData : AddedData {
var owner: ParcelOwner? var owner: ParcelOwner?
val since: DateTime?
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean 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 isAllowed(uuid: UUID) = data.isAllowed(uuid)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = data.canBuild(player) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = data.canBuild(player)
override val since: DateTime? get() = data.since
override var owner: ParcelOwner? override var owner: ParcelOwner?
get() = data.owner get() = data.owner
set(value) { set(value) {
@@ -94,7 +70,7 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData {
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
return data.setAddedStatus(uuid, status).also { 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 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 { class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var owner: ParcelOwner? = null override var owner: ParcelOwner? = null
override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -135,55 +104,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var allowInteractInventory = true 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() { fun loadAllData() {
val channel = storage.readParcelData(allParcels(), 100) val channel = storage.readParcelData(allParcels())
launch(storage.asyncDispatcher) { launch(storage.asyncDispatcher) {
for ((parcel, data) in channel) { for ((parcel, data) in channel) {
data?.let { parcel.copyDataIgnoringDatabase(it) } 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.listener.ParcelListeners
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.FunctionHelper
import io.dico.parcels2.util.tryCreate import io.dico.parcels2.util.tryCreate
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -25,20 +27,15 @@ class ParcelsPlugin : JavaPlugin() {
lateinit var options: Options; private set lateinit var options: Options; private set
lateinit var worlds: Worlds; private set lateinit var worlds: Worlds; private set
lateinit var storage: Storage; private set lateinit var storage: Storage; private set
lateinit var globalAddedData: GlobalAddedDataManager; private set
val registrator = Registrator(this) val registrator = Registrator(this)
lateinit var entityTracker: ParcelEntityTracker; private set lateinit var entityTracker: ParcelEntityTracker; private set
private var listeners: ParcelListeners? = null private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null private var cmdDispatcher: ICommandDispatcher? = null
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
val mainThreadDispatcher = object : Executor { val functionHelper: FunctionHelper = FunctionHelper(this)
private val mainThread = Thread.currentThread() val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
override fun execute(command: Runnable) {
if (Thread.currentThread() === mainThread) command.run()
else server.scheduler.runTask(this@ParcelsPlugin, command)
}
}
override fun onEnable() { override fun onEnable() {
plogger.info("Debug enabled: ${plogger.isDebugEnabled}") plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
@@ -73,6 +70,7 @@ class ParcelsPlugin : JavaPlugin() {
return false return false
} }
globalAddedData = GlobalAddedDataManagerImpl(this)
entityTracker = ParcelEntityTracker(worlds) entityTracker = ParcelEntityTracker(worlds)
registerListeners() registerListeners()
registerCommands() 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 val sign = signBlock.state as org.bukkit.block.Sign
sign.setLine(0, parcel.id) sign.setLine(0, parcel.id)
sign.setLine(2, owner.playerName) sign.setLine(2, owner.name)
sign.update() sign.update()
skullBlock.type = Material.PLAYER_HEAD skullBlock.type = Material.PLAYER_HEAD

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
package io.dico.parcels2.blockvisitor package io.dico.parcels2.blockvisitor
import kotlinx.coroutines.experimental.* import io.dico.parcels2.ParcelsPlugin
import org.bukkit.plugin.Plugin import io.dico.parcels2.util.FunctionHelper
import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.Job
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import java.util.* import java.util.*
import java.util.concurrent.Executor
import java.util.logging.Level import java.util.logging.Level
import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED 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 * 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 * This object attempts to split that maximum amount of milliseconds equally between all jobs
*/ */
class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeOptions) : WorktimeLimiter() { class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// Coroutine dispatcher for jobs
private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher()
// The currently registered bukkit scheduler task // The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null private var bukkitTask: BukkitTask? = null
// The workers. // The workers.
@@ -111,9 +110,9 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO
override val workers: List<Worker> = _workers override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker { override fun submit(task: TimeLimitedTask): Worker {
val worker: WorkerContinuation = WorkerImpl(plugin, dispatcher, task) val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task)
_workers.addFirst(worker) _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 return worker
} }
@@ -146,8 +145,7 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO
} }
private class WorkerImpl(val plugin: Plugin, private class WorkerImpl(val functionHelper: FunctionHelper,
val dispatcher: CoroutineDispatcher,
val task: TimeLimitedTask) : WorkerContinuation { val task: TimeLimitedTask) : WorkerContinuation {
override var job: Job? = null; private set override var job: Job? = null; private set
@@ -179,7 +177,7 @@ private class WorkerImpl(val plugin: Plugin,
// report any error that occurred // report any error that occurred
completionException = exception?.also { completionException = exception?.also {
if (it !is CancellationException) 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 // convert to elapsed time here
@@ -236,10 +234,9 @@ private class WorkerImpl(val plugin: Plugin,
} }
try { try {
launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) { val job = functionHelper.launchLazilyOnMainThread { task() }
initJob(job = kotlin.coroutines.experimental.coroutineContext[Job]!!) initJob(job = job)
task() job.start()
}
} catch (t: Throwable) { } catch (t: Throwable) {
// do nothing: handled by job.invokeOnCompletion() // 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.ExecutionContext
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
@@ -84,12 +85,22 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("clear") @Cmd("clear")
@ParcelRequire(owner = true) @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) world.generator.clearParcel(parcel)
.onProgressUpdate(1000, 1000) { progress, elapsedTime -> .onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed" context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0)) .format(progress * 100, elapsedTime / 1000.0))
} }
return null
}
@Cmd("swap")
fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
} }
@Cmd("make_mess") @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.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.Worlds import io.dico.parcels2.Worlds
import io.dico.parcels2.util.isValid import io.dico.parcels2.util.isValid
import org.bukkit.Bukkit 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 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 setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean)
suspend fun setParcelAllowsInteractInputs(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 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(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>> fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
@@ -37,13 +37,15 @@ interface Storage {
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job 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 setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job
fun setParcelAllowsInteractInputs(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 readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?>
fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job 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 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()
override val isConnected get() = backing.isConnected override val isConnected get() = backing.isConnected
val channelCapacity = 16
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> { 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(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) } } 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() } } produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } 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 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 setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(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 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 com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions import io.dico.parcels2.DataConnectionOptions
import io.dico.parcels2.storage.exposed.ExposedBacking
import kotlin.reflect.KClass import kotlin.reflect.KClass
interface StorageFactory { interface StorageFactory {
@@ -35,7 +36,7 @@ class ConnectionStorageFactory : StorageFactory {
override fun newStorageInstance(dialect: String, options: Any): Storage { override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions) val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = { HikariDataSource(hikariConfig) } val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) 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() onSuccess!!.invoke()
} }
elapsedChecks++
if (maxChecks in 1 until elapsedChecks) { if (maxChecks in 1 until elapsedChecks) {
cancel() cancel()
onFailure?.invoke() 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.* import java.util.*
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String { fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {
return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name } return uuid
?.let { getPlayerName(it) }
?: ifUnknown ?: ifUnknown
?: ":unknown_name:" ?: ":unknown_name:"
} }
@Contract("null -> null; !null -> !null", pure = true) fun getPlayerName(uuid: UUID): String? {
fun UUID?.toByteArray(): ByteArray? = this?.let { return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name
}
fun UUID.toByteArray(): ByteArray =
ByteBuffer.allocate(16).apply { ByteBuffer.allocate(16).apply {
putLong(mostSignificantBits) putLong(mostSignificantBits)
putLong(leastSignificantBits) putLong(leastSignificantBits)
}.array() }.array()
}
@Contract("null -> null; !null -> !null", pure = true) fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) }
fun ByteArray?.toUUID(): UUID? = this?.let {
ByteBuffer.wrap(it).run { UUID(long, long) }
}

View File

@@ -17,3 +17,35 @@ data class Vec3i(
@Suppress("NOTHING_TO_INLINE") @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)
}
*/