From 2225bdae95b3de4985347edf30ae31a28f73f35b Mon Sep 17 00:00:00 2001 From: Dico Date: Wed, 26 Sep 2018 02:42:15 +0100 Subject: Work on schematic and parcel swaps --- build.gradle.kts | 2 +- .../kotlin/io/dico/parcels2/ParcelGenerator.kt | 4 +- src/main/kotlin/io/dico/parcels2/PlayerProfile.kt | 8 +- src/main/kotlin/io/dico/parcels2/Privilege.kt | 2 +- .../io/dico/parcels2/blockvisitor/Attachables.kt | 2 +- .../dico/parcels2/blockvisitor/RegionTraverser.kt | 141 +++++++++++++-------- .../io/dico/parcels2/blockvisitor/Schematic.kt | 65 +++++++--- .../dico/parcels2/blockvisitor/WorktimeLimiter.kt | 129 ++++++++++++------- .../parcels2/command/AbstractParcelCommands.kt | 21 +-- .../io/dico/parcels2/command/CommandsAdmin.kt | 30 ++++- .../io/dico/parcels2/command/CommandsDebug.kt | 18 +++ .../io/dico/parcels2/command/CommandsGeneral.kt | 8 +- .../parcels2/command/CommandsPrivilegesLocal.kt | 19 +-- .../parcels2/defaultimpl/DefaultParcelGenerator.kt | 27 ++-- src/main/kotlin/io/dico/parcels2/util/ext/Math.kt | 1 + 15 files changed, 311 insertions(+), 166 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 06f2041..c745ca5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13")) - c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-rc-conf") + c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-eap13") // not on sk89q maven repo yet compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar")) diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt index d69984b..c11d557 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -59,7 +59,9 @@ interface ParcelBlockManager { fun clearParcel(parcel: ParcelId): Worker - fun submitBlockVisitor(parcelId: ParcelId, task: TimeLimitedTask): Worker + fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker + + fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker /** * Used to update owner blocks in the corner of the parcel diff --git a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt index c430340..53e2d76 100644 --- a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt +++ b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt @@ -226,19 +226,19 @@ class PlayerProfile2 private constructor(uuid: UUID?, ?: PlayerProfile(null, input, !allowFake) } - operator fun invoke(name: String): PlayerProfile { + operator fun createWith(name: String): PlayerProfile { if (name == star.name) return star return PlayerProfile(null, name) } - operator fun invoke(uuid: UUID): PlayerProfile { + operator fun createWith(uuid: UUID): PlayerProfile { if (uuid == star.uuid) return star return PlayerProfile(uuid, null) } - operator fun invoke(player: OfflinePlayer): PlayerProfile { + operator fun createWith(player: OfflinePlayer): PlayerProfile { // avoid UUID comparison against STAR - return if (player.isValid) PlayerProfile(player.uuid, player.name) else invoke(player.name) + return if (player.isValid) PlayerProfile(player.uuid, player.name) else createWith(player.name) } } diff --git a/src/main/kotlin/io/dico/parcels2/Privilege.kt b/src/main/kotlin/io/dico/parcels2/Privilege.kt index 040e5f5..72878a9 100644 --- a/src/main/kotlin/io/dico/parcels2/Privilege.kt +++ b/src/main/kotlin/io/dico/parcels2/Privilege.kt @@ -88,7 +88,7 @@ interface Privileges : PrivilegesMinimal { } fun changePrivilege(key: PrivilegeKey, positive: Boolean, update: Privilege): PrivilegeChangeResult = - if (key != keyOfOwner) FAIL_OWNER + if (key == keyOfOwner) FAIL_OWNER else if (getStoredPrivilege(key).isChangeInDirection(positive, update) && setStoredPrivilege(key, update) ) SUCCESS diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index 84d8a58..f5b7ad8 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -46,7 +46,7 @@ private val attachables = EnumSet.of( fun isAttachable(type: Material) = attachables.contains(type) -fun supportingBlock(data: BlockData): Vec3i = when (data) { +fun getSupportingBlock(data: BlockData): Vec3i = when (data) { //is MultipleFacing -> // fuck it xD this is good enough is Directional -> Vec3i.convert(when (data.material) { diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt index 563d7a1..8f7f8f8 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt @@ -28,8 +28,8 @@ sealed class RegionTraverser { protected abstract suspend fun Scope.build(region: Region) companion object { - val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrder(Dimension.Y, Dimension.X)) - val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrder(Dimension.Y, Dimension.X)) + val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X)) + val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X)) val toClear get() = downward val toFill get() = upward @@ -42,8 +42,34 @@ sealed class RegionTraverser { val direction: TraverseDirection, val order: TraverseOrder ) : RegionTraverser() { + + private inline fun iterate(max: Int, increasing: Boolean, action: (Int) -> Unit) { + for (i in 0..max) { + action(if (increasing) i else max - i) + } + } + override suspend fun Scope.build(region: Region) { - //traverserLogic(region, order, direction) + val order = order + val (primary, secondary, tertiary) = order.toArray() + val (origin, size) = region + + val maxOfPrimary = primary.extract(size) - 1 + val maxOfSecondary = secondary.extract(size) - 1 + val maxOfTertiary = tertiary.extract(size) - 1 + + val isPrimaryIncreasing = direction.isIncreasing(primary) + val isSecondaryIncreasing = direction.isIncreasing(secondary) + val isTertiaryIncreasing = direction.isIncreasing(tertiary) + + iterate(maxOfPrimary, isPrimaryIncreasing) { p -> + iterate(maxOfSecondary, isSecondaryIncreasing) { s -> + iterate(maxOfTertiary, isTertiaryIncreasing) { t -> + yield(order.add(origin, p, s, t)) + } + } + } + } } @@ -77,6 +103,38 @@ sealed class RegionTraverser { } } + fun childForPosition(position: Vec3i): Directional { + var cur = this + while (true) { + when (cur) { + is Directional -> + return cur + is Slicing -> + cur = + if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser + else cur.topTraverser + } + } + } + + fun comesFirst(current: Vec3i, block: Vec3i): Boolean { + var cur = this + while (true) { + when (cur) { + is Directional -> return cur.direction.comesFirst(current, block) + is Slicing -> { + val border = cur.bottomSectionMaxY + cur = when { + current.y <= border && block.y <= border -> cur.bottomTraverser + current.y <= border -> return !cur.bottomFirst + block.y <= border -> return cur.bottomFirst + else -> cur.topTraverser + } + } + } + } + } + } enum class Dimension { @@ -97,8 +155,18 @@ enum class Dimension { } } +object TraverseOrderFactory { + private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3 + + fun createWith(primary: Dimension, secondary: Dimension): TraverseOrder { + // tertiary is implicit + if (primary == secondary) throw IllegalArgumentException() + return TraverseOrder(primary, isSwap(primary, secondary)) + } +} + inline class TraverseOrder(val orderNum: Int) { - private constructor(first: Dimension, swap: Boolean) + constructor(first: Dimension, swap: Boolean) : this(if (swap) first.ordinal + 3 else first.ordinal) @Suppress("NOTHING_TO_INLINE") @@ -126,18 +194,8 @@ inline class TraverseOrder(val orderNum: Int) { */ fun toArray() = arrayOf(primary, secondary, tertiary) - companion object { - private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3 - - operator fun invoke(primary: Dimension, secondary: Dimension): TraverseOrder { - // tertiary is implicit - if (primary == secondary) throw IllegalArgumentException() - return TraverseOrder(primary, isSwap(primary, secondary)) - } - } - fun add(vec: Vec3i, dp: Int, ds: Int, dt: Int): Vec3i = - // optimize this, will be called lots + // optimize this, will be called lots when (orderNum) { 0 -> vec.add(dp, ds, dt) // xyz 1 -> vec.add(dt, dp, ds) // yzx @@ -149,48 +207,19 @@ inline class TraverseOrder(val orderNum: Int) { } } -class AltTraverser(val size: Vec3i, - val order: TraverseOrder, - val direction: TraverseDirection) { - - - suspend fun Scope.build() { - doPrimary() - } - - private suspend fun Scope.doPrimary() { - val dimension = order.primary - direction.directionOf(dimension).traverse(dimension.extract(size)) { value -> - - } - } - - private fun Dimension.setValue(value: Int) { - - } - -} - -enum class Increment(val offset: Int) { - UP(1), - DOWN(-1); - - companion object { - fun convert(bool: Boolean) = if (bool) UP else DOWN - } - - inline fun traverse(size: Int, op: (Int) -> Unit) { - when (this) { - UP -> repeat(size, op) - DOWN -> repeat(size) { op(size - it - 1) } - } - } - -} - inline class TraverseDirection(val bits: Int) { - - fun directionOf(dimension: Dimension) = Increment.convert((1 shl dimension.ordinal) and bits != 0) + fun isIncreasing(dimension: Dimension) = (1 shl dimension.ordinal) and bits != 0 + + fun comesFirst(current: Vec3i, block: Vec3i, dimension: Dimension): Boolean = + if (isIncreasing(dimension)) + dimension.extract(block) <= dimension.extract(current) + else + dimension.extract(block) >= dimension.extract(current) + + fun comesFirst(current: Vec3i, block: Vec3i) = + comesFirst(current, block, Dimension.X) + && comesFirst(current, block, Dimension.Y) + && comesFirst(current, block, Dimension.Z) companion object { operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z)) diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index 8d2084b..9d9a2aa 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -6,6 +6,7 @@ import io.dico.parcels2.util.get import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.World +import org.bukkit.block.Block import org.bukkit.block.data.BlockData private val air = Bukkit.createBlockData(Material.AIR) @@ -20,19 +21,21 @@ class Schematic { } private var blockDatas: Array? = null - //private var extra: Map Unit>? = null + private val extra = mutableMapOf Unit>() private var isLoaded = false; private set private val traverser: RegionTraverser = RegionTraverser.upward - fun getLoadTask(world: World, region: Region): TimeLimitedTask = { + suspend fun WorkerScope.load(world: World, region: Region) { _size = region.size val data = arrayOfNulls(region.blockCount).also { blockDatas = it } - //val extra = mutableMapOf Unit>().also { extra = it } val blocks = traverser.traverseRegion(region) + val total = region.blockCount.toDouble() for ((index, vec) in blocks.withIndex()) { markSuspensionPoint() + setProgress(index / total) + val block = world[vec] if (block.y > 255) continue val blockData = block.blockData @@ -42,30 +45,56 @@ class Schematic { isLoaded = true } - fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = { + suspend fun WorkerScope.paste(world: World, position: Vec3i) { if (!isLoaded) throw IllegalStateException() val region = Region(position, _size!!) val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight) val blockDatas = blockDatas!! + var postponed = hashMapOf() + + // 90% of the progress of this job is allocated to this code block + delegateWork(0.9) { + for ((index, vec) in blocks.withIndex()) { + markSuspensionPoint() + val block = world[vec] + val type = blockDatas[index] ?: air + if (type !== air && isAttachable(type.material)) { + val supportingBlock = vec + getSupportingBlock(type) + + if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) { + block.blockData = type + } else { + postponed[vec] = type + } + + } else { + block.blockData = type + } + } + } - val postponed = mutableListOf>() - - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - val block = world[vec] - val type = blockDatas[index] ?: air - if (type !== air && isAttachable(type.material)) { - - - postponed += vec to type - } else { - block.blockData = type + delegateWork { + while (!postponed.isEmpty()) { + val newMap = hashMapOf() + for ((vec, type) in postponed) { + val supportingBlock = vec + getSupportingBlock(type) + if (supportingBlock in postponed && supportingBlock != vec) { + newMap[vec] = type + } else { + world[vec].blockData = type + } + } + postponed = newMap } } + } - for ((vec, data) in postponed) { + fun getLoadTask(world: World, region: Region): TimeLimitedTask = { + load(world, region) + } - } + fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = { + paste(world, position) } } diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index 4cfd2a3..553362e 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -2,6 +2,7 @@ package io.dico.parcels2.blockvisitor import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.logger +import io.dico.parcels2.util.ext.clampMin import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart.LAZY @@ -14,7 +15,6 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine typealias TimeLimitedTask = suspend WorkerScope.() -> Unit typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit @@ -48,9 +48,9 @@ interface Timed { interface Worker : Timed { /** - * The coroutine associated with this worker, if any + * The coroutine associated with this worker */ - val job: Job? + val job: Job /** * true if this worker has completed @@ -65,9 +65,9 @@ interface Worker : Timed { /** * A value indicating the progress of this worker, in the range 0.0 <= progress <= 1.0 - * with no guarantees to its accuracy. May be null. + * with no guarantees to its accuracy. */ - val progress: Double? + val progress: Double /** * Calls the given [block] whenever the progress of this worker is updated, @@ -89,6 +89,11 @@ interface Worker : Timed { * Await completion of this worker */ suspend fun awaitCompletion() + + /** + * An object attached to this worker + */ + //val attachment: Any? } interface WorkerScope : Timed { @@ -97,10 +102,35 @@ interface WorkerScope : Timed { */ suspend fun markSuspensionPoint() + /** + * A value indicating the progress of this worker, in the range 0.0 <= progress <= 1.0 + * with no guarantees to its accuracy. + */ + val progress: Double + /** * 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 [WorkerScope] that is responsible for [portion] part of the progress + * If [portion] is negative, the remainder of the progress is used + */ + fun delegateWork(portion: Double = -1.0): WorkerScope +} + +inline fun WorkerScope.delegateWork(portion: Double = -1.0, block: WorkerScope.() -> T): T { + delegateWork(portion).apply { + val result = block() + markComplete() + return result + } } interface WorkerInternal : Worker, WorkerScope { @@ -179,37 +209,32 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo } -private class WorkerImpl( - val scope: CoroutineScope, - val task: TimeLimitedTask -) : WorkerInternal, CoroutineScope by scope { - override var job: Job? = null; private set +private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerInternal { + override val job: Job = 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() = job?.let { - if (it.isCompleted) startTimeOrElapsedTime + get() = + if (job.isCompleted) startTimeOrElapsedTime else currentTimeMillis() - startTimeOrElapsedTime - } ?: 0L - override val isComplete get() = job?.isCompleted == true + override val isComplete get() = job.isCompleted + private var _progress = 0.0 + override val progress get() = _progress override var completionException: Throwable? = null; private set - override var progress: Double? = null; private set - private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise private var onProgressUpdate: WorkerUpdateLister? = null private var progressUpdateInterval: Int = 0 private var lastUpdateTime: Long = 0L private var onCompleted: WorkerUpdateLister? = null - private var continuation: Continuation? = null - private var nextSuspensionTime: Long = 0L - private var completeForcefully = false - private fun initJob(job: Job) { - this.job?.let { throw IllegalStateException() } - this.job = job - startTimeOrElapsedTime = System.currentTimeMillis() + init { job.invokeOnCompletion { exception -> // report any error that occurred completionException = exception?.also { @@ -264,7 +289,7 @@ private class WorkerImpl( } override fun setProgress(progress: Double) { - this.progress = progress + this._progress = progress val onProgressUpdate = onProgressUpdate ?: return val time = System.currentTimeMillis() if (time > lastUpdateTime + progressUpdateInterval) { @@ -274,46 +299,54 @@ private class WorkerImpl( } override fun resume(worktime: Long): Boolean { + if (isComplete) return true + if (worktime > 0) { nextSuspensionTime = currentTimeMillis() + worktime } else { completeForcefully = true } - continuation?.let { - continuation = null - it.resume(Unit) - return continuation == null - } - - job?.let { - nextSuspensionTime = 0L - throw IllegalStateException() + if (isStarted) { + continuation?.let { + continuation = null + it.resume(Unit) + return continuation == null + } + return true } - try { - val job = launch(start = LAZY) { task() } - initJob(job = job) - job.start() - } catch (t: Throwable) { - // do nothing: handled by job.invokeOnCompletion() - } + startTimeOrElapsedTime = System.currentTimeMillis() + job.start() return continuation == null } override suspend fun awaitCompletion() { - if (isComplete) return + job.join() + } - // easy path - if the job was initialized already - job?.apply { join(); return } + private fun delegateWork(curPortion: Double, portion: Double): WorkerScope = + DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) - // other way. - return suspendCoroutine { cont -> - onCompleted { prog, el -> cont.resume(Unit) } - } - } + override fun delegateWork(portion: Double): WorkerScope = delegateWork(1.0, portion) + + private inner class DelegateScope(val progressStart: Double, val portion: Double) : WorkerScope { + override val elapsedTime: Long + get() = this@WorkerImpl.elapsedTime + override suspend fun markSuspensionPoint() = + this@WorkerImpl.markSuspensionPoint() + + override val progress: Double + get() = (this@WorkerImpl.progress - progressStart) / portion + + override fun setProgress(progress: Double) = + this@WorkerImpl.setProgress(progressStart + progress * portion) + + override fun delegateWork(portion: Double): WorkerScope = + this@WorkerImpl.delegateWork(this.portion, portion) + } } /* diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index faafca9..6d62729 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -4,7 +4,7 @@ import io.dico.dicore.command.* import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.PrivilegeChangeResult +import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.util.ext.hasPermAdminManage import io.dico.parcels2.util.ext.parcelLimit import org.bukkit.entity.Player @@ -43,14 +43,17 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei 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) - .onProgressUpdate(1000, 1000) { progress, elapsedTime -> - val alt = context.getFormat(EMessageType.NUMBER) - val main = context.getFormat(EMessageType.INFORMATIVE) - context.sendMessage( - EMessageType.INFORMATIVE, false, "$action progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed" - .format(progress * 100, elapsedTime / 1000.0) - ) - } + } + + protected fun Worker.reportProgressUpdates(context: ExecutionContext, action: String) { + onProgressUpdate(1000, 1000) { progress, elapsedTime -> + val alt = context.getFormat(EMessageType.NUMBER) + val main = context.getFormat(EMessageType.INFORMATIVE) + context.sendMessage( + EMessageType.INFORMATIVE, false, "$action progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed" + .format(progress * 100, elapsedTime / 1000.0) + ) + } } protected fun err(message: String): Nothing = throw CommandException(message) diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt index 0b155f2..9975535 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt @@ -1,11 +1,15 @@ package io.dico.parcels2.command +import io.dico.dicore.command.CommandException 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.command.ParcelTarget.Companion.ID +import io.dico.parcels2.command.ParcelTarget.Kind class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @@ -28,18 +32,34 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("reset") @RequireParcelPrivilege(Privilege.ADMIN) fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? { + Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") if (!sure) return areYouSureMessage(context) + parcel.dispose() - clearWithProgressUpdates(context, "Reset") - return null + 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, @Flag sure: Boolean): Any? { + fun ParcelScope.cmdSwap(context: ExecutionContext, + @Kind(ID) target: ParcelTarget, + @Flag sure: Boolean): Any? { + Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") if (!sure) return areYouSureMessage(context) - TODO("implement swap") - } + val parcel2 = (target as ParcelTarget.ByID).getParcel() + ?: 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") + + val data = parcel.data + parcel.copyData(parcel2.data) + parcel2.copyData(data) + + world.blockManager.swapParcels(parcel.id, parcel2.id).reportProgressUpdates(context, "Swap") + return null + } } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 916fccd..969d964 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -3,10 +3,12 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandException import io.dico.dicore.command.EMessageType import io.dico.dicore.command.ExecutionContext +import io.dico.dicore.command.Validate import io.dico.dicore.command.annotation.Cmd import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.Privilege import io.dico.parcels2.blockvisitor.RegionTraverser +import io.dico.parcels2.blockvisitor.TickWorktimeLimiter import io.dico.parcels2.doBlockOperation import org.bukkit.Bukkit import org.bukkit.Material @@ -35,6 +37,8 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("make_mess") @RequireParcelPrivilege(Privilege.OWNER) fun ParcelScope.cmdMakeMess(context: ExecutionContext) { + Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") + val server = plugin.server val blockDatas = arrayOf( server.createBlockData(Material.BLUE_WOOL), @@ -72,4 +76,18 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { blockData.javaClass.interfaces!!.contentToString() } + @Cmd("visitors") + fun cmdVisitors(): Any? { + val workers = plugin.worktimeLimiter.workers + println(workers.map { it.job }.joinToString(separator = "\n")) + return "Task count: ${workers.size}" + } + + @Cmd("force_visitors") + fun cmdForceVisitors(): Any? { + val workers = plugin.worktimeLimiter.workers + plugin.worktimeLimiter.completeAllTasks() + return "Task count: ${workers.size}" + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 3cb0621..750fd96 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -120,9 +120,9 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("clear") @RequireParcelPrivilege(Privilege.OWNER) 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) - - clearWithProgressUpdates(context, "Clear") + world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Clear") return null } @@ -130,8 +130,8 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @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) - return "Biome has been set to $biome" + world.blockManager.setBiome(parcel.id, biome).reportProgressUpdates(context, "Biome change") + return null } } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt index ea422a5..5202683 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt @@ -12,6 +12,13 @@ import org.bukkit.entity.Player class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { + private fun ParcelScope.checkPrivilege(sender: Player, player: OfflinePlayer) { + if (!sender.hasPermAdminManage) { + Validate.isTrue(parcel.owner != null, "This parcel is unowned") + Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}") + } + } + @Cmd("entrust") @Desc( "Allows a player to manage this parcel", @@ -52,8 +59,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl ) @RequireParcelPrivilege(Privilege.CAN_MANAGE) fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") - Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}") + checkPrivilege(sender, player) return when (parcel.allowBuild(player)) { FAIL_OWNER -> err("The target already owns the parcel") @@ -70,8 +76,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl ) @RequireParcelPrivilege(Privilege.CAN_MANAGE) fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") - Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}") + checkPrivilege(sender, player) return when (parcel.disallowBuild(player)) { FAIL_OWNER -> err("The target owns the parcel") @@ -88,8 +93,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl ) @RequireParcelPrivilege(Privilege.CAN_MANAGE) fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") - Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}") + checkPrivilege(sender, player) return when (parcel.disallowBuild(player)) { FAIL_OWNER -> err("The target owns the parcel") @@ -106,8 +110,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl ) @RequireParcelPrivilege(Privilege.CAN_MANAGE) fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") - Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}") + checkPrivilege(sender, player) return when (parcel.disallowBuild(player)) { FAIL_OWNER -> err("The target owns the parcel") diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt index 8c8f303..dddb221 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -1,10 +1,7 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.RegionTraverser -import io.dico.parcels2.blockvisitor.TimeLimitedTask -import io.dico.parcels2.blockvisitor.Worker -import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.blockvisitor.* import io.dico.parcels2.options.DefaultGeneratorOptions import io.dico.parcels2.util.Region import io.dico.parcels2.util.Vec2i @@ -225,15 +222,18 @@ class DefaultParcelGenerator( return world.getParcelById(parcelId) } - override fun submitBlockVisitor(parcelId: ParcelId, task: TimeLimitedTask): Worker { - val parcel = getParcel(parcelId) ?: return worktimeLimiter.submit(task) - if (parcel.hasBlockVisitors) throw IllegalArgumentException("This parcel already has a block visitor") + override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker { + val parcels = parcelIds.mapNotNull { getParcel(it) } + if (parcels.isEmpty()) return worktimeLimiter.submit(task) + if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor") val worker = worktimeLimiter.submit(task) - launch(start = UNDISPATCHED) { - parcel.withBlockVisitorPermit { - worker.awaitCompletion() + for (parcel in parcels) { + launch(start = UNDISPATCHED) { + parcel.withBlockVisitorPermit { + worker.awaitCompletion() + } } } @@ -276,6 +276,13 @@ class DefaultParcelGenerator( } } + override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker = submitBlockVisitor(parcel1, parcel2) { + val schematicOf1 = delegateWork(0.15) { Schematic().apply { load(world, getRegion(parcel1)) } } + val schematicOf2 = delegateWork(0.15) { Schematic().apply { load(world, getRegion(parcel2)) } } + delegateWork(0.35) { with(schematicOf1) { paste(world, getRegion(parcel2).origin) } } + delegateWork(0.35) { with(schematicOf2) { paste(world, getRegion(parcel1).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/util/ext/Math.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt index f3b6d3b..a123bcf 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt @@ -33,6 +33,7 @@ fun IntRange.clamp(min: Int, max: Int): IntRange { // the name coerceAtMost is bad fun Int.clampMax(max: Int) = coerceAtMost(max) +fun Double.clampMin(min: Double) = coerceAtLeast(min) // Why does this not exist? infix fun Int.ceilDiv(divisor: Int): Int { -- cgit v1.2.3