Improve database abstractions, add GlobalAddedData, some other things
This commit is contained in:
47
src/main/kotlin/io/dico/parcels2/AddedData.kt
Normal file
47
src/main/kotlin/io/dico/parcels2/AddedData.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package io.dico.parcels2
|
||||
|
||||
import io.dico.parcels2.util.uuid
|
||||
import org.bukkit.OfflinePlayer
|
||||
import java.util.*
|
||||
|
||||
interface AddedData {
|
||||
val added: Map<UUID, AddedStatus>
|
||||
|
||||
fun getAddedStatus(uuid: UUID): AddedStatus
|
||||
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
|
||||
|
||||
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean =
|
||||
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) }
|
||||
|
||||
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
|
||||
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED)
|
||||
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
|
||||
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
|
||||
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED)
|
||||
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT)
|
||||
|
||||
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid)
|
||||
fun allow(player: OfflinePlayer) = allow(player.uuid)
|
||||
fun disallow(player: OfflinePlayer) = disallow(player.uuid)
|
||||
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
|
||||
fun ban(player: OfflinePlayer) = ban(player.uuid)
|
||||
fun unban(player: OfflinePlayer) = unban(player.uuid)
|
||||
}
|
||||
|
||||
open class AddedDataHolder(override var added: MutableMap<UUID, AddedStatus>
|
||||
= mutableMapOf<UUID, AddedStatus>()) : AddedData {
|
||||
override fun getAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT)
|
||||
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
|
||||
?.let { added.put(uuid, it) != it }
|
||||
?: added.remove(uuid) != null
|
||||
}
|
||||
|
||||
enum class AddedStatus {
|
||||
DEFAULT,
|
||||
ALLOWED,
|
||||
BANNED;
|
||||
|
||||
val isDefault get() = this == DEFAULT
|
||||
val isAllowed get() = this == ALLOWED
|
||||
val isBanned get() = this == BANNED
|
||||
}
|
||||
@@ -1,38 +1,25 @@
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package io.dico.parcels2
|
||||
|
||||
import io.dico.parcels2.util.uuid
|
||||
import kotlinx.coroutines.experimental.CompletableDeferred
|
||||
import kotlinx.coroutines.experimental.Deferred
|
||||
import org.bukkit.OfflinePlayer
|
||||
import java.util.*
|
||||
|
||||
interface GlobalAddedData : AddedData {
|
||||
val uuid: UUID
|
||||
val owner: ParcelOwner
|
||||
}
|
||||
|
||||
class GlobalAddedDataManager(val plugin: ParcelsPlugin) {
|
||||
private val map = mutableMapOf<UUID, GlobalAddedData?>()
|
||||
interface GlobalAddedDataManager {
|
||||
operator fun get(owner: ParcelOwner): GlobalAddedData
|
||||
}
|
||||
|
||||
operator fun get(player: OfflinePlayer) = get(player.uuid)
|
||||
|
||||
operator fun get(uuid: UUID): GlobalAddedData? {
|
||||
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
|
||||
private val map = mutableMapOf<ParcelOwner, GlobalAddedData>()
|
||||
|
||||
override fun get(owner: ParcelOwner): GlobalAddedData {
|
||||
return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it }
|
||||
}
|
||||
|
||||
fun getDeferred(uuid: UUID): Deferred<AddedData> {
|
||||
get(uuid)?.let { return CompletableDeferred(it) }
|
||||
|
||||
}
|
||||
|
||||
private suspend fun getAsync(uuid: UUID): GlobalAddedData {
|
||||
val data = plugin.storage.readGlobalAddedData(ParcelOwner(uuid = uuid)).await()
|
||||
?: return GlobalAddedDataImpl(uuid)
|
||||
val result = GlobalAddedDataImpl(uuid, data)
|
||||
map[uuid] = result
|
||||
return result
|
||||
}
|
||||
|
||||
private inner class GlobalAddedDataImpl(override val uuid: UUID,
|
||||
private inner class GlobalAddedDataImpl(override val owner: ParcelOwner,
|
||||
data: MutableMap<UUID, AddedStatus> = emptyData)
|
||||
: AddedDataHolder(data), GlobalAddedData {
|
||||
|
||||
@@ -45,7 +32,7 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) {
|
||||
data = mutableMapOf()
|
||||
}
|
||||
return super.setAddedStatus(uuid, status).also {
|
||||
if (it) plugin.storage.setGlobalAddedStatus(ParcelOwner(uuid = this.uuid), uuid, status)
|
||||
if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,5 +46,3 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
53
src/main/kotlin/io/dico/parcels2/ParcelOwner.kt
Normal file
53
src/main/kotlin/io/dico/parcels2/ParcelOwner.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
package io.dico.parcels2
|
||||
|
||||
import io.dico.parcels2.util.getPlayerNameOrDefault
|
||||
import io.dico.parcels2.util.isValid
|
||||
import io.dico.parcels2.util.uuid
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.entity.Player
|
||||
import java.util.*
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
class ParcelOwner private constructor(val uuid: UUID?,
|
||||
name: String?) {
|
||||
var name: String? = name
|
||||
get() = field ?: getPlayerNameOrDefault(uuid!!).also { field = it }
|
||||
private set
|
||||
|
||||
constructor(name: String) : this(null, name)
|
||||
constructor(uuid: UUID) : this(uuid, null)
|
||||
constructor(player: OfflinePlayer) : this(player.uuid, player.name)
|
||||
|
||||
companion object {
|
||||
fun nameless(player: OfflinePlayer) = ParcelOwner(player.uuid, null)
|
||||
}
|
||||
|
||||
inline val hasUUID: Boolean get() = uuid != null
|
||||
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
|
||||
val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) }
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val offlinePlayer
|
||||
get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
|
||||
?.takeIf { it.isValid }
|
||||
|
||||
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
|
||||
return uuid?.let { it == player.uniqueId } ?: false
|
||||
|| (allowNameMatch && name?.let { it == player.name } ?: false)
|
||||
}
|
||||
|
||||
fun equals(other: ParcelOwner): Boolean {
|
||||
return if (hasUUID) other.hasUUID && uuid == other.uuid
|
||||
else !other.hasUUID && name == other.name
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ParcelOwner && equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return if (hasUUID) uuid!!.hashCode() else name!!.hashCode()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,4 +64,4 @@ val attachables: Set<Material> = EnumSet.of(
|
||||
WALL_SIGN,
|
||||
LILY_PAD,
|
||||
DANDELION
|
||||
);
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
23
src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt
Normal file
23
src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
package io.dico.parcels2.command
|
||||
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelWorld
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
import kotlinx.coroutines.experimental.Deferred
|
||||
|
||||
interface ParcelTarget {
|
||||
val world: ParcelWorld
|
||||
val isByID: Boolean
|
||||
val isByOwner: Boolean get() = !isByID
|
||||
suspend fun ParcelsPlugin.await(): Parcel?
|
||||
fun ParcelsPlugin.get(): Deferred<Parcel?> =
|
||||
}
|
||||
|
||||
class ParcelTargetByOwner : ParcelTarget {
|
||||
override val isByID get() = false
|
||||
}
|
||||
|
||||
class ParcelTargetByID : ParcelTarget {
|
||||
override val isByID get() = true
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
23
src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt
Normal file
23
src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
package io.dico.parcels2.listener
|
||||
|
||||
import io.dico.dicore.RegistratorListener
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
import org.bukkit.event.Event
|
||||
|
||||
interface HasPlugin {
|
||||
val plugin: ParcelsPlugin
|
||||
}
|
||||
|
||||
inline fun <reified T: Event> HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener<T> { event ->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
121
src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
Normal file
121
src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
Normal file
@@ -0,0 +1,121 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.ParcelWorld
|
||||
import io.dico.parcels2.storage.SerializableParcel
|
||||
import io.dico.parcels2.storage.SerializableWorld
|
||||
import io.dico.parcels2.storage.uniqueIndexR
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import io.dico.parcels2.util.toByteArray
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import java.util.*
|
||||
|
||||
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj, SerializableObj>,
|
||||
QueryObj, SerializableObj>(tableName: String, columnName: String)
|
||||
: Table(tableName) {
|
||||
val id = integer(columnName).autoIncrement().primaryKey()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline val table: TableT
|
||||
get() = this as TableT
|
||||
|
||||
internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op<Boolean>): Int? {
|
||||
return select { where(table) }.firstOrNull()?.let { it[id] }
|
||||
}
|
||||
|
||||
internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement<Number>) -> Unit): Int {
|
||||
return table.insert(body)[id] ?: insertError(objName)
|
||||
}
|
||||
|
||||
private inline fun insertError(obj: String): Nothing = throw ExposedDatabaseException("This should not happen - failed to insert $obj and get its id")
|
||||
|
||||
abstract fun getId(obj: QueryObj): Int?
|
||||
abstract fun getOrInitId(obj: QueryObj): Int
|
||||
fun getSerializable(id: Int): SerializableObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getSerializable(it) }
|
||||
abstract fun getSerializable(row: ResultRow): SerializableObj?
|
||||
}
|
||||
|
||||
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorld, SerializableWorld>("parcel_worlds", "world_id") {
|
||||
val name = varchar("name", 50)
|
||||
val uid = binary("uid", 2)
|
||||
val index_uid = uniqueIndexR("index_uid", uid)
|
||||
|
||||
internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid }
|
||||
internal inline fun getId(uid: UUID): Int? = getId(uid.toByteArray())
|
||||
internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid ->
|
||||
getId(binaryUid)
|
||||
?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName }
|
||||
}
|
||||
|
||||
override fun getId(world: ParcelWorld): Int? = getId(world.world.uid)
|
||||
override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) }
|
||||
|
||||
override fun getSerializable(row: ResultRow): SerializableWorld {
|
||||
return SerializableWorld(row[name], row[uid].toUUID())
|
||||
}
|
||||
}
|
||||
|
||||
object ParcelsT : IdTransactionsTable<ParcelsT, Parcel, SerializableParcel>("parcels", "parcel_id") {
|
||||
val world_id = integer("world_id").references(WorldsT.id)
|
||||
val px = integer("px")
|
||||
val pz = integer("pz")
|
||||
val owner_id = integer("owner_id").references(OwnersT.id).nullable()
|
||||
val claim_time = datetime("claim_time").nullable()
|
||||
val index_location = uniqueIndexR("index_location", world_id, px, pz)
|
||||
|
||||
private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) }
|
||||
private inline fun getId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldUid)?.let { getId(it, parcelX, parcelZ) }
|
||||
private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int {
|
||||
val worldId = WorldsT.getOrInitId(worldUid, worldName)
|
||||
return getId(worldId, parcelX, parcelZ)
|
||||
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }
|
||||
}
|
||||
|
||||
override fun getId(parcel: Parcel): Int? = getId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z)
|
||||
override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.z) }
|
||||
|
||||
private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
|
||||
fun getRow(parcel: Parcel): ResultRow? = getId(parcel)?.let { getRow(it) }
|
||||
|
||||
override fun getSerializable(row: ResultRow): SerializableParcel? {
|
||||
val worldId = row[world_id]
|
||||
val world = WorldsT.getSerializable(worldId) ?: return null
|
||||
return SerializableParcel(world, Vec2i(row[px], row[pz]))
|
||||
}
|
||||
}
|
||||
|
||||
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner, ParcelOwner>("parcel_owners", "owner_id") {
|
||||
val uuid = binary("uuid", 2).nullable()
|
||||
val name = varchar("name", 32).nullable()
|
||||
val index_pair = uniqueIndexR("index_pair", uuid, name)
|
||||
|
||||
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
|
||||
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
|
||||
private inline fun getId(name: String) = getId { OwnersT.name eq name }
|
||||
|
||||
private inline fun getOrInitId(uuid: UUID) = uuid.toByteArray().let { binaryUuid ->
|
||||
getId(binaryUuid)
|
||||
?: insertAndGetId("owner(uuid = $uuid)") { it[OwnersT.uuid] = binaryUuid }
|
||||
}
|
||||
|
||||
private inline fun getOrInitId(name: String) =
|
||||
getId(name)
|
||||
?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name }
|
||||
|
||||
override fun getId(owner: ParcelOwner): Int? =
|
||||
if (owner.hasUUID) getId(owner.uuid!!)
|
||||
else getId(owner.name!!)
|
||||
|
||||
override fun getOrInitId(owner: ParcelOwner): Int =
|
||||
if (owner.hasUUID) getOrInitId(owner.uuid!!)
|
||||
else getOrInitId(owner.name!!)
|
||||
|
||||
override fun getSerializable(row: ResultRow): ParcelOwner {
|
||||
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]!!)
|
||||
}
|
||||
}
|
||||
104
src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
Normal file
104
src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
Normal file
@@ -0,0 +1,104 @@
|
||||
@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE")
|
||||
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import io.dico.parcels2.AddedStatus
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.storage.SerializableParcel
|
||||
import io.dico.parcels2.storage.uniqueIndexR
|
||||
import io.dico.parcels2.storage.upsert
|
||||
import io.dico.parcels2.util.toByteArray
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import kotlinx.coroutines.experimental.channels.SendChannel
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import java.util.*
|
||||
|
||||
object AddedLocalT : AddedTable<Parcel, SerializableParcel>("parcels_added_local", ParcelsT)
|
||||
object AddedGlobalT : AddedTable<ParcelOwner, ParcelOwner>("parcels_added_global", OwnersT)
|
||||
|
||||
object ParcelOptionsT : Table("parcel_options") {
|
||||
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
|
||||
val interact_inventory = bool("interact_inventory").default(false)
|
||||
val interact_inputs = bool("interact_inputs").default(false)
|
||||
}
|
||||
|
||||
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableMap<UUID, AddedStatus>>>
|
||||
|
||||
sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTransactionsTable<*, AttachT, SerializableT>) : Table(name) {
|
||||
val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
|
||||
val player_uuid = binary("player_uuid", 16)
|
||||
val allowed_flag = bool("allowed_flag")
|
||||
val index_pair = uniqueIndexR("index_pair", attach_id, player_uuid)
|
||||
|
||||
fun setPlayerStatus(attachedOn: AttachT, player: UUID, status: AddedStatus) {
|
||||
val binaryUuid = player.toByteArray()
|
||||
|
||||
if (status.isDefault) {
|
||||
idTable.getId(attachedOn)?.let { id ->
|
||||
deleteWhere { (attach_id eq id) and (player_uuid eq binaryUuid) }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val id = idTable.getOrInitId(attachedOn)
|
||||
upsert(conflictIndex = index_pair) {
|
||||
it[attach_id] = id
|
||||
it[player_uuid] = binaryUuid
|
||||
it[allowed_flag] = status.isAllowed
|
||||
}
|
||||
}
|
||||
|
||||
fun readAddedData(id: Int): MutableMap<UUID, AddedStatus> {
|
||||
return slice(player_uuid, allowed_flag).select { attach_id eq id }
|
||||
.associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() })
|
||||
}
|
||||
|
||||
suspend fun sendAllAddedData(channel: AddedStatusSendChannel<SerializableT>) {
|
||||
val iterator = selectAll().orderBy(attach_id).iterator()
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
val firstRow = iterator.next()
|
||||
var id: Int = firstRow[attach_id]
|
||||
var attach: SerializableT? = null
|
||||
var map: MutableMap<UUID, AddedStatus>? = null
|
||||
|
||||
fun initAttachAndMap() {
|
||||
attach = idTable.getSerializable(id)
|
||||
map = attach?.let { mutableMapOf() }
|
||||
}
|
||||
|
||||
suspend fun sendIfPresent() {
|
||||
if (attach != null && map != null && map!!.isNotEmpty()) {
|
||||
channel.send(attach!! to map!!)
|
||||
}
|
||||
attach = null
|
||||
map = null
|
||||
}
|
||||
|
||||
initAttachAndMap()
|
||||
|
||||
for (row in iterator) {
|
||||
val rowId = row[attach_id]
|
||||
if (rowId != id) {
|
||||
sendIfPresent()
|
||||
id = rowId
|
||||
initAttachAndMap()
|
||||
}
|
||||
|
||||
if (attach == null) {
|
||||
continue // owner not found for this owner id
|
||||
}
|
||||
|
||||
val player_uuid = row[player_uuid].toUUID()
|
||||
val status = row[allowed_flag].asAddedStatus()
|
||||
map!![player_uuid] = status
|
||||
}
|
||||
|
||||
sendIfPresent()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED
|
||||
|
||||
}
|
||||
@@ -49,8 +49,6 @@ class AwaitTask : Runnable {
|
||||
onSuccess!!.invoke()
|
||||
}
|
||||
|
||||
elapsedChecks++
|
||||
|
||||
if (maxChecks in 1 until elapsedChecks) {
|
||||
cancel()
|
||||
onFailure?.invoke()
|
||||
|
||||
53
src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt
Normal file
53
src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
package io.dico.parcels2.util
|
||||
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
import kotlinx.coroutines.experimental.*
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
import kotlin.coroutines.experimental.CoroutineContext
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
class FunctionHelper(val plugin: ParcelsPlugin) {
|
||||
val mainThreadDispatcher: MainThreadDispatcher = MainThreadDispatcherImpl()
|
||||
|
||||
fun <T> deferLazilyOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
|
||||
return async(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
|
||||
}
|
||||
|
||||
fun <T> deferUndispatchedOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
|
||||
return async(context = mainThreadDispatcher, start = CoroutineStart.UNDISPATCHED, block = block)
|
||||
}
|
||||
|
||||
fun launchLazilyOnMainThread(block: suspend CoroutineScope.() -> Unit): Job {
|
||||
return launch(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
|
||||
}
|
||||
|
||||
inline fun schedule(noinline task: () -> Unit) = schedule(0, task)
|
||||
|
||||
fun schedule(delay: Int, task: () -> Unit): BukkitTask {
|
||||
return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
|
||||
}
|
||||
|
||||
fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
|
||||
return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
|
||||
}
|
||||
|
||||
abstract class MainThreadDispatcher : CoroutineDispatcher() {
|
||||
abstract val mainThread: Thread
|
||||
abstract fun runOnMainThread(task: Runnable)
|
||||
}
|
||||
|
||||
private inner class MainThreadDispatcherImpl : MainThreadDispatcher() {
|
||||
override val mainThread: Thread = Thread.currentThread()
|
||||
|
||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
||||
runOnMainThread(block)
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_BY_INLINE")
|
||||
override inline fun runOnMainThread(task: Runnable) {
|
||||
if (Thread.currentThread() === mainThread) task.run()
|
||||
else plugin.server.scheduler.runTaskLater(plugin, task, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,21 +6,21 @@ import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
|
||||
return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name }
|
||||
fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {
|
||||
return uuid
|
||||
?.let { getPlayerName(it) }
|
||||
?: ifUnknown
|
||||
?: ":unknown_name:"
|
||||
}
|
||||
|
||||
@Contract("null -> null; !null -> !null", pure = true)
|
||||
fun UUID?.toByteArray(): ByteArray? = this?.let {
|
||||
fun getPlayerName(uuid: UUID): String? {
|
||||
return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name
|
||||
}
|
||||
|
||||
fun UUID.toByteArray(): ByteArray =
|
||||
ByteBuffer.allocate(16).apply {
|
||||
putLong(mostSignificantBits)
|
||||
putLong(leastSignificantBits)
|
||||
}.array()
|
||||
}
|
||||
|
||||
@Contract("null -> null; !null -> !null", pure = true)
|
||||
fun ByteArray?.toUUID(): UUID? = this?.let {
|
||||
ByteBuffer.wrap(it).run { UUID(long, long) }
|
||||
}
|
||||
fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) }
|
||||
|
||||
@@ -16,4 +16,36 @@ data class Vec3i(
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
|
||||
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
|
||||
|
||||
/*
|
||||
inline class IVec3i(private val data: Long) {
|
||||
|
||||
private companion object {
|
||||
const val mask = 0x001F_FFFF
|
||||
const val max: Int = 0x000F_FFFF // +1048575
|
||||
const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Int.compressIntoLong(offset: Int): Long {
|
||||
if (this !in min..max) throw IllegalArgumentException()
|
||||
return and(mask).toLong().shl(offset)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Long.extractInt(offset: Int): Int {
|
||||
return ushr(offset).toInt().and(mask)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(x: Int, y: Int, z: Int) : this(
|
||||
x.compressIntoLong(42)
|
||||
or y.compressIntoLong(21)
|
||||
or z.compressIntoLong(0))
|
||||
|
||||
val x: Int get() = data.extractInt(42)
|
||||
val y: Int get() = data.extractInt(21)
|
||||
val z: Int get() = data.extractInt(0)
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user