From 67cb73e4c713118b266f8d2b25eafe9527af7c73 Mon Sep 17 00:00:00 2001 From: Dico Date: Fri, 28 Sep 2018 21:16:14 +0100 Subject: 10/10 commit messages btw --- build.gradle.kts | 2 +- src/main/kotlin/io/dico/parcels2/JobDispatcher.kt | 337 ++++++++++++++++++++ src/main/kotlin/io/dico/parcels2/Parcel.kt | 17 +- .../kotlin/io/dico/parcels2/ParcelGenerator.kt | 36 ++- src/main/kotlin/io/dico/parcels2/ParcelId.kt | 30 +- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 15 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 4 +- .../io/dico/parcels2/blockvisitor/JobDispatcher.kt | 339 --------------------- .../dico/parcels2/blockvisitor/RegionTraverser.kt | 139 +++++++-- .../io/dico/parcels2/blockvisitor/Schematic.kt | 2 + .../parcels2/command/AbstractParcelCommands.kt | 27 +- .../io/dico/parcels2/command/CommandsAdmin.kt | 49 ++- .../io/dico/parcels2/command/CommandsDebug.kt | 9 +- .../io/dico/parcels2/command/CommandsGeneral.kt | 4 +- .../io/dico/parcels2/command/ParcelTarget.kt | 4 +- .../parcels2/defaultimpl/DefaultParcelContainer.kt | 2 +- .../parcels2/defaultimpl/DefaultParcelGenerator.kt | 188 ++++++------ .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 88 ++++-- .../parcels2/defaultimpl/ParcelProviderImpl.kt | 89 +++++- .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 65 ++-- .../io/dico/parcels2/listener/ParcelListeners.kt | 30 +- .../io/dico/parcels2/options/MigrationOptions.kt | 3 +- .../kotlin/io/dico/parcels2/options/Options.kt | 2 +- .../kotlin/io/dico/parcels2/storage/Backing.kt | 4 +- .../kotlin/io/dico/parcels2/storage/Storage.kt | 12 +- .../parcels2/storage/exposed/ExposedBacking.kt | 7 +- .../parcels2/storage/exposed/ExposedExtensions.kt | 14 +- .../storage/migration/plotme/PlotmeMigration.kt | 28 +- .../storage/migration/plotme/PlotmeTables.kt | 41 +-- .../kotlin/io/dico/parcels2/util/BukkitUtil.kt | 2 + .../kotlin/io/dico/parcels2/util/math/Vec2i.kt | 5 +- .../kotlin/io/dico/parcels2/util/math/Vec3i.kt | 2 + 32 files changed, 923 insertions(+), 673 deletions(-) create mode 100644 src/main/kotlin/io/dico/parcels2/JobDispatcher.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt diff --git a/build.gradle.kts b/build.gradle.kts index bd94b3f..12c2d97 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,7 +77,7 @@ dependencies { // not on sk89q maven repo yet compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar")) - //compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar")) + compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar")) compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false } compile("joda-time:joda-time:2.10") diff --git a/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt b/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt new file mode 100644 index 0000000..10da0da --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt @@ -0,0 +1,337 @@ +package io.dico.parcels2 + +import io.dico.parcels2.util.math.clampMin +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart.LAZY +import kotlinx.coroutines.Job as CoroutineJob +import kotlinx.coroutines.launch +import org.bukkit.scheduler.BukkitTask +import java.lang.System.currentTimeMillis +import java.util.LinkedList +import kotlin.coroutines.Continuation +import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn +import kotlin.coroutines.resume + +typealias JobFunction = suspend JobScope.() -> Unit +typealias JobUpdateLister = Job.(Double, Long) -> Unit + +data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int) + +interface JobDispatcher { + /** + * Submit a [function] that should be run synchronously, but limited such that it does not stall the server + */ + fun dispatch(function: JobFunction): Job + + /** + * Get a list of all jobs + */ + val jobs: List + + /** + * Attempts to complete any remaining tasks immediately, without suspension. + */ + fun completeAllTasks() +} + +interface JobAndScopeMembersUnion { + /** + * The time that elapsed since this job was dispatched, in milliseconds + */ + val elapsedTime: Long + + /** + * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0 + * with no guarantees to its accuracy. + */ + val progress: Double +} + +interface Job : JobAndScopeMembersUnion { + /** + * The coroutine associated with this job + */ + val coroutine: CoroutineJob + + /** + * true if this job has completed + */ + val isComplete: Boolean + + /** + * If an exception was thrown during the execution of this task, + * returns that exception. Returns null otherwise. + */ + val completionException: Throwable? + + /** + * Calls the given [block] whenever the progress of this job is updated, + * if [minInterval] milliseconds expired since the last call. + * The first call occurs after at least [minDelay] milliseconds in a likewise manner. + * Repeated invocations of this method result in an [IllegalStateException] + * + * if [asCompletionListener] is true, [onCompleted] is called with the same [block] + */ + fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job + + /** + * Calls the given [block] when this job completes, with the progress value 1.0. + * Multiple listeners may be registered to this function. + */ + fun onCompleted(block: JobUpdateLister): Job + + /** + * Await completion of this job + */ + suspend fun awaitCompletion() +} + +interface JobScope : JobAndScopeMembersUnion { + /** + * A task should call this frequently during its execution, such that the timer can suspend it when necessary. + */ + suspend fun markSuspensionPoint() + + /** + * A task should call this method to indicate its progress + */ + fun setProgress(progress: Double) + + /** + * Indicate that this job is complete + */ + fun markComplete() = setProgress(1.0) + + /** + * Get a [JobScope] that is responsible for [portion] part of the progress + * If [portion] is negative, the remaining progress is used + */ + fun delegateProgress(portion: Double = -1.0): JobScope +} + +inline fun JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T { + delegateProgress(portion).apply { + val result = block() + markComplete() + return result + } +} + +interface JobInternal : Job, JobScope { + /** + * Start or resumes the execution of this job + * and returns true if the job completed + * + * [worktime] is the maximum amount of time, in milliseconds, + * that this job may run for until suspension. + * + * If [worktime] is not positive, the job will complete + * without suspension and this method will always return true. + */ + fun resume(worktime: Long): Boolean +} + +/** + * An object that controls one or more jobs, ensuring that they don't stall the server too much. + * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick + * This object attempts to split that maximum amount of milliseconds equally between all jobs + */ +class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher { + // The currently registered bukkit scheduler task + private var bukkitTask: BukkitTask? = null + // The jobs. + private val _jobs = LinkedList() + override val jobs: List = _jobs + + override fun dispatch(function: JobFunction): Job { + val job: JobInternal = JobImpl(plugin, function) + + if (bukkitTask == null) { + val completed = job.resume(options.jobTime.toLong()) + if (completed) return job + bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() } + } + _jobs.addFirst(job) + return job + } + + private fun tickCoroutineJobs() { + val jobs = _jobs + if (jobs.isEmpty()) return + val tickStartTime = System.currentTimeMillis() + + val iterator = jobs.listIterator(index = 0) + while (iterator.hasNext()) { + val time = System.currentTimeMillis() + val timeElapsed = time - tickStartTime + val timeLeft = options.jobTime - timeElapsed + if (timeLeft <= 0) return + + val count = jobs.size - iterator.nextIndex() + val timePerJob = (timeLeft + count - 1) / count + val job = iterator.next() + val completed = job.resume(timePerJob) + if (completed) { + iterator.remove() + } + } + + if (jobs.isEmpty()) { + bukkitTask?.cancel() + bukkitTask = null + } + } + + override fun completeAllTasks() { + _jobs.forEach { + it.resume(-1) + } + _jobs.clear() + bukkitTask?.cancel() + bukkitTask = null + } + +} + +private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal { + override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() } + + private var continuation: Continuation? = null + private var nextSuspensionTime: Long = 0L + private var completeForcefully = false + private var isStarted = false + + override val elapsedTime + get() = + if (coroutine.isCompleted) startTimeOrElapsedTime + else currentTimeMillis() - startTimeOrElapsedTime + + override val isComplete get() = coroutine.isCompleted + + private var _progress = 0.0 + override val progress get() = _progress + override var completionException: Throwable? = null; private set + + private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise + private var onProgressUpdate: JobUpdateLister? = null + private var progressUpdateInterval: Int = 0 + private var lastUpdateTime: Long = 0L + private var onCompleted: JobUpdateLister? = null + + init { + coroutine.invokeOnCompletion { exception -> + // report any error that occurred + completionException = exception?.also { + if (it !is CancellationException) + logger.error("JobFunction generated an exception", it) + } + + // convert to elapsed time here + startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime + onCompleted?.let { it(1.0, elapsedTime) } + + onCompleted = null + onProgressUpdate = { prog, el -> } + } + } + + override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job { + onProgressUpdate?.let { throw IllegalStateException() } + if (asCompletionListener) onCompleted(block) + if (isComplete) return this + onProgressUpdate = block + progressUpdateInterval = minInterval + lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval + + return this + } + + override fun onCompleted(block: JobUpdateLister): Job { + if (isComplete) { + block(1.0, startTimeOrElapsedTime) + return this + } + + val cur = onCompleted + onCompleted = if (cur == null) { + block + } else { + fun Job.(prog: Double, el: Long) { + cur(prog, el) + block(prog, el) + } + } + return this + } + + override suspend fun markSuspensionPoint() { + if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) + suspendCoroutineUninterceptedOrReturn { cont: Continuation -> + continuation = cont + COROUTINE_SUSPENDED + } + } + + override fun setProgress(progress: Double) { + this._progress = progress + val onProgressUpdate = onProgressUpdate ?: return + val time = System.currentTimeMillis() + if (time > lastUpdateTime + progressUpdateInterval) { + onProgressUpdate(progress, elapsedTime) + lastUpdateTime = time + } + } + + override fun resume(worktime: Long): Boolean { + if (isComplete) return true + + if (worktime > 0) { + nextSuspensionTime = currentTimeMillis() + worktime + } else { + completeForcefully = true + } + + if (isStarted) { + continuation?.let { + continuation = null + it.resume(Unit) + return continuation == null + } + return true + } + + isStarted = true + startTimeOrElapsedTime = System.currentTimeMillis() + coroutine.start() + + return continuation == null + } + + override suspend fun awaitCompletion() { + coroutine.join() + } + + private fun delegateProgress(curPortion: Double, portion: Double): JobScope = + DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) + + override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion) + + private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope { + override val elapsedTime: Long + get() = this@JobImpl.elapsedTime + + override suspend fun markSuspensionPoint() = + this@JobImpl.markSuspensionPoint() + + override val progress: Double + get() = (this@JobImpl.progress - progressStart) / portion + + override fun setProgress(progress: Double) = + this@JobImpl.setProgress(progressStart + progress * portion) + + override fun delegateProgress(portion: Double): JobScope = + this@JobImpl.delegateProgress(this.portion, portion) + } +} diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 517b88f..4f89fe0 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,6 +1,7 @@ package io.dico.parcels2 import io.dico.parcels2.util.math.Vec2i +import io.dico.parcels2.util.math.Vec3i import org.bukkit.Location import org.joda.time.DateTime import java.util.UUID @@ -19,7 +20,7 @@ interface Parcel : ParcelData, Privileges { val pos: Vec2i val x: Int val z: Int - val data: ParcelData + val data: ParcelDataHolder val infoString: String val hasBlockVisitors: Boolean val globalPrivileges: GlobalPrivileges? @@ -27,21 +28,21 @@ interface Parcel : ParcelData, Privileges { override val keyOfOwner: PlayerProfile.Real? get() = owner as? PlayerProfile.Real - fun copyDataIgnoringDatabase(data: ParcelData) + fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean = false) - fun copyData(data: ParcelData) + fun dispose() = copyData(ParcelDataHolder()) - fun dispose() - - suspend fun withBlockVisitorPermit(block: suspend () -> Unit) + fun updateOwnerSign(force: Boolean = false) val homeLocation: Location get() = world.blockManager.getHomeLocation(id) } + + interface ParcelData : RawPrivileges { var owner: PlayerProfile? val lastClaimTime: DateTime? - var ownerSignOutdated: Boolean + var isOwnerSignOutdated: Boolean var interactableConfig: InteractableConfiguration //fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean @@ -59,7 +60,7 @@ class ParcelDataHolder(addedMap: MutablePrivilegeMap = mutableMapOf()) : ParcelData, PrivilegesHolder(addedMap) { override var owner: PlayerProfile? = null override var lastClaimTime: DateTime? = null - override var ownerSignOutdated = false + override var isOwnerSignOutdated = false override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration() } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt index d45ff83..109c5dc 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -1,15 +1,18 @@ package io.dico.parcels2 -import io.dico.parcels2.blockvisitor.* +import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.util.math.Region import io.dico.parcels2.util.math.Vec2i +import io.dico.parcels2.util.math.Vec3i import io.dico.parcels2.util.math.get import kotlinx.coroutines.CoroutineScope import org.bukkit.Chunk import org.bukkit.Location +import org.bukkit.Material import org.bukkit.World import org.bukkit.block.Biome import org.bukkit.block.Block +import org.bukkit.block.BlockFace import org.bukkit.entity.Entity import org.bukkit.generator.BlockPopulator import org.bukkit.generator.ChunkGenerator @@ -34,10 +37,12 @@ abstract class ParcelGenerator : ChunkGenerator() { }) } - abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId, - container: ParcelContainer, - coroutineScope: CoroutineScope, - jobDispatcher: JobDispatcher): Pair + abstract fun makeParcelLocatorAndBlockManager( + parcelProvider: ParcelProvider, + container: ParcelContainer, + coroutineScope: CoroutineScope, + jobDispatcher: JobDispatcher + ): Pair } interface ParcelBlockManager { @@ -45,7 +50,7 @@ interface ParcelBlockManager { val jobDispatcher: JobDispatcher val parcelTraverser: RegionTraverser - // fun getBottomBlock(parcel: ParcelId): Vec2i + fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i() fun getHomeLocation(parcel: ParcelId): Location @@ -53,15 +58,15 @@ interface ParcelBlockManager { fun getEntities(parcel: ParcelId): Collection - fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) + fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean - fun setBiome(parcel: ParcelId, biome: Biome): Job + fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) - fun clearParcel(parcel: ParcelId): Job + fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? - fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Job + fun setBiome(parcel: ParcelId, biome: Biome): Job? - fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job + fun clearParcel(parcel: ParcelId): Job? /** * Used to update owner blocks in the corner of the parcel @@ -69,9 +74,12 @@ interface ParcelBlockManager { fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection } -inline fun ParcelBlockManager.doBlockOperation(parcel: ParcelId, - traverser: RegionTraverser, - crossinline operation: suspend JobScope.(Block) -> Unit) = submitBlockVisitor(parcel) { +inline fun ParcelBlockManager.tryDoBlockOperation( + parcelProvider: ParcelProvider, + parcel: ParcelId, + traverser: RegionTraverser, + crossinline operation: suspend JobScope.(Block) -> Unit +) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) { val region = getRegion(parcel) val blockCount = region.blockCount.toDouble() val blocks = traverser.traverseRegion(region) diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt index 926fc23..eef7129 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelId.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt @@ -1,3 +1,5 @@ +@file:Suppress("FunctionName") + package io.dico.parcels2 import io.dico.parcels2.util.math.Vec2i @@ -15,14 +17,12 @@ interface ParcelWorldId { fun equals(id: ParcelWorldId): Boolean = name == id.name || (uid != null && uid == id.uid) val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) } - - companion object { - operator fun invoke(worldName: String, worldUid: UUID?): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid) - operator fun invoke(worldName: String): ParcelWorldId = ParcelWorldIdImpl(worldName, null) - } } -fun ParcelWorldId.toStringExt() = "ParcelWorld($name)" +fun ParcelWorldId.parcelWorldIdToString() = "ParcelWorld($name)" + +fun ParcelWorldId(worldName: String, worldUid: UUID? = null): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid) +fun ParcelWorldId(world: World) = ParcelWorldId(world.name, world.uid) /** * Used by storage backing options to encompass the location of a parcel @@ -35,24 +35,22 @@ interface ParcelId { val pos: Vec2i get() = Vec2i(x, z) val idString get() = "$x,$z" fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId) - - companion object { - operator fun invoke(worldId: ParcelWorldId, pos: Vec2i) = invoke(worldId, pos.x, pos.z) - operator fun invoke(worldName: String, worldUid: UUID?, pos: Vec2i) = invoke(worldName, worldUid, pos.x, pos.z) - operator fun invoke(worldName: String, worldUid: UUID?, x: Int, z: Int) = invoke(ParcelWorldId(worldName, worldUid), x, z) - operator fun invoke(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z) - } } -fun ParcelId.toStringExt() = "Parcel(${worldId.name},$idString)" +fun ParcelId.parcelIdToString() = "Parcel(${worldId.name},$idString)" + +fun ParcelId(worldId: ParcelWorldId, pos: Vec2i) = ParcelId(worldId, pos.x, pos.z) +fun ParcelId(worldName: String, worldUid: UUID?, pos: Vec2i) = ParcelId(worldName, worldUid, pos.x, pos.z) +fun ParcelId(worldName: String, worldUid: UUID?, x: Int, z: Int) = ParcelId(ParcelWorldId(worldName, worldUid), x, z) +fun ParcelId(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z) private class ParcelWorldIdImpl(override val name: String, override val uid: UUID?) : ParcelWorldId { - override fun toString() = toStringExt() + override fun toString() = parcelWorldIdToString() } private class ParcelIdImpl(override val worldId: ParcelWorldId, override val x: Int, override val z: Int) : ParcelId { - override fun toString() = toStringExt() + override fun toString() = parcelIdToString() } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index a3c6c24..c31b11a 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -9,8 +9,11 @@ import org.bukkit.World import org.bukkit.block.Block import org.bukkit.entity.Entity import org.joda.time.DateTime +import java.lang.IllegalStateException import java.util.UUID +class Permit + interface ParcelProvider { val worlds: Map @@ -43,6 +46,15 @@ interface ParcelProvider { fun getWorldGenerator(worldName: String): ParcelGenerator? fun loadWorlds() + + fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean + + @Throws(IllegalStateException::class) + fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) + + fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array, function: JobFunction): Job? + + fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? } interface ParcelLocator { @@ -69,7 +81,7 @@ interface ParcelContainer { fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z) - fun getParcelById(id: ParcelId): Parcel? = getParcelById(id.x, id.z) + fun getParcelById(id: ParcelId): Parcel? fun nextEmptyParcel(): Parcel? @@ -88,5 +100,4 @@ interface ParcelWorld : ParcelLocator, ParcelContainer { val globalPrivileges: GlobalPrivilegesManager val creationTime: DateTime? - } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index b1bf554..a6ebcd8 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -3,8 +3,6 @@ package io.dico.parcels2 import io.dico.dicore.Registrator import io.dico.dicore.command.EOverridePolicy import io.dico.dicore.command.ICommandDispatcher -import io.dico.parcels2.blockvisitor.BukkitJobDispatcher -import io.dico.parcels2.blockvisitor.JobDispatcher import io.dico.parcels2.command.getParcelCommands import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl import io.dico.parcels2.defaultimpl.ParcelProviderImpl @@ -17,6 +15,7 @@ import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.MainThreadDispatcher import io.dico.parcels2.util.PluginScheduler import io.dico.parcels2.util.ext.tryCreate +import io.dico.parcels2.util.isServerThread import kotlinx.coroutines.CoroutineScope import org.bukkit.Bukkit import org.bukkit.generator.ChunkGenerator @@ -47,6 +46,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler { val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) } override fun onEnable() { + plogger.info("Is server thread: ${isServerThread()}") plogger.info("Debug enabled: ${plogger.isDebugEnabled}") plogger.debug(System.getProperty("user.dir")) if (!init()) { diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt deleted file mode 100644 index c6bcacc..0000000 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt +++ /dev/null @@ -1,339 +0,0 @@ -package io.dico.parcels2.blockvisitor - -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.logger -import io.dico.parcels2.util.math.clampMin -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart.LAZY -import kotlinx.coroutines.Job as CoroutineJob -import kotlinx.coroutines.launch -import org.bukkit.scheduler.BukkitTask -import java.lang.System.currentTimeMillis -import java.util.LinkedList -import kotlin.coroutines.Continuation -import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn -import kotlin.coroutines.resume - -typealias JobFunction = suspend JobScope.() -> Unit -typealias JobUpdateLister = Job.(Double, Long) -> Unit - -data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int) - -interface JobDispatcher { - /** - * Submit a [task] that should be run synchronously, but limited such that it does not stall the server - */ - fun dispatch(task: JobFunction): Job - - /** - * Get a list of all jobs - */ - val jobs: List - - /** - * Attempts to complete any remaining tasks immediately, without suspension. - */ - fun completeAllTasks() -} - -interface JobAndScopeMembersUnion { - /** - * The time that elapsed since this job was dispatched, in milliseconds - */ - val elapsedTime: Long - - /** - * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0 - * with no guarantees to its accuracy. - */ - val progress: Double -} - -interface Job : JobAndScopeMembersUnion { - /** - * The coroutine associated with this job - */ - val job: CoroutineJob - - /** - * true if this job has completed - */ - val isComplete: Boolean - - /** - * If an exception was thrown during the execution of this task, - * returns that exception. Returns null otherwise. - */ - val completionException: Throwable? - - /** - * Calls the given [block] whenever the progress of this job is updated, - * if [minInterval] milliseconds expired since the last call. - * The first call occurs after at least [minDelay] milliseconds in a likewise manner. - * Repeated invocations of this method result in an [IllegalStateException] - * - * if [asCompletionListener] is true, [onCompleted] is called with the same [block] - */ - fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job - - /** - * Calls the given [block] when this job completes, with the progress value 1.0. - * Multiple listeners may be registered to this function. - */ - fun onCompleted(block: JobUpdateLister): Job - - /** - * Await completion of this job - */ - suspend fun awaitCompletion() -} - -interface JobScope : JobAndScopeMembersUnion { - /** - * A task should call this frequently during its execution, such that the timer can suspend it when necessary. - */ - suspend fun markSuspensionPoint() - - /** - * A task should call this method to indicate its progress - */ - fun setProgress(progress: Double) - - /** - * Indicate that this job is complete - */ - fun markComplete() = setProgress(1.0) - - /** - * Get a [JobScope] that is responsible for [portion] part of the progress - * If [portion] is negative, the remaining progress is used - */ - fun delegateProgress(portion: Double = -1.0): JobScope -} - -inline fun JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T { - delegateProgress(portion).apply { - val result = block() - markComplete() - return result - } -} - -interface JobInternal : Job, JobScope { - /** - * Start or resumes the execution of this job - * and returns true if the job completed - * - * [worktime] is the maximum amount of time, in milliseconds, - * that this job may run for until suspension. - * - * If [worktime] is not positive, the job will complete - * without suspension and this method will always return true. - */ - fun resume(worktime: Long): Boolean -} - -/** - * An object that controls one or more jobs, ensuring that they don't stall the server too much. - * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick - * This object attempts to split that maximum amount of milliseconds equally between all jobs - */ -class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher { - // The currently registered bukkit scheduler task - private var bukkitTask: BukkitTask? = null - // The jobs. - private val _jobs = LinkedList() - override val jobs: List = _jobs - - override fun dispatch(task: JobFunction): Job { - val job: JobInternal = JobImpl(plugin, task) - - if (bukkitTask == null) { - val completed = job.resume(options.jobTime.toLong()) - if (completed) return job - bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() } - } - _jobs.addFirst(job) - return job - } - - private fun tickCoroutineJobs() { - val jobs = _jobs - if (jobs.isEmpty()) return - val tickStartTime = System.currentTimeMillis() - - val iterator = jobs.listIterator(index = 0) - while (iterator.hasNext()) { - val time = System.currentTimeMillis() - val timeElapsed = time - tickStartTime - val timeLeft = options.jobTime - timeElapsed - if (timeLeft <= 0) return - - val count = jobs.size - iterator.nextIndex() - val timePerJob = (timeLeft + count - 1) / count - val job = iterator.next() - val completed = job.resume(timePerJob) - if (completed) { - iterator.remove() - } - } - - if (jobs.isEmpty()) { - bukkitTask?.cancel() - bukkitTask = null - } - } - - override fun completeAllTasks() { - _jobs.forEach { - it.resume(-1) - } - _jobs.clear() - bukkitTask?.cancel() - bukkitTask = null - } - -} - -private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal { - override val job: CoroutineJob = scope.launch(start = LAZY) { task() } - - private var continuation: Continuation? = null - private var nextSuspensionTime: Long = 0L - private var completeForcefully = false - private var isStarted = false - - override val elapsedTime - get() = - if (job.isCompleted) startTimeOrElapsedTime - else currentTimeMillis() - startTimeOrElapsedTime - - override val isComplete get() = job.isCompleted - - private var _progress = 0.0 - override val progress get() = _progress - override var completionException: Throwable? = null; private set - - private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise - private var onProgressUpdate: JobUpdateLister? = null - private var progressUpdateInterval: Int = 0 - private var lastUpdateTime: Long = 0L - private var onCompleted: JobUpdateLister? = null - - init { - job.invokeOnCompletion { exception -> - // report any error that occurred - completionException = exception?.also { - if (it !is CancellationException) - logger.error("JobFunction generated an exception", it) - } - - // convert to elapsed time here - startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime - onCompleted?.let { it(1.0, elapsedTime) } - - onCompleted = null - onProgressUpdate = { prog, el -> } - } - } - - override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job { - onProgressUpdate?.let { throw IllegalStateException() } - if (asCompletionListener) onCompleted(block) - if (isComplete) return this - onProgressUpdate = block - progressUpdateInterval = minInterval - lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval - - return this - } - - override fun onCompleted(block: JobUpdateLister): Job { - if (isComplete) { - block(1.0, startTimeOrElapsedTime) - return this - } - - val cur = onCompleted - onCompleted = if (cur == null) { - block - } else { - fun Job.(prog: Double, el: Long) { - cur(prog, el) - block(prog, el) - } - } - return this - } - - override suspend fun markSuspensionPoint() { - if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) - suspendCoroutineUninterceptedOrReturn { cont: Continuation -> - continuation = cont - COROUTINE_SUSPENDED - } - } - - override fun setProgress(progress: Double) { - this._progress = progress - val onProgressUpdate = onProgressUpdate ?: return - val time = System.currentTimeMillis() - if (time > lastUpdateTime + progressUpdateInterval) { - onProgressUpdate(progress, elapsedTime) - lastUpdateTime = time - } - } - - override fun resume(worktime: Long): Boolean { - if (isComplete) return true - - if (worktime > 0) { - nextSuspensionTime = currentTimeMillis() + worktime - } else { - completeForcefully = true - } - - if (isStarted) { - continuation?.let { - continuation = null - it.resume(Unit) - return continuation == null - } - return true - } - - isStarted = true - startTimeOrElapsedTime = System.currentTimeMillis() - job.start() - - return continuation == null - } - - override suspend fun awaitCompletion() { - job.join() - } - - private fun delegateProgress(curPortion: Double, portion: Double): JobScope = - DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) - - override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion) - - private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope { - override val elapsedTime: Long - get() = this@JobImpl.elapsedTime - - override suspend fun markSuspensionPoint() = - this@JobImpl.markSuspensionPoint() - - override val progress: Double - get() = (this@JobImpl.progress - progressStart) / portion - - override fun setProgress(progress: Double) = - this@JobImpl.setProgress(progressStart + progress * portion) - - override fun delegateProgress(portion: Double): JobScope = - this@JobImpl.delegateProgress(this.portion, portion) - } -} diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt index dc8ac28..b749b36 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt @@ -6,27 +6,89 @@ import io.dico.parcels2.util.math.Vec3i import io.dico.parcels2.util.math.clampMax private typealias Scope = SequenceScope +/* +class ParcelTraverser( + val parcelProvider: ParcelProvider, + val delegate: RegionTraverser, + scope: CoroutineScope +) : RegionTraverser(), CoroutineScope by scope { -sealed class RegionTraverser { - fun traverseRegion(region: Region, worldHeight: Int = 256): Iterable = - Iterable { iterator { build(validify(region, worldHeight)) } } - - private fun validify(region: Region, worldHeight: Int): Region { - if (region.origin.y < 0) { - val origin = region.origin withY 0 - val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight)) - return Region(origin, size) + class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied") + + /** + * Traverse the blocks of parcel's land + * The iterator must be exhausted, else the permit to traverse it will not be reclaimed. + * + * @throws OccupiedException if a parcel is maintained with the given parcel id and an + * iterator exists for it that has not been exhausted + */ + fun traverseParcel(parcelId: ParcelId): Iterator { + val world = parcelProvider.getWorldById(parcelId.worldId) + ?: throw IllegalArgumentException() + val parcel = parcelProvider.getParcelById(parcelId) + + val medium = if (parcel != null) { + if (parcel.hasBlockVisitors || parcel !is ParcelImpl) { + throw OccupiedException(parcelId) + } + parcel.hasBlockVisitors = true + TraverserMedium { parcel.hasBlockVisitors = false } + } else { + TraverserMedium.DoNothing + } + + val region = world.blockManager.getRegion(parcelId) + return traverseRegion(region, world.world.maxHeight, medium) + } + + override suspend fun Scope.build(region: Region, medium: TraverserMedium) { + with(delegate) { + return build(region, medium) } + } + +} - if (region.origin.y + region.size.y > worldHeight) { - val size = region.size.withY(worldHeight - region.origin.y) - return Region(region.origin, size) +@Suppress("FunctionName") +inline fun TraverserMedium(crossinline whenComplete: () -> Unit) = + object : TraverserMedium { + override fun iterationCompleted() { + whenComplete() } + } + +/** + * An object that is able to communicate with an iterator returned by [RegionTraverser] + * + */ +interface TraverserMedium { - return region + /** + * Called by the traverser during first [Iterator.hasNext] call that returns false + */ + fun iterationCompleted() + + /** + * The default [TraverserMedium], which does nothing. + */ + object DoNothing : TraverserMedium { + override fun iterationCompleted() {} } +}*/ + +sealed class RegionTraverser { - protected abstract suspend fun Scope.build(region: Region) + /** + * Get an iterator traversing [region] using this traverser. + * Depending on the implementation, [region] might be traversed in a specific order and direction. + */ + fun traverseRegion( + region: Region, + worldHeight: Int = 256/*, + medium: TraverserMedium = TraverserMedium.DoNothing*/ + ): Iterator = iterator { build(validify(region, worldHeight)/*, medium*/) } + + abstract suspend fun Scope.build(region: Region/*, medium: TraverserMedium = TraverserMedium.DoNothing*/) companion object { val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X)) @@ -34,9 +96,35 @@ sealed class RegionTraverser { val toClear get() = downward val toFill get() = upward + /** + * The returned [RegionTraverser] will traverse the regions + * * below and including absolute level [y] first, in [upward] direction. + * * above absolute level [y] last, in [downward] direction. + */ fun convergingTo(y: Int) = Slicing(y, upward, downward, true) + /** + * The returned [RegionTraverser] will traverse the regions + * * above absolute level [y] first, in [upward] direction. + * * below and including absolute level [y] second, in [downward] direction. + */ fun separatingFrom(y: Int) = Slicing(y, downward, upward, false) + + private fun validify(region: Region, worldHeight: Int): Region { + if (region.origin.y < 0) { + val origin = region.origin withY 0 + val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight)) + return Region(origin, size) + } + + if (region.origin.y + region.size.y > worldHeight) { + val size = region.size.withY(worldHeight - region.origin.y) + return Region(region.origin, size) + } + + return region + } + } class Directional( @@ -50,7 +138,7 @@ sealed class RegionTraverser { } } - override suspend fun Scope.build(region: Region) { + override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) { val order = order val (primary, secondary, tertiary) = order.toArray() val (origin, size) = region @@ -71,6 +159,7 @@ sealed class RegionTraverser { } } + /*medium.iterationCompleted()*/ } } @@ -91,7 +180,7 @@ sealed class RegionTraverser { return region to null } - override suspend fun Scope.build(region: Region) { + override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) { val (bottom, top) = slice(region, bottomSectionMaxY) if (bottomFirst) { @@ -101,15 +190,22 @@ sealed class RegionTraverser { top?.let { with(topTraverser) { build(it) } } with(bottomTraverser) { build(bottom) } } + + /*medium.iterationCompleted()*/ } } + /** + * Returns [Directional] instance that would be responsible for + * emitting the given position if it is contained in a region. + * [Directional] instance has a set order and direction + */ fun childForPosition(position: Vec3i): Directional { var cur = this while (true) { when (cur) { - is Directional -> - return cur + /*is ParcelTraverser -> cur = cur.delegate*/ + is Directional -> return cur is Slicing -> cur = if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser @@ -118,10 +214,17 @@ sealed class RegionTraverser { } } + /** + * Returns true if and only if this traverser would visit the given + * [block] position before the given [current] position. + * If at least one of [block] and [current] is not contained in a + * region being traversed the result is undefined. + */ fun comesFirst(current: Vec3i, block: Vec3i): Boolean { var cur = this while (true) { when (cur) { + /*is ParcelTraverser -> cur = cur.delegate*/ is Directional -> return cur.direction.comesFirst(current, block) is Slicing -> { val border = cur.bottomSectionMaxY diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index 84931b2..df3cfab 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -1,5 +1,7 @@ package io.dico.parcels2.blockvisitor +import io.dico.parcels2.JobFunction +import io.dico.parcels2.JobScope import io.dico.parcels2.util.math.Region import io.dico.parcels2.util.math.Vec3i import io.dico.parcels2.util.math.get diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index 41f5d36..bcc7997 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -1,12 +1,12 @@ package io.dico.parcels2.command -import io.dico.dicore.command.* -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.PlayerProfile.* -import io.dico.parcels2.PrivilegeKey -import io.dico.parcels2.blockvisitor.Job +import io.dico.dicore.command.CommandException +import io.dico.dicore.command.EMessageType +import io.dico.dicore.command.ExecutionContext +import io.dico.dicore.command.ICommandReceiver +import io.dico.parcels2.* +import io.dico.parcels2.PlayerProfile.Real +import io.dico.parcels2.PlayerProfile.Unresolved import io.dico.parcels2.util.ext.hasPermAdminManage import io.dico.parcels2.util.ext.parcelLimit import org.bukkit.entity.Player @@ -42,15 +42,13 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei else -> throw CommandException() } - protected fun areYouSureMessage(context: ExecutionContext) = "Are you sure? You cannot undo this action!\n" + - "Run \"/${context.route.joinToString(" ")} -sure\" if you want to go through with this." - - protected fun ParcelScope.clearWithProgressUpdates(context: ExecutionContext, action: String) { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - world.blockManager.clearParcel(parcel.id) + protected fun areYouSureMessage(context: ExecutionContext): String { + val command = (context.route + context.original).joinToString(" ") + " -sure" + return "Are you sure? You cannot undo this action!\n" + + "Run \"/$command\" if you want to go through with this." } - protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String) { + protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String): Job = onProgressUpdate(1000, 1000) { progress, elapsedTime -> val alt = context.getFormat(EMessageType.NUMBER) val main = context.getFormat(EMessageType.INFORMATIVE) @@ -59,7 +57,6 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei .format(progress * 100, elapsedTime / 1000.0) ) } - } override fun getCoroutineContext() = plugin.coroutineContext } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt index 79028a2..e700d6d 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt @@ -5,11 +5,10 @@ import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.Validate import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Flag -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.Privilege +import io.dico.parcels2.* import io.dico.parcels2.command.ParcelTarget.TargetKind -import io.dico.parcels2.resolved +import io.dico.parcels2.defaultimpl.DefaultParcelContainer +import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @@ -23,6 +22,33 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { return "${profile.notNullName}$fakeString is the new owner of (${parcel.id.idString})" } + @Cmd("update_all_owner_signs") + fun cmdUpdateAllOwnerSigns(context: ExecutionContext): Any? { + Validate.isAuthorized(context.sender, PERM_ADMIN_MANAGE) + plugin.jobDispatcher.dispatch { + fun getParcelCount(world: ParcelWorld) = (world.options.axisLimit * 2 + 1).let { it * it } + val parcelCount = plugin.parcelProvider.worlds.values.sumBy { getParcelCount(it) }.toDouble() + var processed = 0 + for (world in plugin.parcelProvider.worlds.values) { + markSuspensionPoint() + + val container = world.container as? DefaultParcelContainer + if (container == null) { + processed += getParcelCount(world) + setProgress(processed / parcelCount) + continue + } + + for (parcel in container.getAllParcels()) { + parcel.updateOwnerSign(force = true) + processed++ + setProgress(processed / parcelCount) + } + } + }.reportProgressUpdates(context, "Updating") + return null + } + @Cmd("dispose") @RequireParcelPrivilege(Privilege.ADMIN) fun ParcelScope.cmdDispose(): Any? { @@ -37,15 +63,17 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { if (!sure) return areYouSureMessage(context) parcel.dispose() - world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Reset") + world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Reset") return "Data of (${parcel.id.idString}) has been disposed" } @Cmd("swap") @RequireParcelPrivilege(Privilege.ADMIN) - fun ParcelScope.cmdSwap(context: ExecutionContext, - @TargetKind(TargetKind.ID) target: ParcelTarget, - @Flag sure: Boolean): Any? { + fun ParcelScope.cmdSwap( + context: ExecutionContext, + @TargetKind(TargetKind.ID) target: ParcelTarget, + @Flag sure: Boolean + ): Any? { Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") if (!sure) return areYouSureMessage(context) @@ -53,13 +81,14 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { ?: throw CommandException("Invalid parcel target") // Validate.isTrue(parcel2.world == world, "Parcel must be in the same world") - Validate.isTrue(!parcel2.hasBlockVisitors, "A process is already running in this parcel") + Validate.isTrue(!parcel2.hasBlockVisitors, "A process is already running in that parcel") val data = parcel.data parcel.copyData(parcel2.data) parcel2.copyData(data) - world.blockManager.swapParcels(parcel.id, parcel2.id).reportProgressUpdates(context, "Swap") + val job = plugin.parcelProvider.swapParcels(parcel.id, parcel2.id)?.reportProgressUpdates(context, "Swap") + Validate.notNull(job, "A process is already running in some parcel (internal error)") return null } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index c6a4d6a..e0bde7d 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -1,5 +1,6 @@ package io.dico.parcels2.command +import io.dico.dicore.Formatting import io.dico.dicore.command.* import io.dico.dicore.command.IContextFilter.Priority.PERMISSION import io.dico.dicore.command.annotation.Cmd @@ -54,9 +55,9 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { ) val random = Random() - world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block -> + world.blockManager.tryDoBlockOperation(plugin.parcelProvider, parcel.id, traverser = RegionTraverser.upward) { block -> block.blockData = blockDatas[random.nextInt(7)] - }.onProgressUpdate(1000, 1000) { progress, elapsedTime -> + }?.onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage( EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" .format(progress * 100, elapsedTime / 1000.0) @@ -82,7 +83,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("jobs") fun cmdJobs(): Any? { val workers = plugin.jobDispatcher.jobs - println(workers.map { it.job }.joinToString(separator = "\n")) + println(workers.map { it.coroutine }.joinToString(separator = "\n")) return "Task count: ${workers.size}" } @@ -95,7 +96,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @PreprocessArgs fun cmdMessage(sender: CommandSender, message: String): Any? { // testing @PreprocessArgs which merges "hello there" into a single argument - sender.sendMessage(message) + sender.sendMessage(Formatting.translate(message)) return null } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 7f791cf..c918b80 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -127,7 +127,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") if (!sure) return areYouSureMessage(context) - world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Clear") + world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear") return null } @@ -135,7 +135,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab @RequireParcelPrivilege(Privilege.OWNER) fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - world.blockManager.setBiome(parcel.id, biome).reportProgressUpdates(context, "Biome change") + world.blockManager.setBiome(parcel.id, biome)?.reportProgressUpdates(context, "Biome change") return null } diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index f4d051e..70a8dda 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -92,8 +92,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef override fun parse(parameter: Parameter, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget { var input = buffer.next() - val worldString = input.substringBefore("->", missingDelimiterValue = "") - input = input.substringAfter("->") + val worldString = input.substringBefore("/", missingDelimiterValue = "") + input = input.substringAfter("/") val world = if (worldString.isEmpty()) { val player = requirePlayer(sender, parameter, "the world") diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt index 24db275..1193af3 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt @@ -64,7 +64,7 @@ class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer { } } - fun allParcels(): Sequence = sequence { + fun getAllParcels(): Iterator = iterator { for (array in parcels) { yieldAll(array.iterator()) } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt index 51671d4..e9ec148 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -1,22 +1,14 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.* +import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.options.DefaultGeneratorOptions -import io.dico.parcels2.util.math.Region -import io.dico.parcels2.util.math.Vec2i -import io.dico.parcels2.util.math.Vec3i -import io.dico.parcels2.util.math.even -import io.dico.parcels2.util.math.umod -import io.dico.parcels2.util.math.get +import io.dico.parcels2.util.math.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart.UNDISPATCHED -import kotlinx.coroutines.launch import org.bukkit.* import org.bukkit.block.Biome import org.bukkit.block.BlockFace import org.bukkit.block.Skull -import org.bukkit.block.data.BlockData import org.bukkit.block.data.type.Slab import org.bukkit.block.data.type.WallSign import java.util.Random @@ -118,12 +110,13 @@ class DefaultParcelGenerator( } override fun makeParcelLocatorAndBlockManager( - worldId: ParcelWorldId, + parcelProvider: ParcelProvider, container: ParcelContainer, coroutineScope: CoroutineScope, jobDispatcher: JobDispatcher ): Pair { - return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, jobDispatcher) + val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher) + return impl to impl } private inline fun convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? { @@ -139,11 +132,25 @@ class DefaultParcelGenerator( return null } - private inner class ParcelLocatorImpl( - val worldId: ParcelWorldId, - val container: ParcelContainer - ) : ParcelLocator { - override val world: World = this@DefaultParcelGenerator.world + @Suppress("DEPRECATION") + private inner class ParcelLocatorAndBlockManagerImpl( + val parcelProvider: ParcelProvider, + val container: ParcelContainer, + coroutineScope: CoroutineScope, + override val jobDispatcher: JobDispatcher + ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope { + + override val world: World get() = this@DefaultParcelGenerator.world + val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world) + override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight) + + private val cornerWallType = when { + o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } + o.wallType.material.name.endsWith("CARPET") -> { + Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL")) + } + else -> null + } override fun getParcelAt(x: Int, z: Int): Parcel? { return convertBlockLocationToId(x, z, container::getParcelById) @@ -152,112 +159,113 @@ class DefaultParcelGenerator( override fun getParcelIdAt(x: Int, z: Int): ParcelId? { return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) } } - } - @Suppress("DEPRECATION") - private inner class ParcelBlockManagerImpl( - val worldId: ParcelWorldId, - coroutineScope: CoroutineScope, - override val jobDispatcher: JobDispatcher - ) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope { - override val world: World = this@DefaultParcelGenerator.world - override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight) - /*override*/ fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i( - sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, - sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ - ) + private fun checkParcelId(parcel: ParcelId): ParcelId { + if (!parcel.worldId.equals(worldId)) { + throw IllegalArgumentException() + } + return parcel + } - override fun getHomeLocation(parcel: ParcelId): Location { - val bottom = getBottomBlock(parcel) - val x = bottom.x + (o.parcelSize - 1) / 2.0 - val z = bottom.z - 2 - return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) + override fun getRegionOrigin(parcel: ParcelId): Vec2i { + checkParcelId(parcel) + return Vec2i( + sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, + sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ + ) } override fun getRegion(parcel: ParcelId): Region { - val bottom = getBottomBlock(parcel) + val origin = getRegionOrigin(parcel) return Region( - Vec3i(bottom.x, 0, bottom.z), + Vec3i(origin.x, 0, origin.z), Vec3i(o.parcelSize, maxHeight, o.parcelSize) ) } - private fun getRegionConsideringWorld(parcel: ParcelId): Region { - if (parcel.worldId != worldId) { - (parcel.worldId as? ParcelWorld)?.let { - return it.blockManager.getRegion(parcel) + override fun getHomeLocation(parcel: ParcelId): Location { + val origin = getRegionOrigin(parcel) + val x = origin.x + (o.parcelSize - 1) / 2.0 + val z = origin.z - 2 + return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) + } + + override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? { + if (block.y != o.floorHeight + 1) return null + + val expectedParcelOrigin = when (type) { + Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2) + o.wallType.material, cornerWallType?.material -> { + if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) { + return null + } + + Vec2i(block.x + 1, block.z + 1) } - throw IllegalArgumentException() + else -> return null } - return getRegion(parcel) + + return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z) + ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) } + ?.also { parcel -> + if (type != Material.WALL_SIGN && parcel.owner != null) { + updateParcelInfo(parcel.id, parcel.owner) + parcel.isOwnerSignOutdated = false + } + } } - override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) { - val b = getBottomBlock(parcel) + override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean { + val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk() + return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z) + } + + override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) { + val b = getRegionOrigin(parcel) val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) - val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1) + val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2) val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1) if (owner == null) { wallBlock.blockData = o.wallType signBlock.type = Material.AIR skullBlock.type = Material.AIR - } else { - val wallBlockType: BlockData = if (o.wallType is Slab) - (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } - else - o.wallType - - wallBlock.blockData = wallBlockType + } else { + cornerWallType?.let { wallBlock.blockData = it } signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH } val sign = signBlock.state as org.bukkit.block.Sign sign.setLine(0, "${parcel.x},${parcel.z}") - sign.setLine(2, owner.name) + sign.setLine(2, owner.name ?: "") sign.update() + skullBlock.type = Material.AIR skullBlock.type = Material.PLAYER_HEAD val skull = skullBlock.state as Skull if (owner is PlayerProfile.Real) { skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid) - } else { - skull.owner = owner.name + + } else if (!skull.setOwner(owner.name)) { + skullBlock.type = Material.AIR + return } - skull.rotation = BlockFace.WEST + + skull.rotation = BlockFace.SOUTH skull.update() } } - private fun getParcel(parcelId: ParcelId): Parcel? { - // todo dont rely on this cast - val world = worldId as? ParcelWorld ?: return null - return world.getParcelById(parcelId) + private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? { + parcels.forEach { checkParcelId(it) } + return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function) } - override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job { - val parcels = parcelIds.mapNotNull { getParcel(it) } - if (parcels.isEmpty()) return jobDispatcher.dispatch(task) - if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor") - - val worker = jobDispatcher.dispatch(task) - - for (parcel in parcels) { - launch(start = UNDISPATCHED) { - parcel.withBlockVisitorPermit { - worker.awaitCompletion() - } - } - } - - return worker - } - - override fun setBiome(parcel: ParcelId, biome: Biome): Job = submitBlockVisitor(parcel) { + override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) { val world = world - val b = getBottomBlock(parcel) + val b = getRegionOrigin(parcel) val parcelSize = o.parcelSize for (x in b.x until b.x + parcelSize) { for (z in b.z until b.z + parcelSize) { @@ -267,7 +275,7 @@ class DefaultParcelGenerator( } } - override fun clearParcel(parcel: ParcelId): Job = submitBlockVisitor(parcel) { + override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) { val region = getRegion(parcel) val blocks = parcelTraverser.traverseRegion(region) val blockCount = region.blockCount.toDouble() @@ -291,22 +299,6 @@ class DefaultParcelGenerator( } } - override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Job = submitBlockVisitor(parcel1, parcel2) { - var region1 = getRegionConsideringWorld(parcel1) - var region2 = getRegionConsideringWorld(parcel2) - - val size = region1.size.clampMax(region2.size) - if (size != region1.size) { - region1 = region1.withSize(size) - region2 = region2.withSize(size) - } - - val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(world, region1) } } - val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(world, region2) } } - delegateWork(0.25) { with(schematicOf1) { paste(world, region2.origin) } } - delegateWork(0.25) { with(schematicOf2) { paste(world, region1.origin) } } - } - override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection { /* * Get the offsets for the world out of the way diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index 16136e9..db5d193 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -3,59 +3,84 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* import io.dico.parcels2.Privilege.* import io.dico.parcels2.util.ext.alsoIfTrue +import io.dico.parcels2.util.isServerThread import io.dico.parcels2.util.math.Vec2i import org.bukkit.Material import org.joda.time.DateTime -import java.util.concurrent.atomic.AtomicInteger +import java.lang.IllegalStateException -class ParcelImpl( +class ParcelImpl ( override val world: ParcelWorld, override val x: Int, override val z: Int ) : Parcel, ParcelId { - override val id: ParcelId = this + override val id: ParcelId get() = this override val pos get() = Vec2i(x, z) - override var data: ParcelDataHolder = ParcelDataHolder(); private set - override val hasBlockVisitors get() = blockVisitors.get() > 0 + override var data = ParcelDataHolder(); private set override val worldId: ParcelWorldId get() = world.id - override fun copyDataIgnoringDatabase(data: ParcelData) { - this.data = ((data as? Parcel)?.data ?: data) as ParcelDataHolder - } + override fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean) { + if (callerIsDatabase) { + data = newData + return + } - override fun copyData(data: ParcelData) { - copyDataIgnoringDatabase(data) - world.storage.setParcelData(this, data) - } + val ownerChanged = owner != newData.owner + if (ownerChanged) { + updateOwnerSign(true, false, true) + } - override fun dispose() { - copyDataIgnoringDatabase(ParcelDataHolder()) - world.storage.setParcelData(this, null) - } + val ownerSignWasOutdated = if (callerIsDatabase) newData.isOwnerSignOutdated else isOwnerSignOutdated + val ownerChanged = owner != newData.owner + + data = newData + + if (ownerChanged && isServerThread()) { + updateOwnerSign(true, false, updateDatabase = callerIsDatabase) + } else { + newData.isOwnerSignOutdated = ownerChanged || ownerSignWasOutdated + } + + world.storage.setParcelData(this, data) + } override var owner: PlayerProfile? get() = data.owner set(value) { if (data.owner != value) { world.storage.setParcelOwner(this, value) - world.blockManager.setOwnerBlock(this, value) data.owner = value + updateOwnerSign(true, false, true) } } override val lastClaimTime: DateTime? get() = data.lastClaimTime - override var ownerSignOutdated: Boolean - get() = data.ownerSignOutdated + override var isOwnerSignOutdated: Boolean + get() = data.isOwnerSignOutdated set(value) { - if (data.ownerSignOutdated != value) { + if (data.isOwnerSignOutdated != value) { world.storage.setParcelOwnerSignOutdated(this, value) - data.ownerSignOutdated = value + data.isOwnerSignOutdated = value } } + override fun updateOwnerSign(force: Boolean) { + updateOwnerSign(false, force, true) + } + + private fun updateOwnerSign(ownerChanged: Boolean, force: Boolean, updateDatabase: Boolean) { + if (!ownerChanged && !isOwnerSignOutdated && !force) return + + val update = force || world.blockManager.isParcelInfoSectionLoaded(this) + if (update) world.blockManager.updateParcelInfo(this, owner) + + if (updateDatabase) isOwnerSignOutdated = !update + else data.isOwnerSignOutdated = !update + } + override val privilegeMap: PrivilegeMap get() = data.privilegeMap @@ -106,7 +131,24 @@ class ParcelImpl( } } + override val hasBlockVisitors: Boolean + get() = permit != null + private var permit: Permit? = null + + fun acquireBlockVisitorPermit(with: Permit): Boolean { + if (permit === with) return true + if (permit != null) return false + permit = with + return true + } + + fun releaseBlockVisitorPermit(with: Permit) { + if (permit !== with) throw IllegalStateException() + permit = null + } + + /* private var blockVisitors = AtomicInteger(0) override suspend fun withBlockVisitorPermit(block: suspend () -> Unit) { @@ -116,9 +158,9 @@ class ParcelImpl( } finally { blockVisitors.getAndDecrement() } - } + }*/ - override fun toString() = toStringExt() + override fun toString() = parcelIdToString() override val infoString: String get() = getInfoString() diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt index 7a2534f..48a7fee 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -1,8 +1,10 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* +import io.dico.parcels2.blockvisitor.Schematic import io.dico.parcels2.util.schedule -import kotlinx.coroutines.Unconfined +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.bukkit.Bukkit import org.bukkit.WorldCreator @@ -44,10 +46,11 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { private fun loadWorlds0() { if (Bukkit.getWorlds().isEmpty()) { plugin.schedule(::loadWorlds0) - plugin.logger.warning("Scheduling to load worlds in the next tick, \nbecause no bukkit worlds are loaded yet") + plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet") return } + val newlyCreatedWorlds = mutableListOf() for ((worldName, worldOptions) in options.worlds.entries) { var parcelWorld = _worlds[worldName] if (parcelWorld != null) continue @@ -56,19 +59,20 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { val worldExists = Bukkit.getWorld(worldName) != null val bukkitWorld = if (worldExists) Bukkit.getWorld(worldName)!! - else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") } + else { + logger.info("Creating world $worldName") + WorldCreator(worldName).generator(generator).createWorld() + } - parcelWorld = ParcelWorldImpl( - bukkitWorld, generator, worldOptions.runtime, plugin.storage, - plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.jobDispatcher - ) + parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer) if (!worldExists) { val time = DateTime.now() plugin.storage.setWorldCreationTime(parcelWorld.id, time) parcelWorld.creationTime = time + newlyCreatedWorlds.add(parcelWorld) } else { - launch(context = Unconfined) { + GlobalScope.launch(context = Dispatchers.Unconfined) { parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now() } } @@ -76,11 +80,11 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { _worlds[worldName] = parcelWorld } - loadStoredData() + loadStoredData(newlyCreatedWorlds.toSet()) } - private fun loadStoredData() { - plugin.launch { + private fun loadStoredData(newlyCreatedWorlds: Collection = emptyList()) { + plugin.launch(Dispatchers.Default) { val migration = plugin.options.migration if (migration.enabled) { migration.instance?.newInstance()?.apply { @@ -96,11 +100,14 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { } logger.info("Loading all parcel data...") - val channel = plugin.storage.transmitAllParcelData() - while (true) { - val (id, data) = channel.receiveOrNull() ?: break - val parcel = getParcelById(id) ?: continue - data?.let { parcel.copyDataIgnoringDatabase(it) } + + val job1 = launch { + val channel = plugin.storage.transmitAllParcelData() + while (true) { + val (id, data) = channel.receiveOrNull() ?: break + val parcel = getParcelById(id) ?: continue + data?.let { parcel.copyData(it, callerIsDatabase = true) } + } } val channel2 = plugin.storage.transmitAllGlobalPrivileges() @@ -113,11 +120,61 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data) } + job1.join() + logger.info("Loading data completed") _dataIsLoaded = true } } + override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean { + val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true + return parcel.acquireBlockVisitorPermit(with) + } + + override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) { + val parcel = getParcelById(parcelId) as? ParcelImpl ?: return + parcel.releaseBlockVisitorPermit(with) + } + + override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? { + val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) } + if (withPermit.size != parcelIds.size) { + withPermit.forEach { releaseBlockVisitorPermit(it, permit) } + return null + } + + val job = plugin.jobDispatcher.dispatch(function) + + plugin.launch { + job.awaitCompletion() + withPermit.forEach { releaseBlockVisitorPermit(it, permit) } + } + + return job + } + + override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? { + val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null + val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null + + return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) { + var region1 = blockManager1.getRegion(parcelId1) + var region2 = blockManager2.getRegion(parcelId2) + + val size = region1.size.clampMax(region2.size) + if (size != region1.size) { + region1 = region1.withSize(size) + region2 = region2.withSize(size) + } + + val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } } + val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } } + delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } } + delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } } + } + } + /* fun loadWorlds(options: Options) { for ((worldName, worldOptions) in options.worlds.entries) { diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt index 78257c3..531a25f 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -3,30 +3,27 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.JobDispatcher import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.storage.Storage import kotlinx.coroutines.CoroutineScope +import org.bukkit.GameRule import org.bukkit.World import org.joda.time.DateTime import java.util.UUID -class ParcelWorldImpl(override val world: World, - override val generator: ParcelGenerator, - override var options: RuntimeWorldOptions, - override val storage: Storage, - override val globalPrivileges: GlobalPrivilegesManager, - containerFactory: ParcelContainerFactory, - coroutineScope: CoroutineScope, - jobDispatcher: JobDispatcher) - : ParcelWorld, - ParcelWorldId, - ParcelContainer, /* missing delegation */ - ParcelLocator /* missing delegation */ { - +class ParcelWorldImpl( + val plugin: ParcelsPlugin, + override val world: World, + override val generator: ParcelGenerator, + override var options: RuntimeWorldOptions, + containerFactory: ParcelContainerFactory +) : ParcelWorld, ParcelWorldId, ParcelContainer, ParcelLocator { override val id: ParcelWorldId get() = this override val uid: UUID? get() = world.uid + override val storage get() = plugin.storage + override val globalPrivileges get() = plugin.globalPrivileges + init { if (generator.world != world) { throw IllegalArgumentException() @@ -39,52 +36,40 @@ class ParcelWorldImpl(override val world: World, override val blockManager: ParcelBlockManager init { - val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, jobDispatcher) - locator = pair.first - blockManager = pair.second - + val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher) + this.locator = locator + this.blockManager = blockManager enforceOptions() } fun enforceOptions() { if (options.dayTime) { - world.setGameRuleValue("doDaylightCycle", "false") + world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false) world.setTime(6000) } if (options.noWeather) { world.setStorm(false) world.setThundering(false) - world.weatherDuration = Integer.MAX_VALUE + world.weatherDuration = Int.MAX_VALUE } - world.setGameRuleValue("doTileDrops", "${options.doTileDrops}") + world.setGameRule(GameRule.DO_TILE_DROPS, options.doTileDrops) } - // Updated by ParcelProviderImpl + // Accessed by ParcelProviderImpl override var creationTime: DateTime? = null - /* - Interface delegation needs to be implemented manually because JetBrains has yet to fix it. - */ - // ParcelLocator interface - override fun getParcelAt(x: Int, z: Int): Parcel? { - return locator.getParcelAt(x, z) - } + override fun getParcelAt(x: Int, z: Int): Parcel? = locator.getParcelAt(x, z) - override fun getParcelIdAt(x: Int, z: Int): ParcelId? { - return locator.getParcelIdAt(x, z) - } + override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z) - // ParcelContainer interface - override fun getParcelById(x: Int, z: Int): Parcel? { - return container.getParcelById(x, z) - } + override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z) - override fun nextEmptyParcel(): Parcel? { - return container.nextEmptyParcel() - } + override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id) + + override fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel() - override fun toString() = toStringExt() + override fun toString() = parcelWorldIdToString() } diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index 6c3b355..04941b4 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -2,16 +2,13 @@ package io.dico.parcels2.listener import gnu.trove.TLongCollection import gnu.trove.set.hash.TLongHashSet +import io.dico.dicore.Formatting import io.dico.dicore.ListenerMarker import io.dico.dicore.RegistratorListener import io.dico.parcels2.* import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.ext.* -import io.dico.parcels2.util.math.Dimension -import io.dico.parcels2.util.math.Vec3d -import io.dico.parcels2.util.math.Vec3i -import io.dico.parcels2.util.math.clampMax -import io.dico.parcels2.util.math.clampMin +import io.dico.parcels2.util.math.* import org.bukkit.Location import org.bukkit.Material.* import org.bukkit.World @@ -128,6 +125,8 @@ class ParcelListeners( if (!canBuildOnArea(event.player, area)) { event.isCancelled = true } + + area?.updateOwnerSign() } /* @@ -253,10 +252,15 @@ class ParcelListeners( } } - onPlayerInteractEvent_RightClick(event, world, parcel) + onPlayerRightClick(event, world, parcel) + + if (!event.isCancelled && parcel == null) { + world.blockManager.getParcelForInfoBlockInteraction(Vec3i(clickedBlock), type, event.blockFace) + ?.apply { user.sendMessage(Formatting.GREEN + infoString) } + } } - Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel) + Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel) Action.PHYSICAL -> if (!canBuildOnArea(user, parcel) && !(parcel != null && parcel.interactableConfig("pressure_plates"))) { user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") event.isCancelled = true; return@l @@ -265,7 +269,7 @@ class ParcelListeners( } @Suppress("NON_EXHAUSTIVE_WHEN") - private fun onPlayerInteractEvent_RightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) { + private fun onPlayerRightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) { if (event.hasItem()) { val item = event.item.type if (world.options.blockedItems.contains(item)) { @@ -275,7 +279,9 @@ class ParcelListeners( if (!canBuildOnArea(event.player, parcel)) { when (item) { - LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> event.isCancelled = true + LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> { + event.isCancelled = true + } } } } @@ -614,9 +620,9 @@ class ParcelListeners( if (parcels.isEmpty()) return@l parcels.forEach { id -> - val parcel = world.getParcelById(id)?.takeIf { it.ownerSignOutdated } ?: return@forEach - world.blockManager.setOwnerBlock(parcel.id, parcel.owner) - parcel.ownerSignOutdated = false + val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach + world.blockManager.updateParcelInfo(parcel.id, parcel.owner) + parcel.isOwnerSignOutdated = false } } diff --git a/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt b/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt index 2ad10e9..5e36099 100644 --- a/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt +++ b/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt @@ -18,4 +18,5 @@ private class PlotmeMigrationFactory : PolymorphicOptionsFactory { } class PlotmeMigrationOptions(val worldsFromTo: Map = mapOf("plotworld" to "parcels"), - val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme"))) \ No newline at end of file + val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme")), + val tableNamesUppercase: Boolean = false) \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/Options.kt b/src/main/kotlin/io/dico/parcels2/options/Options.kt index dc80db9..35d48ba 100644 --- a/src/main/kotlin/io/dico/parcels2/options/Options.kt +++ b/src/main/kotlin/io/dico/parcels2/options/Options.kt @@ -1,6 +1,6 @@ package io.dico.parcels2.options -import io.dico.parcels2.blockvisitor.TickJobtimeOptions +import io.dico.parcels2.TickJobtimeOptions import org.bukkit.GameMode import org.bukkit.Material import java.io.Reader diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 2e0a8ee..12d5bc7 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -43,14 +43,14 @@ interface Backing { fun transmitAllParcelData(channel: SendChannel) - fun readParcelData(parcel: ParcelId): ParcelData? + fun readParcelData(parcel: ParcelId): ParcelDataHolder? fun getOwnedParcels(user: PlayerProfile): List fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size - fun setParcelData(parcel: ParcelId, data: ParcelData?) + fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index abd9f41..d718f20 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -13,7 +13,7 @@ import org.joda.time.DateTime import java.util.UUID import kotlin.coroutines.CoroutineContext -typealias DataPair = Pair +typealias DataPair = Pair typealias PrivilegePair = Pair interface Storage { @@ -33,7 +33,7 @@ interface Storage { fun updatePlayerName(uuid: UUID, name: String): Job - fun readParcelData(parcel: ParcelId): Deferred + fun readParcelData(parcel: ParcelId): Deferred fun transmitParcelData(parcels: Sequence): ReceiveChannel @@ -44,7 +44,7 @@ interface Storage { fun getNumParcels(user: PlayerProfile): Deferred - fun setParcelData(parcel: ParcelId, data: ParcelData?): Job + fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?): Job fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job @@ -62,7 +62,7 @@ interface Storage { fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job - fun getChannelToUpdateParcelData(): SendChannel> + fun getChannelToUpdateParcelData(): SendChannel> } class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope { @@ -93,7 +93,7 @@ class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineSco override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) } - override fun setParcelData(parcel: ParcelId, data: ParcelData?) = b.launchJob { b.setParcelData(parcel, data) } + override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) = b.launchJob { b.setParcelData(parcel, data) } override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) } @@ -110,5 +110,5 @@ class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineSco override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) } - override fun getChannelToUpdateParcelData(): SendChannel> = b.openChannelForWriting { b.setParcelData(it.first, it.second) } + override fun getChannelToUpdateParcelData(): SendChannel> = b.openChannelForWriting { b.setParcelData(it.first, it.second) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index fa68338..e49be79 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -9,6 +9,7 @@ import io.dico.parcels2.storage.* import io.dico.parcels2.util.math.clampMax import io.dico.parcels2.util.ext.synchronized import kotlinx.coroutines.* +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.ArrayChannel import kotlinx.coroutines.channels.LinkedListChannel import kotlinx.coroutines.channels.ReceiveChannel @@ -152,7 +153,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi channel.close() } - override fun readParcelData(parcel: ParcelId): ParcelData? { + override fun readParcelData(parcel: ParcelId): ParcelDataHolder? { val row = ParcelsT.getRow(parcel) ?: return null return rowToParcelData(row) } @@ -165,7 +166,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi .toList() } - override fun setParcelData(parcel: ParcelId, data: ParcelData?) { + override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) { if (data == null) { transaction { ParcelsT.getId(parcel)?.let { id -> @@ -262,7 +263,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) } lastClaimTime = row[ParcelsT.claim_time] - ownerSignOutdated = row[ParcelsT.sign_oudated] + isOwnerSignOutdated = row[ParcelsT.sign_oudated] val id = row[ParcelsT.id] ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt index 6ebac6d..0245625 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt @@ -5,8 +5,7 @@ import org.jetbrains.exposed.sql.Function import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.transactions.TransactionManager -class UpsertStatement(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) - : InsertStatement(table, false) { +class UpsertStatement(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement(table, false) { val indexName: String val indexColumns: List> @@ -66,9 +65,10 @@ class Abs(val expr: Expression) : Function(IntegerColumnType()) override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})" } -fun > SqlExpressionBuilder.greater(col1: ExpressionWithColumnType, col2: ExpressionWithColumnType): Expression { - return case(col1) - .When(col1.greater(col2), col1) - .Else(col2) -} +fun > greaterOf(col1: ExpressionWithColumnType, col2: ExpressionWithColumnType): Expression = + with(SqlExpressionBuilder) { + case(col1) + .When(col1.greater(col2), col1) + .Else(col2) + } diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt index a79d315..831fe42 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -7,8 +7,10 @@ import io.dico.parcels2.* import io.dico.parcels2.options.PlotmeMigrationOptions import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.exposed.abs -import io.dico.parcels2.storage.exposed.greater +import io.dico.parcels2.storage.exposed.greaterOf import io.dico.parcels2.storage.migration.Migration +import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmePlotPlayerMap +import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmeTable import io.dico.parcels2.storage.toUUID import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -24,6 +26,7 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { private var database: Database? = null private var isShutdown: Boolean = false private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") + private val tables = PlotmeTables(options.tableNamesUppercase) val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread") private fun transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) @@ -51,9 +54,9 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { isShutdown = true } - suspend fun doWork(target: Storage) { + suspend fun doWork(target: Storage) = with (tables) { val exit = transaction { - (!PlotmePlotsT.exists()).also { + (!PlotmePlots.exists()).also { if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.") } } @@ -75,29 +78,32 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { } mlogger.info("Transmitting data from plotmeplots table") + var count = 0 transaction { - PlotmePlotsT.selectAll() - .orderBy(PlotmePlotsT.world_name) - .orderBy(with(SqlExpressionBuilder) { greater(PlotmePlotsT.px.abs(), PlotmePlotsT.pz.abs()) }) + + PlotmePlots.selectAll() + .orderBy(PlotmePlots.world_name) + .orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs())) .forEach { row -> - val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach - val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name]) + val parcel = getParcelId(PlotmePlots, row) ?: return@forEach + val owner = PlayerProfile.safe(row[PlotmePlots.owner_uuid]?.toUUID(), row[PlotmePlots.owner_name]) target.setParcelOwner(parcel, owner) target.setParcelOwnerSignOutdated(parcel, true) + ++count } } mlogger.info("Transmitting data from plotmeallowed table") transaction { - PlotmeAllowedT.transmitPlotmeAddedTable(Privilege.CAN_BUILD) + PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD) } mlogger.info("Transmitting data from plotmedenied table") transaction { - PlotmeDeniedT.transmitPlotmeAddedTable(Privilege.BANNED) + PlotmeDenied.transmitPlotmeAddedTable(Privilege.BANNED) } - mlogger.warn("Data has been **transmitted**.") + mlogger.warn("Data has been **transmitted**. $count plots were migrated to the parcels database.") mlogger.warn("Loading parcel data might take a while as enqueued transactions from this migration are completed.") } diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt index 8564ad3..dc788c8 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt @@ -2,25 +2,30 @@ package io.dico.parcels2.storage.migration.plotme import org.jetbrains.exposed.sql.Table -const val uppercase: Boolean = false -@Suppress("ConstantConditionIf") -fun String.toCorrectCase() = if (uppercase) this else toLowerCase() - -sealed class PlotmeTable(name: String) : Table(name) { - val px = integer("idX").primaryKey() - val pz = integer("idZ").primaryKey() - val world_name = varchar("world", 32).primaryKey() -} +class PlotmeTables(val uppercase: Boolean) { + fun String.toCorrectCase() = if (uppercase) this else toLowerCase() -object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) { - val owner_name = varchar("owner", 32) - val owner_uuid = blob("ownerid").nullable() -} + val PlotmePlots = PlotmePlotsT() + val PlotmeAllowed = PlotmeAllowedT() + val PlotmeDenied = PlotmeDeniedT() + + inner abstract class PlotmeTable(name: String) : Table(name) { + val px = integer("idX").primaryKey() + val pz = integer("idZ").primaryKey() + val world_name = varchar("world", 32).primaryKey() + } + + inner abstract class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) { + val player_name = varchar("player", 32) + val player_uuid = blob("playerid").nullable() + } + + inner class PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) { + val owner_name = varchar("owner", 32) + val owner_uuid = blob("ownerid").nullable() + } -sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) { - val player_name = varchar("player", 32) - val player_uuid = blob("playerid").nullable() + inner class PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) + inner class PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase()) } -object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) -object PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase()) diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt index d6837bd..618eaed 100644 --- a/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt @@ -10,3 +10,5 @@ fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid } fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid } + +fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread" diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt index 9385535..5945120 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt @@ -3,4 +3,7 @@ package io.dico.parcels2.util.math data class Vec2i( val x: Int, val z: Int -) +) { + fun add(ox: Int, oz: Int) = Vec2i(x + ox, z + oz) + fun toChunk() = Vec2i(x shr 4, z shr 4) +} diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt index a721afa..b25764e 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt @@ -11,7 +11,9 @@ data class Vec3i( val z: Int ) { constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ) + constructor(block: Block) : this(block.x, block.y, block.z) + fun toVec2i() = Vec2i(x, z) operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z) operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z) infix fun addX(o: Int) = Vec3i(x + o, y, z) -- cgit v1.2.3