summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico <dico.karssiens@gmail.com>2018-09-26 02:42:15 +0100
committerDico <dico.karssiens@gmail.com>2018-09-26 02:42:15 +0100
commit2225bdae95b3de4985347edf30ae31a28f73f35b (patch)
tree3145fae97135d1864ea29c608be2917d74e2dadc
parentdcd90c09add292300b163edf44c26ddf99f1199b (diff)
Work on schematic and parcel swaps
-rw-r--r--build.gradle.kts2
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/PlayerProfile.kt8
-rw-r--r--src/main/kotlin/io/dico/parcels2/Privilege.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt141
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt65
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt129
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt21
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt30
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt18
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt8
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt19
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt27
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/ext/Math.kt1
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<BlockData?>? = null
- //private var extra: Map<Vec3i, (Block) -> Unit>? = null
+ private val extra = mutableMapOf<Vec3i, (Block) -> 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<BlockData>(region.blockCount).also { blockDatas = it }
- //val extra = mutableMapOf<Vec3i, (Block) -> 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<Vec3i, BlockData>()
+
+ // 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<Pair<Vec3i, BlockData>>()
-
- 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<Vec3i, BlockData>()
+ 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 {
@@ -98,9 +103,34 @@ 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 <T> 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<Unit>? = 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<Unit>? = 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<Vec2i> {
/*
* 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 {