summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico Karssiens <dico.karssiens@gmail.com>2018-08-01 16:45:27 +0100
committerDico Karssiens <dico.karssiens@gmail.com>2018-08-01 16:45:27 +0100
commit472e700e0422d1829aa26e04b74e2077807e75f0 (patch)
treeec6787ff6c0841989951ef65edc3e792ae067b36
parent1ec6dd136b678a312d5865ef1fdfd994d58796d3 (diff)
Improve database abstractions, add GlobalAddedData, some other things
-rw-r--r--build.gradle.kts4
-rw-r--r--src/main/kotlin/io/dico/parcels2/AddedData.kt47
-rw-r--r--src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt39
-rw-r--r--src/main/kotlin/io/dico/parcels2/Parcel.kt93
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelOwner.kt53
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelWorld.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt14
-rw-r--r--src/main/kotlin/io/dico/parcels2/WorldGenerator.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt1
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt27
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt13
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt23
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt1
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt23
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Backing.kt6
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt322
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Storage.kt20
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt3
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt186
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt121
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt104
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt53
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt18
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/Vec3i.kt34
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)
+
+}
+*/