diff options
author | Dico Karssiens <dico.karssiens@gmail.com> | 2018-08-01 16:45:27 +0100 |
---|---|---|
committer | Dico Karssiens <dico.karssiens@gmail.com> | 2018-08-01 16:45:27 +0100 |
commit | 472e700e0422d1829aa26e04b74e2077807e75f0 (patch) | |
tree | ec6787ff6c0841989951ef65edc3e792ae067b36 | |
parent | 1ec6dd136b678a312d5865ef1fdfd994d58796d3 (diff) |
Improve database abstractions, add GlobalAddedData, some other things
26 files changed, 726 insertions, 487 deletions
diff --git a/build.gradle.kts b/build.gradle.kts index 878e17c..11ead8b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ project(":dicore3:dicore3-command") { dependencies { c.kotlinStd(kotlin("stdlib-jdk8")) 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("com.thoughtworks.paranamer:paranamer:2.8") @@ -86,6 +86,8 @@ tasks { val compileKotlin by getting(KotlinCompile::class) { kotlinOptions { javaParameters = true + suppressWarnings = true + //freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental") } } diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt new file mode 100644 index 0000000..7bcf8f1 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -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 +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt index eb00c52..055e681 100644 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt @@ -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?>() - - operator fun get(player: OfflinePlayer) = get(player.uuid) - - operator fun get(uuid: UUID): GlobalAddedData? { - - } - - fun getDeferred(uuid: UUID): Deferred<AddedData> { - get(uuid)?.let { return CompletableDeferred(it) } +interface GlobalAddedDataManager { + operator fun get(owner: ParcelOwner): GlobalAddedData +} - } +class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { + private val map = mutableMapOf<ParcelOwner, GlobalAddedData>() - 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 + override fun get(owner: ParcelOwner): GlobalAddedData { + return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it } } - 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) { - - diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 1d323dd..c8e7713 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -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 } -} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt new file mode 100644 index 0000000..c602ff3 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt @@ -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() + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 9a50b31..7bb827c 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -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) } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 53a2d15..d55320e 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -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() diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt index 72ca3bd..04e9a26 100644 --- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt @@ -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 diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index c046940..e753295 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -64,4 +64,4 @@ val attachables: Set<Material> = EnumSet.of( WALL_SIGN, LILY_PAD, DANDELION -);
\ No newline at end of file +)
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index 41df083..c375e5a 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -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 diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index 45196f2..7c5f2c5 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -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() } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 78a989e..fa9a696 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -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") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt new file mode 100644 index 0000000..e9472bc --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt @@ -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 + +} diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt index ab97023..dcc3f8c 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt @@ -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 diff --git a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt new file mode 100644 index 0000000..97d045f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt @@ -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 -> + + + + + + + + + +} + + diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 7f00976..7224dd1 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -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) }
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt deleted file mode 100644 index 1a8a252..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt +++ /dev/null @@ -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] - } - } - -} - - - - - - - diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 2323571..36f241e 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -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) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt index bb5013a..90992c6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt @@ -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)) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt new file mode 100644 index 0000000..affa14e --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -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] + } + } + +} + diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt new file mode 100644 index 0000000..c75ea9f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -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]!!) + } +} diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt new file mode 100644 index 0000000..5c6ce25 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -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 + +} diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt index 4ca549f..0df14e7 100644 --- a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt +++ b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt @@ -49,8 +49,6 @@ class AwaitTask : Runnable { onSuccess!!.invoke() } - elapsedChecks++ - if (maxChecks in 1 until elapsedChecks) { cancel() onFailure?.invoke() diff --git a/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt new file mode 100644 index 0000000..ea16652 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt @@ -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) + } + } + +} diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt index 10fbbbb..b93dec2 100644 --- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt @@ -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) } -}
\ No newline at end of file +fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) } diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt index a4655d0..694f2aa 100644 --- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt @@ -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)
\ No newline at end of file +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) + +} +*/ |