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("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"))

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 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 {
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.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>()
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()) {
markSuspensionPoint()
val block = world[vec]
val type = blockDatas[index] ?: air
if (type !== air && isAttachable(type.material)) {
if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
block.blockData = type
} else {
postponed[vec] = type
}
postponed += vec to type
} else {
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.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 <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
if (isStarted) {
continuation?.let {
continuation = null
it.resume(Unit)
return continuation == null
}
return true
}
job?.let {
nextSuspensionTime = 0L
throw IllegalStateException()
}
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
// easy path - if the job was initialized already
job?.apply { join(); return }
// other way.
return suspendCoroutine { cont ->
onCompleted { prog, el -> cont.resume(Unit) }
}
job.join()
}
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.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)

View File

@@ -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
}
}

View File

@@ -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}"
}
}

View File

@@ -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
}
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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 {