Archived
0

Work on schematic and parcel swaps

This commit is contained in:
Dico
2018-09-26 02:42:15 +01:00
parent dcd90c09ad
commit 2225bdae95
15 changed files with 309 additions and 164 deletions

View File

@@ -67,7 +67,7 @@ dependencies {
c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13")) 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 // not on sk89q maven repo yet
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar")) compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))

View File

@@ -59,7 +59,9 @@ interface ParcelBlockManager {
fun clearParcel(parcel: ParcelId): Worker 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 * Used to update owner blocks in the corner of the parcel

View File

@@ -226,19 +226,19 @@ class PlayerProfile2 private constructor(uuid: UUID?,
?: PlayerProfile(null, input, !allowFake) ?: PlayerProfile(null, input, !allowFake)
} }
operator fun invoke(name: String): PlayerProfile { operator fun createWith(name: String): PlayerProfile {
if (name == star.name) return star if (name == star.name) return star
return PlayerProfile(null, name) return PlayerProfile(null, name)
} }
operator fun invoke(uuid: UUID): PlayerProfile { operator fun createWith(uuid: UUID): PlayerProfile {
if (uuid == star.uuid) return star if (uuid == star.uuid) return star
return PlayerProfile(uuid, null) return PlayerProfile(uuid, null)
} }
operator fun invoke(player: OfflinePlayer): PlayerProfile { operator fun createWith(player: OfflinePlayer): PlayerProfile {
// avoid UUID comparison against STAR // 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)
} }
} }

View File

@@ -88,7 +88,7 @@ interface Privileges : PrivilegesMinimal {
} }
fun changePrivilege(key: PrivilegeKey, positive: Boolean, update: Privilege): PrivilegeChangeResult = 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) else if (getStoredPrivilege(key).isChangeInDirection(positive, update)
&& setStoredPrivilege(key, update) && setStoredPrivilege(key, update)
) SUCCESS ) SUCCESS

View File

@@ -46,7 +46,7 @@ private val attachables = EnumSet.of(
fun isAttachable(type: Material) = attachables.contains(type) 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 MultipleFacing -> // fuck it xD this is good enough
is Directional -> Vec3i.convert(when (data.material) { is Directional -> Vec3i.convert(when (data.material) {

View File

@@ -28,8 +28,8 @@ sealed class RegionTraverser {
protected abstract suspend fun Scope.build(region: Region) protected abstract suspend fun Scope.build(region: Region)
companion object { companion object {
val upward = 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), TraverseOrder(Dimension.Y, Dimension.X)) val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
val toClear get() = downward val toClear get() = downward
val toFill get() = upward val toFill get() = upward
@@ -42,8 +42,34 @@ sealed class RegionTraverser {
val direction: TraverseDirection, val direction: TraverseDirection,
val order: TraverseOrder val order: TraverseOrder
) : RegionTraverser() { ) : 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) { 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 { 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) { 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) : this(if (swap) first.ordinal + 3 else first.ordinal)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@@ -126,18 +194,8 @@ inline class TraverseOrder(val orderNum: Int) {
*/ */
fun toArray() = arrayOf(primary, secondary, tertiary) 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 = 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) { when (orderNum) {
0 -> vec.add(dp, ds, dt) // xyz 0 -> vec.add(dp, ds, dt) // xyz
1 -> vec.add(dt, dp, ds) // yzx 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) { inline class TraverseDirection(val bits: Int) {
fun isIncreasing(dimension: Dimension) = (1 shl dimension.ordinal) and bits != 0
fun directionOf(dimension: Dimension) = Increment.convert((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 { companion object {
operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z)) operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z))

View File

@@ -6,6 +6,7 @@ import io.dico.parcels2.util.get
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Block
import org.bukkit.block.data.BlockData import org.bukkit.block.data.BlockData
private val air = Bukkit.createBlockData(Material.AIR) private val air = Bukkit.createBlockData(Material.AIR)
@@ -20,19 +21,21 @@ class Schematic {
} }
private var blockDatas: Array<BlockData?>? = null 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 var isLoaded = false; private set
private val traverser: RegionTraverser = RegionTraverser.upward private val traverser: RegionTraverser = RegionTraverser.upward
fun getLoadTask(world: World, region: Region): TimeLimitedTask = { suspend fun WorkerScope.load(world: World, region: Region) {
_size = region.size _size = region.size
val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it } val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it }
//val extra = mutableMapOf<Vec3i, (Block) -> Unit>().also { extra = it }
val blocks = traverser.traverseRegion(region) val blocks = traverser.traverseRegion(region)
val total = region.blockCount.toDouble()
for ((index, vec) in blocks.withIndex()) { for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint() markSuspensionPoint()
setProgress(index / total)
val block = world[vec] val block = world[vec]
if (block.y > 255) continue if (block.y > 255) continue
val blockData = block.blockData val blockData = block.blockData
@@ -42,30 +45,56 @@ class Schematic {
isLoaded = true isLoaded = true
} }
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = { suspend fun WorkerScope.paste(world: World, position: Vec3i) {
if (!isLoaded) throw IllegalStateException() if (!isLoaded) throw IllegalStateException()
val region = Region(position, _size!!) val region = Region(position, _size!!)
val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight) val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
val blockDatas = blockDatas!! val blockDatas = blockDatas!!
var postponed = hashMapOf<Vec3i, BlockData>()
val postponed = mutableListOf<Pair<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)
for ((index, vec) in blocks.withIndex()) { if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
markSuspensionPoint() block.blockData = type
val block = world[vec] } else {
val type = blockDatas[index] ?: air postponed[vec] = type
if (type !== air && isAttachable(type.material)) { }
} else {
postponed += vec to type block.blockData = type
} else { }
block.blockData = type
} }
} }
for ((vec, data) in postponed) { 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
}
} }
} }
fun getLoadTask(world: World, region: Region): TimeLimitedTask = {
load(world, region)
}
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
paste(world, position)
}
} }

View File

@@ -2,6 +2,7 @@ package io.dico.parcels2.blockvisitor
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.logger import io.dico.parcels2.logger
import io.dico.parcels2.util.ext.clampMin
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart.LAZY import kotlinx.coroutines.CoroutineStart.LAZY
@@ -14,7 +15,6 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
typealias TimeLimitedTask = suspend WorkerScope.() -> Unit typealias TimeLimitedTask = suspend WorkerScope.() -> Unit
typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit
@@ -48,9 +48,9 @@ interface Timed {
interface Worker : 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 * 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 * 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, * Calls the given [block] whenever the progress of this worker is updated,
@@ -89,6 +89,11 @@ interface Worker : Timed {
* Await completion of this worker * Await completion of this worker
*/ */
suspend fun awaitCompletion() suspend fun awaitCompletion()
/**
* An object attached to this worker
*/
//val attachment: Any?
} }
interface WorkerScope : Timed { interface WorkerScope : Timed {
@@ -97,10 +102,35 @@ interface WorkerScope : Timed {
*/ */
suspend fun markSuspensionPoint() 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 * A task should call this method to indicate its progress
*/ */
fun setProgress(progress: Double) 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 { interface WorkerInternal : Worker, WorkerScope {
@@ -179,37 +209,32 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo
} }
private class WorkerImpl( private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerInternal {
val scope: CoroutineScope, override val job: Job = scope.launch(start = LAZY) { task() }
val task: TimeLimitedTask
) : WorkerInternal, CoroutineScope by scope { private var continuation: Continuation<Unit>? = null
override var job: Job? = null; private set private var nextSuspensionTime: Long = 0L
private var completeForcefully = false
private var isStarted = false
override val elapsedTime override val elapsedTime
get() = job?.let { get() =
if (it.isCompleted) startTimeOrElapsedTime if (job.isCompleted) startTimeOrElapsedTime
else currentTimeMillis() - 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 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 startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise
private var onProgressUpdate: WorkerUpdateLister? = null private var onProgressUpdate: WorkerUpdateLister? = null
private var progressUpdateInterval: Int = 0 private var progressUpdateInterval: Int = 0
private var lastUpdateTime: Long = 0L private var lastUpdateTime: Long = 0L
private var onCompleted: WorkerUpdateLister? = null 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) { init {
this.job?.let { throw IllegalStateException() }
this.job = job
startTimeOrElapsedTime = System.currentTimeMillis()
job.invokeOnCompletion { exception -> job.invokeOnCompletion { exception ->
// report any error that occurred // report any error that occurred
completionException = exception?.also { completionException = exception?.also {
@@ -264,7 +289,7 @@ private class WorkerImpl(
} }
override fun setProgress(progress: Double) { override fun setProgress(progress: Double) {
this.progress = progress this._progress = progress
val onProgressUpdate = onProgressUpdate ?: return val onProgressUpdate = onProgressUpdate ?: return
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
if (time > lastUpdateTime + progressUpdateInterval) { if (time > lastUpdateTime + progressUpdateInterval) {
@@ -274,46 +299,54 @@ private class WorkerImpl(
} }
override fun resume(worktime: Long): Boolean { override fun resume(worktime: Long): Boolean {
if (isComplete) return true
if (worktime > 0) { if (worktime > 0) {
nextSuspensionTime = currentTimeMillis() + worktime nextSuspensionTime = currentTimeMillis() + worktime
} else { } else {
completeForcefully = true completeForcefully = true
} }
continuation?.let { if (isStarted) {
continuation = null continuation?.let {
it.resume(Unit) continuation = null
return continuation == null it.resume(Unit)
return continuation == null
}
return true
} }
job?.let { startTimeOrElapsedTime = System.currentTimeMillis()
nextSuspensionTime = 0L job.start()
throw IllegalStateException()
}
try {
val job = launch(start = LAZY) { task() }
initJob(job = job)
job.start()
} catch (t: Throwable) {
// do nothing: handled by job.invokeOnCompletion()
}
return continuation == null return continuation == null
} }
override suspend fun awaitCompletion() { override suspend fun awaitCompletion() {
if (isComplete) return job.join()
// easy path - if the job was initialized already
job?.apply { join(); return }
// other way.
return suspendCoroutine { cont ->
onCompleted { prog, el -> cont.resume(Unit) }
}
} }
private fun delegateWork(curPortion: Double, portion: Double): WorkerScope =
DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
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)
}
} }
/* /*

View File

@@ -4,7 +4,7 @@ import io.dico.dicore.command.*
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile 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.hasPermAdminManage
import io.dico.parcels2.util.ext.parcelLimit import io.dico.parcels2.util.ext.parcelLimit
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -43,14 +43,17 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
protected fun ParcelScope.clearWithProgressUpdates(context: ExecutionContext, action: String) { protected fun ParcelScope.clearWithProgressUpdates(context: ExecutionContext, action: String) {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
world.blockManager.clearParcel(parcel.id) world.blockManager.clearParcel(parcel.id)
.onProgressUpdate(1000, 1000) { progress, elapsedTime -> }
val alt = context.getFormat(EMessageType.NUMBER)
val main = context.getFormat(EMessageType.INFORMATIVE) protected fun Worker.reportProgressUpdates(context: ExecutionContext, action: String) {
context.sendMessage( onProgressUpdate(1000, 1000) { progress, elapsedTime ->
EMessageType.INFORMATIVE, false, "$action progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed" val alt = context.getFormat(EMessageType.NUMBER)
.format(progress * 100, elapsedTime / 1000.0) 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) protected fun err(message: String): Nothing = throw CommandException(message)

View File

@@ -1,11 +1,15 @@
package io.dico.parcels2.command package io.dico.parcels2.command
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.ExecutionContext 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.Cmd
import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.Flag
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.Privilege 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) { class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@@ -28,18 +32,34 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("reset") @Cmd("reset")
@RequireParcelPrivilege(Privilege.ADMIN) @RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? { 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) if (!sure) return areYouSureMessage(context)
parcel.dispose() parcel.dispose()
clearWithProgressUpdates(context, "Reset") world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Reset")
return null return "Data of (${parcel.id.idString}) has been disposed"
} }
@Cmd("swap") @Cmd("swap")
@RequireParcelPrivilege(Privilege.ADMIN) @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) 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
} }
} }

View File

@@ -3,10 +3,12 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException import io.dico.dicore.command.CommandException
import io.dico.dicore.command.EMessageType import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext 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.Cmd
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.Privilege import io.dico.parcels2.Privilege
import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.RegionTraverser
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
import io.dico.parcels2.doBlockOperation import io.dico.parcels2.doBlockOperation
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.Material import org.bukkit.Material
@@ -35,6 +37,8 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("make_mess") @Cmd("make_mess")
@RequireParcelPrivilege(Privilege.OWNER) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdMakeMess(context: ExecutionContext) { fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
val server = plugin.server val server = plugin.server
val blockDatas = arrayOf( val blockDatas = arrayOf(
server.createBlockData(Material.BLUE_WOOL), server.createBlockData(Material.BLUE_WOOL),
@@ -72,4 +76,18 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
blockData.javaClass.interfaces!!.contentToString() 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}"
}
} }

View File

@@ -120,9 +120,9 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("clear") @Cmd("clear")
@RequireParcelPrivilege(Privilege.OWNER) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { 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) if (!sure) return areYouSureMessage(context)
world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Clear")
clearWithProgressUpdates(context, "Clear")
return null return null
} }
@@ -130,8 +130,8 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@RequireParcelPrivilege(Privilege.OWNER) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
world.blockManager.setBiome(parcel.id, biome) world.blockManager.setBiome(parcel.id, biome).reportProgressUpdates(context, "Biome change")
return "Biome has been set to $biome" return null
} }
} }

View File

@@ -12,6 +12,13 @@ import org.bukkit.entity.Player
class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { 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") @Cmd("entrust")
@Desc( @Desc(
"Allows a player to manage this parcel", "Allows a player to manage this parcel",
@@ -52,8 +59,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl
) )
@RequireParcelPrivilege(Privilege.CAN_MANAGE) @RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? { fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") checkPrivilege(sender, player)
Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}")
return when (parcel.allowBuild(player)) { return when (parcel.allowBuild(player)) {
FAIL_OWNER -> err("The target already owns the parcel") FAIL_OWNER -> err("The target already owns the parcel")
@@ -70,8 +76,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl
) )
@RequireParcelPrivilege(Privilege.CAN_MANAGE) @RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? { fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") checkPrivilege(sender, player)
Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}")
return when (parcel.disallowBuild(player)) { return when (parcel.disallowBuild(player)) {
FAIL_OWNER -> err("The target owns the parcel") FAIL_OWNER -> err("The target owns the parcel")
@@ -88,8 +93,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl
) )
@RequireParcelPrivilege(Privilege.CAN_MANAGE) @RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? { fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") checkPrivilege(sender, player)
Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}")
return when (parcel.disallowBuild(player)) { return when (parcel.disallowBuild(player)) {
FAIL_OWNER -> err("The target owns the parcel") FAIL_OWNER -> err("The target owns the parcel")
@@ -106,8 +110,7 @@ class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(pl
) )
@RequireParcelPrivilege(Privilege.CAN_MANAGE) @RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? { fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") checkPrivilege(sender, player)
Validate.isTrue(parcel.privilege(sender) > parcel.privilege(player), "You may not change the privilege of ${player.name}")
return when (parcel.disallowBuild(player)) { return when (parcel.disallowBuild(player)) {
FAIL_OWNER -> err("The target owns the parcel") FAIL_OWNER -> err("The target owns the parcel")

View File

@@ -1,10 +1,7 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.*
import io.dico.parcels2.blockvisitor.TimeLimitedTask
import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.DefaultGeneratorOptions import io.dico.parcels2.options.DefaultGeneratorOptions
import io.dico.parcels2.util.Region import io.dico.parcels2.util.Region
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
@@ -225,15 +222,18 @@ class DefaultParcelGenerator(
return world.getParcelById(parcelId) return world.getParcelById(parcelId)
} }
override fun submitBlockVisitor(parcelId: ParcelId, task: TimeLimitedTask): Worker { override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker {
val parcel = getParcel(parcelId) ?: return worktimeLimiter.submit(task) val parcels = parcelIds.mapNotNull { getParcel(it) }
if (parcel.hasBlockVisitors) throw IllegalArgumentException("This parcel already has a block visitor") 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) val worker = worktimeLimiter.submit(task)
launch(start = UNDISPATCHED) { for (parcel in parcels) {
parcel.withBlockVisitorPermit { launch(start = UNDISPATCHED) {
worker.awaitCompletion() 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> { override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
/* /*
* Get the offsets for the world out of the way * Get the offsets for the world out of the way

View File

@@ -33,6 +33,7 @@ fun IntRange.clamp(min: Int, max: Int): IntRange {
// the name coerceAtMost is bad // the name coerceAtMost is bad
fun Int.clampMax(max: Int) = coerceAtMost(max) fun Int.clampMax(max: Int) = coerceAtMost(max)
fun Double.clampMin(min: Double) = coerceAtLeast(min)
// Why does this not exist? // Why does this not exist?
infix fun Int.ceilDiv(divisor: Int): Int { infix fun Int.ceilDiv(divisor: Int): Int {