Archived
0

Make progress

This commit is contained in:
Dico
2018-09-26 07:08:42 +01:00
parent 2225bdae95
commit 520ae530d2
25 changed files with 434 additions and 281 deletions

View File

@@ -37,12 +37,12 @@ abstract class ParcelGenerator : ChunkGenerator() {
abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
container: ParcelContainer,
coroutineScope: CoroutineScope,
worktimeLimiter: WorktimeLimiter): Pair<ParcelLocator, ParcelBlockManager>
workDispatcher: WorkDispatcher): Pair<ParcelLocator, ParcelBlockManager>
}
interface ParcelBlockManager {
val world: World
val worktimeLimiter: WorktimeLimiter
val workDispatcher: WorkDispatcher
val parcelTraverser: RegionTraverser
// fun getBottomBlock(parcel: ParcelId): Vec2i
@@ -61,7 +61,7 @@ interface ParcelBlockManager {
fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker
fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker
fun submitBlockVisitor(vararg parcelIds: ParcelId, task: WorkerTask): Worker
/**
* Used to update owner blocks in the corner of the parcel

View File

@@ -3,8 +3,8 @@ package io.dico.parcels2
import io.dico.dicore.Registrator
import io.dico.dicore.command.EOverridePolicy
import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.blockvisitor.BukkitWorkDispatcher
import io.dico.parcels2.blockvisitor.WorkDispatcher
import io.dico.parcels2.command.getParcelCommands
import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
@@ -44,7 +44,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
override val plugin: Plugin get() = this
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
val workDispatcher: WorkDispatcher by lazy { BukkitWorkDispatcher(this, options.tickWorktime) }
override fun onEnable() {
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
@@ -55,11 +55,11 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
}
override fun onDisable() {
val hasWorkers = worktimeLimiter.workers.isNotEmpty()
val hasWorkers = workDispatcher.workers.isNotEmpty()
if (hasWorkers) {
plogger.warn("Parcels is attempting to complete all ${worktimeLimiter.workers.size} remaining jobs before shutdown...")
plogger.warn("Parcels is attempting to complete all ${workDispatcher.workers.size} remaining jobs before shutdown...")
}
worktimeLimiter.completeAllTasks()
workDispatcher.completeAllTasks()
if (hasWorkers) {
plogger.info("Parcels has completed the remaining jobs.")
}

View File

@@ -0,0 +1,38 @@
package io.dico.parcels2.blockvisitor
import org.bukkit.block.Block
import org.bukkit.block.BlockState
import org.bukkit.block.Sign
import kotlin.reflect.KClass
interface ExtraBlockChange {
fun update(block: Block)
}
abstract class BlockStateChange<T : BlockState> : ExtraBlockChange {
abstract val stateClass: KClass<T>
abstract fun update(state: T)
override fun update(block: Block) {
val state = block.state
if (stateClass.isInstance(state)) {
@Suppress("UNCHECKED_CAST")
update(state as T)
}
}
}
class SignStateChange(state: Sign) : BlockStateChange<Sign>() {
val lines = state.lines
override val stateClass: KClass<Sign>
get() = Sign::class
override fun update(state: Sign) {
for (i in lines.indices) {
val line = lines[i]
state.setLine(i, line)
}
}
}

View File

@@ -235,87 +235,3 @@ inline class TraverseDirection(val bits: Int) {
}
}
/*
private typealias Scope = SequenceScope<Vec3i>
private typealias ScopeAction = suspend Scope.(Int, Int, Int) -> Unit
@Suppress("NON_EXHAUSTIVE_WHEN")
suspend fun Scope.traverserLogic(
region: Region,
order: TraverseOrder,
direction: TraverseDirection
) = with(direction) {
val (primary, secondary, tertiary) = order.toArray()
val (origin, size) = region
when (order.primary) {
Dimension.X ->
when (order.secondary) {
Dimension.Y -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(p, s, t))
}
}
}
}
Dimension.Z -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(p, t, s))
}
}
}
}
}
Dimension.Y ->
when (order.secondary) {
Dimension.X -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(s, p, t))
}
}
}
}
Dimension.Z -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(t, p, s))
}
}
}
}
}
Dimension.Z ->
when (order.secondary) {
Dimension.X -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(s, t, p))
}
}
}
}
Dimension.Y -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(t, s, p))
}
}
}
}
}
}
}
*/

View File

@@ -6,12 +6,11 @@ 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.Sign
import org.bukkit.block.data.BlockData
private val air = Bukkit.createBlockData(Material.AIR)
// TODO order paste such that attachables are placed after the block they depend on
class Schematic {
val size: Vec3i get() = _size!!
private var _size: Vec3i? = null
@@ -21,7 +20,7 @@ class Schematic {
}
private var blockDatas: Array<BlockData?>? = null
private val extra = mutableMapOf<Vec3i, (Block) -> Unit>()
private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>()
private var isLoaded = false; private set
private val traverser: RegionTraverser = RegionTraverser.upward
@@ -32,7 +31,7 @@ class Schematic {
val blocks = traverser.traverseRegion(region)
val total = region.blockCount.toDouble()
for ((index, vec) in blocks.withIndex()) {
loop@ for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
setProgress(index / total)
@@ -40,6 +39,14 @@ class Schematic {
if (block.y > 255) continue
val blockData = block.blockData
data[index] = blockData
val extraChange = when (blockData.material) {
Material.SIGN,
Material.WALL_SIGN -> SignStateChange(block.state as Sign)
else -> continue@loop
}
extra += (vec - region.origin) to extraChange
}
isLoaded = true
@@ -47,53 +54,65 @@ class Schematic {
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)
val total = region.blockCount.toDouble()
var processed = 0
if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
block.blockData = type
} else {
postponed[vec] = type
}
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
setProgress(index / total)
} else {
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
setProgress(++processed / total)
} else {
postponed[vec] = type
}
} else {
block.blockData = type
setProgress(++processed / total)
}
}
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
}
while (!postponed.isEmpty()) {
markSuspensionPoint()
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
setProgress(++processed / total)
}
postponed = newMap
}
postponed = newMap
}
// Should be negligible so we don't track progress
for ((vec, extraChange) in extra) {
markSuspensionPoint()
val block = world[position + vec]
extraChange.update(block)
}
}
fun getLoadTask(world: World, region: Region): TimeLimitedTask = {
fun getLoadTask(world: World, region: Region): WorkerTask = {
load(world, region)
}
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
fun getPasteTask(world: World, position: Vec3i): WorkerTask = {
paste(world, position)
}

View File

@@ -16,17 +16,17 @@ import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.resume
typealias TimeLimitedTask = suspend WorkerScope.() -> Unit
typealias WorkerTask = suspend WorkerScope.() -> Unit
typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit
data class TickWorktimeOptions(var workTime: Int, var tickInterval: Int)
interface WorktimeLimiter {
interface WorkDispatcher {
/**
* Submit a [task] that should be run synchronously, but limited such that it does not stall the server
* a bunch
*/
fun submit(task: TimeLimitedTask): Worker
fun dispatch(task: WorkerTask): Worker
/**
* Get a list of all workers
@@ -39,14 +39,20 @@ interface WorktimeLimiter {
fun completeAllTasks()
}
interface Timed {
interface WorkerAndScopeMembersUnion {
/**
* The time that elapsed since this worker was dispatched, in milliseconds
*/
val elapsedTime: Long
/**
* 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
}
interface Worker : Timed {
interface Worker : WorkerAndScopeMembersUnion {
/**
* The coroutine associated with this worker
*/
@@ -63,12 +69,6 @@ interface Worker : Timed {
*/
val completionException: Throwable?
/**
* 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
/**
* Calls the given [block] whenever the progress of this worker is updated,
* if [minInterval] milliseconds expired since the last call.
@@ -96,18 +96,12 @@ interface Worker : Timed {
//val attachment: Any?
}
interface WorkerScope : Timed {
interface WorkerScope : WorkerAndScopeMembersUnion {
/**
* A task should call this frequently during its execution, such that the timer can suspend it when necessary.
*/
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
*/
@@ -152,14 +146,14 @@ interface WorkerInternal : Worker, WorkerScope {
* There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick
* This object attempts to split that maximum amount of milliseconds equally between all jobs
*/
class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter {
class BukkitWorkDispatcher(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorkDispatcher {
// The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null
// The workers.
private val _workers = LinkedList<WorkerInternal>()
override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker {
override fun dispatch(task: WorkerTask): Worker {
val worker: WorkerInternal = WorkerImpl(plugin, task)
if (bukkitTask == null) {
@@ -209,7 +203,7 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo
}
private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerInternal {
private class WorkerImpl(scope: CoroutineScope, task: WorkerTask) : WorkerInternal {
override val job: Job = scope.launch(start = LAZY) { task() }
private var continuation: Continuation<Unit>? = null
@@ -239,7 +233,7 @@ private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerI
// report any error that occurred
completionException = exception?.also {
if (it !is CancellationException)
logger.error("TimeLimitedTask generated an exception", it)
logger.error("WorkerTask generated an exception", it)
}
// convert to elapsed time here
@@ -316,6 +310,7 @@ private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerI
return true
}
isStarted = true
startTimeOrElapsedTime = System.currentTimeMillis()
job.start()
@@ -348,14 +343,3 @@ private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerI
this@WorkerImpl.delegateWork(this.portion, portion)
}
}
/*
/**
* While the implementation of [kotlin.coroutines.experimental.intrinsics.intercepted] is intrinsic, it should look something like this
* We don't care for intercepting the coroutine as we want it to resume immediately when we call resume().
* Thus, above, we use an unintercepted suspension. It's not necessary as the dispatcher (or interceptor) also calls it synchronously, but whatever.
*/
private fun <T> Continuation<T>.interceptedImpl(): Continuation<T> {
return context[ContinuationInterceptor]?.interceptContinuation(this) ?: this
}
*/

View File

@@ -8,7 +8,6 @@ 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
@@ -78,15 +77,15 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("visitors")
fun cmdVisitors(): Any? {
val workers = plugin.worktimeLimiter.workers
val workers = plugin.workDispatcher.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()
val workers = plugin.workDispatcher.workers
plugin.workDispatcher.completeAllTasks()
return "Task count: ${workers.size}"
}

View File

@@ -16,7 +16,7 @@ import io.dico.parcels2.util.ext.uuid
import org.bukkit.block.Biome
import org.bukkit.entity.Player
class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : AbstractParcelCommands(plugin) {
@Cmd("auto")
@Desc(
@@ -43,6 +43,10 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
)
fun ParcelScope.cmdInfo(player: Player) = parcel.infoString
init {
parent.addSpeciallyTreatedKeys("home", "h")
}
@Cmd("home", aliases = ["h"])
@Desc(
"Teleports you to your parcels,",

View File

@@ -1,8 +1,6 @@
package io.dico.parcels2.command
import io.dico.dicore.command.CommandBuilder
import io.dico.dicore.command.ICommandAddress
import io.dico.dicore.command.ICommandDispatcher
import io.dico.dicore.command.*
import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
import io.dico.parcels2.Interactables
import io.dico.parcels2.ParcelsPlugin
@@ -13,14 +11,16 @@ import java.util.Queue
@Suppress("UsePropertyAccessSyntax")
fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher =
with(CommandBuilder()) {
val parcelsAddress = SpecialCommandAddress()
setChatController(ParcelsChatController())
addParameterType(false, ParcelParameterType(plugin.parcelProvider))
addParameterType(false, ProfileParameterType())
addParameterType(true, ParcelTarget.PType(plugin.parcelProvider))
addParameterType(true, ParcelTarget.PType(plugin.parcelProvider, parcelsAddress))
group("parcel", "plot", "plots", "p") {
group(parcelsAddress, "parcel", "plot", "plots", "p") {
addRequiredPermission("parcels.command")
registerCommands(CommandsGeneral(plugin))
registerCommands(CommandsGeneral(plugin, parcelsAddress))
registerCommands(CommandsPrivilegesLocal(plugin))
group("option", "opt", "o") {
@@ -63,6 +63,12 @@ inline fun CommandBuilder.group(name: String, vararg aliases: String, config: Co
parent()
}
inline fun CommandBuilder.group(address: ICommandAddress, name: String, vararg aliases: String, config: CommandBuilder.() -> Unit) {
group(address, name, *aliases)
config()
parent()
}
private fun CommandBuilder.generateHelpAndSyntaxCommands(): CommandBuilder {
generateCommands(dispatcher as ICommandAddress, "help", "syntax")
return this
@@ -80,3 +86,37 @@ private fun generateCommands(address: ICommandAddress, vararg names: String) {
}
}
}
class SpecialCommandAddress : ChildCommandAddress() {
private val speciallyTreatedKeys = mutableListOf<String>()
// Used to allow /p h:1 syntax, which is the same as what PlotMe uses.
var speciallyParsedIndex: Int? = null; private set
fun addSpeciallyTreatedKeys(vararg keys: String) {
for (key in keys) {
speciallyTreatedKeys.add(key + ":")
}
}
@Throws(CommandException::class)
override fun getChild(key: String, context: ExecutionContext): ChildCommandAddress? {
speciallyParsedIndex = null
for (specialKey in speciallyTreatedKeys) {
if (key.startsWith(specialKey)) {
val result = getChild(specialKey.substring(0, specialKey.length - 1))
?: return null
val text = key.substring(specialKey.length)
val num = text.toIntOrNull() ?: throw CommandException("$text is not a number")
speciallyParsedIndex = num
return result
}
}
return super.getChild(key)
}
}

View File

@@ -83,7 +83,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
}
}
class PType(val parcelProvider: ParcelProvider) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, Config) {
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, Config) {
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
var input = buffer.next()
@@ -124,11 +124,25 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
val ownerString: String
val index: Int?
val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex
if (splitIdx == -1) {
// just the index.
index = input.toIntOrNull()
ownerString = if (index == null) input else ""
if (speciallyParsedIndex == null) {
// just the index.
index = input.toIntOrNull()
ownerString = if (index == null) input else ""
} else {
// just the owner.
index = speciallyParsedIndex
ownerString = input
}
} else {
if (speciallyParsedIndex != null) {
invalidInput(parameter, "Duplicate home index")
}
ownerString = input.substring(0, splitIdx)
val indexString = input.substring(splitIdx + 1)
@@ -165,7 +179,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
return ByID(world, id, kind, true)
}
return ByOwner(world, PlayerProfile(player), 0, kind, true)
return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
}
}

View File

@@ -119,9 +119,9 @@ class DefaultParcelGenerator(
worldId: ParcelWorldId,
container: ParcelContainer,
coroutineScope: CoroutineScope,
worktimeLimiter: WorktimeLimiter
workDispatcher: WorkDispatcher
): Pair<ParcelLocator, ParcelBlockManager> {
return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, worktimeLimiter)
return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, workDispatcher)
}
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
@@ -156,7 +156,7 @@ class DefaultParcelGenerator(
private inner class ParcelBlockManagerImpl(
val worldId: ParcelWorldId,
coroutineScope: CoroutineScope,
override val worktimeLimiter: WorktimeLimiter
override val workDispatcher: WorkDispatcher
) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope {
override val world: World = this@DefaultParcelGenerator.world
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
@@ -222,12 +222,12 @@ class DefaultParcelGenerator(
return world.getParcelById(parcelId)
}
override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker {
override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: WorkerTask): Worker {
val parcels = parcelIds.mapNotNull { getParcel(it) }
if (parcels.isEmpty()) return worktimeLimiter.submit(task)
if (parcels.isEmpty()) return workDispatcher.dispatch(task)
if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor")
val worker = worktimeLimiter.submit(task)
val worker = workDispatcher.dispatch(task)
for (parcel in parcels) {
launch(start = UNDISPATCHED) {
@@ -277,10 +277,10 @@ 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) } }
val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(world, getRegion(parcel1)) } }
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(world, getRegion(parcel2)) } }
delegateWork(0.25) { with(schematicOf1) { paste(world, getRegion(parcel2).origin) } }
delegateWork(0.25) { with(schematicOf2) { paste(world, getRegion(parcel1).origin) } }
}
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {

View File

@@ -59,7 +59,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") }
parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter)
plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.workDispatcher)
if (!worldExists) {
val time = DateTime.now()

View File

@@ -3,7 +3,7 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.blockvisitor.WorkDispatcher
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage
import kotlinx.coroutines.CoroutineScope
@@ -18,7 +18,7 @@ class ParcelWorldImpl(override val world: World,
override val globalPrivileges: GlobalPrivilegesManager,
containerFactory: ParcelContainerFactory,
coroutineScope: CoroutineScope,
worktimeLimiter: WorktimeLimiter)
workDispatcher: WorkDispatcher)
: ParcelWorld,
ParcelWorldId,
ParcelContainer, /* missing delegation */
@@ -39,7 +39,7 @@ class ParcelWorldImpl(override val world: World,
override val blockManager: ParcelBlockManager
init {
val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, worktimeLimiter)
val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, workDispatcher)
locator = pair.first
blockManager = pair.second

View File

@@ -198,8 +198,8 @@ class ParcelListeners(
val type = clickedBlock.type
val interactableClass = Interactables[type]
if (interactableClass != null && (parcel.effectiveInteractableConfig.isInteractable(type) || (parcel != null && parcel.canBuild(user)))) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} in this parcel")
if (interactableClass != null && !parcel.effectiveInteractableConfig.isInteractable(type) && (parcel == null || !parcel.canBuild(user))) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} here")
event.isCancelled = true
return@l
}
@@ -595,4 +595,16 @@ class ParcelListeners(
storage.updatePlayerName(event.player.uuid, event.player.name)
}
/**
* Attempts to prevent redstone contraptions from breaking while they are being swapped
* Might remove if it causes lag
*/
@ListenerMarker
val onBlockRedstoneEvent = RegistratorListener<BlockRedstoneEvent> l@{ event ->
val (_, area) = getWorldAndArea(event.block) ?: return@l
if (area == null || area.hasBlockVisitors) {
event.newCurrent = event.oldCurrent
}
}
}

View File

@@ -26,6 +26,7 @@ data class Vec3i(
val z: Int
) {
operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
infix fun addX(o: Int) = Vec3i(x + o, y, z)
infix fun addY(o: Int) = Vec3i(x, y + o, z)
infix fun addZ(o: Int) = Vec3i(x, y, z + o)