From 842e52bd92f0c67aa1906b899ce600ecb3b26bda Mon Sep 17 00:00:00 2001 From: Dico Date: Thu, 27 Sep 2018 07:03:02 +0100 Subject: Fixes n tweaks --- src/main/kotlin/io/dico/parcels2/Interactable.kt | 2 +- src/main/kotlin/io/dico/parcels2/Parcel.kt | 2 +- .../kotlin/io/dico/parcels2/ParcelGenerator.kt | 20 +- src/main/kotlin/io/dico/parcels2/ParcelId.kt | 2 +- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 4 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 12 +- src/main/kotlin/io/dico/parcels2/Privilege.kt | 7 +- .../io/dico/parcels2/blockvisitor/Attachables.kt | 2 +- .../io/dico/parcels2/blockvisitor/JobDispatcher.kt | 339 +++++++++++++++++++++ .../dico/parcels2/blockvisitor/RegionTraverser.kt | 49 +-- .../io/dico/parcels2/blockvisitor/Schematic.kt | 14 +- .../dico/parcels2/blockvisitor/WorkDispatcher.kt | 339 --------------------- .../parcels2/command/AbstractParcelCommands.kt | 4 +- .../io/dico/parcels2/command/CommandsDebug.kt | 4 +- .../io/dico/parcels2/command/ParcelTarget.kt | 4 +- .../parcels2/defaultimpl/DefaultParcelGenerator.kt | 35 ++- .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 2 +- .../parcels2/defaultimpl/ParcelProviderImpl.kt | 23 +- .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 6 +- .../io/dico/parcels2/listener/ParcelListeners.kt | 51 +++- .../kotlin/io/dico/parcels2/options/Options.kt | 4 +- .../kotlin/io/dico/parcels2/storage/Backing.kt | 4 +- .../kotlin/io/dico/parcels2/storage/Storage.kt | 10 +- .../parcels2/storage/exposed/ExposedBacking.kt | 19 +- .../io/dico/parcels2/storage/exposed/ListTables.kt | 18 +- src/main/kotlin/io/dico/parcels2/util/Region.kt | 18 -- src/main/kotlin/io/dico/parcels2/util/Vec2i.kt | 11 - src/main/kotlin/io/dico/parcels2/util/Vec3i.kt | 96 ------ src/main/kotlin/io/dico/parcels2/util/ext/Math.kt | 41 --- src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt | 5 + .../kotlin/io/dico/parcels2/util/math/Dimension.kt | 19 ++ .../kotlin/io/dico/parcels2/util/math/Region.kt | 37 +++ .../kotlin/io/dico/parcels2/util/math/Vec2i.kt | 6 + .../kotlin/io/dico/parcels2/util/math/Vec3d.kt | 54 ++++ .../kotlin/io/dico/parcels2/util/math/Vec3i.kt | 104 +++++++ .../kotlin/io/dico/parcels2/util/math/ext/Math.kt | 42 +++ 36 files changed, 780 insertions(+), 629 deletions(-) create mode 100644 src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/blockvisitor/WorkDispatcher.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/util/Region.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/util/Vec2i.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/util/Vec3i.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/util/ext/Math.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/math/Region.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/math/ext/Math.kt diff --git a/src/main/kotlin/io/dico/parcels2/Interactable.kt b/src/main/kotlin/io/dico/parcels2/Interactable.kt index 100e433..825be94 100644 --- a/src/main/kotlin/io/dico/parcels2/Interactable.kt +++ b/src/main/kotlin/io/dico/parcels2/Interactable.kt @@ -1,6 +1,6 @@ package io.dico.parcels2 -import io.dico.parcels2.util.ext.ceilDiv +import io.dico.parcels2.util.math.ext.ceilDiv import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix import org.bukkit.Material import java.util.EnumMap diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 22ec0df..517b88f 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,6 +1,6 @@ package io.dico.parcels2 -import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.math.Vec2i import org.bukkit.Location import org.joda.time.DateTime import java.util.UUID diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt index 4e0aeb4..d45ff83 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -1,9 +1,9 @@ package io.dico.parcels2 import io.dico.parcels2.blockvisitor.* -import io.dico.parcels2.util.Region -import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.get +import io.dico.parcels2.util.math.Region +import io.dico.parcels2.util.math.Vec2i +import io.dico.parcels2.util.math.get import kotlinx.coroutines.CoroutineScope import org.bukkit.Chunk import org.bukkit.Location @@ -37,12 +37,12 @@ abstract class ParcelGenerator : ChunkGenerator() { abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId, container: ParcelContainer, coroutineScope: CoroutineScope, - workDispatcher: WorkDispatcher): Pair + jobDispatcher: JobDispatcher): Pair } interface ParcelBlockManager { val world: World - val workDispatcher: WorkDispatcher + val jobDispatcher: JobDispatcher val parcelTraverser: RegionTraverser // fun getBottomBlock(parcel: ParcelId): Vec2i @@ -55,13 +55,13 @@ interface ParcelBlockManager { fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) - fun setBiome(parcel: ParcelId, biome: Biome): Worker + fun setBiome(parcel: ParcelId, biome: Biome): Job - fun clearParcel(parcel: ParcelId): Worker + fun clearParcel(parcel: ParcelId): Job - fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker + fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Job - fun submitBlockVisitor(vararg parcelIds: ParcelId, task: WorkerTask): Worker + fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job /** * Used to update owner blocks in the corner of the parcel @@ -71,7 +71,7 @@ interface ParcelBlockManager { inline fun ParcelBlockManager.doBlockOperation(parcel: ParcelId, traverser: RegionTraverser, - crossinline operation: suspend WorkerScope.(Block) -> Unit) = submitBlockVisitor(parcel) { + crossinline operation: suspend JobScope.(Block) -> Unit) = submitBlockVisitor(parcel) { val region = getRegion(parcel) val blockCount = region.blockCount.toDouble() val blocks = traverser.traverseRegion(region) diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt index 72eceb9..926fc23 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelId.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt @@ -1,6 +1,6 @@ package io.dico.parcels2 -import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.math.Vec2i import org.bukkit.Bukkit import org.bukkit.World import java.util.UUID diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 1e9dc44..87e4c68 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -2,8 +2,8 @@ package io.dico.parcels2 import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.ext.floor +import io.dico.parcels2.util.math.Vec2i +import io.dico.parcels2.util.math.ext.floor import org.bukkit.Location import org.bukkit.World import org.bukkit.block.Block diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 7af7468..b1bf554 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -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.BukkitWorkDispatcher -import io.dico.parcels2.blockvisitor.WorkDispatcher +import io.dico.parcels2.blockvisitor.BukkitJobDispatcher +import io.dico.parcels2.blockvisitor.JobDispatcher 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 workDispatcher: WorkDispatcher by lazy { BukkitWorkDispatcher(this, options.tickWorktime) } + val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) } override fun onEnable() { plogger.info("Debug enabled: ${plogger.isDebugEnabled}") @@ -55,11 +55,11 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler { } override fun onDisable() { - val hasWorkers = workDispatcher.workers.isNotEmpty() + val hasWorkers = jobDispatcher.jobs.isNotEmpty() if (hasWorkers) { - plogger.warn("Parcels is attempting to complete all ${workDispatcher.workers.size} remaining jobs before shutdown...") + plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...") } - workDispatcher.completeAllTasks() + jobDispatcher.completeAllTasks() if (hasWorkers) { plogger.info("Parcels has completed the remaining jobs.") } diff --git a/src/main/kotlin/io/dico/parcels2/Privilege.kt b/src/main/kotlin/io/dico/parcels2/Privilege.kt index bc16f77..7949dbc 100644 --- a/src/main/kotlin/io/dico/parcels2/Privilege.kt +++ b/src/main/kotlin/io/dico/parcels2/Privilege.kt @@ -66,7 +66,7 @@ interface RawPrivileges { open class PrivilegesHolder(override var privilegeMap: MutablePrivilegeMap = EmptyPrivilegeMap) : RawPrivileges { private var _privilegeOfStar: Privilege = DEFAULT - override var privilegeOfStar: Privilege + override /*open*/ var privilegeOfStar: Privilege get() = _privilegeOfStar set(value) = run { _privilegeOfStar = value } @@ -94,5 +94,10 @@ open class PrivilegesHolder(override var privilegeMap: MutablePrivilegeMap = Emp return if (privilege == DEFAULT) privilegeMap.remove(key) != null else privilegeMap.put(key, privilege) != privilege } + + fun copyPrivilegesFrom(other: PrivilegesHolder) { + privilegeMap = other.privilegeMap + privilegeOfStar = other.privilegeOfStar + } } diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index f5b7ad8..8f2c565 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -1,6 +1,6 @@ package io.dico.parcels2.blockvisitor -import io.dico.parcels2.util.Vec3i +import io.dico.parcels2.util.math.Vec3i import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix import io.dico.parcels2.util.ext.getMaterialsWithWoolColorPrefix import org.bukkit.Material diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt new file mode 100644 index 0000000..702a680 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/JobDispatcher.kt @@ -0,0 +1,339 @@ +package io.dico.parcels2.blockvisitor + +import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.logger +import io.dico.parcels2.util.math.ext.clampMin +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart.LAZY +import kotlinx.coroutines.Job as CoroutineJob +import kotlinx.coroutines.launch +import org.bukkit.scheduler.BukkitTask +import java.lang.System.currentTimeMillis +import java.util.LinkedList +import kotlin.coroutines.Continuation +import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn +import kotlin.coroutines.resume + +typealias JobFunction = suspend JobScope.() -> Unit +typealias JobUpdateLister = Job.(Double, Long) -> Unit + +data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int) + +interface JobDispatcher { + /** + * Submit a [task] that should be run synchronously, but limited such that it does not stall the server + */ + fun dispatch(task: JobFunction): Job + + /** + * Get a list of all jobs + */ + val jobs: List + + /** + * Attempts to complete any remaining tasks immediately, without suspension. + */ + fun completeAllTasks() +} + +interface JobAndScopeMembersUnion { + /** + * The time that elapsed since this job was dispatched, in milliseconds + */ + val elapsedTime: Long + + /** + * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0 + * with no guarantees to its accuracy. + */ + val progress: Double +} + +interface Job : JobAndScopeMembersUnion { + /** + * The coroutine associated with this job + */ + val job: CoroutineJob + + /** + * true if this job has completed + */ + val isComplete: Boolean + + /** + * If an exception was thrown during the execution of this task, + * returns that exception. Returns null otherwise. + */ + val completionException: Throwable? + + /** + * Calls the given [block] whenever the progress of this job is updated, + * if [minInterval] milliseconds expired since the last call. + * The first call occurs after at least [minDelay] milliseconds in a likewise manner. + * Repeated invocations of this method result in an [IllegalStateException] + * + * if [asCompletionListener] is true, [onCompleted] is called with the same [block] + */ + fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job + + /** + * Calls the given [block] when this job completes, with the progress value 1.0. + * Multiple listeners may be registered to this function. + */ + fun onCompleted(block: JobUpdateLister): Job + + /** + * Await completion of this job + */ + suspend fun awaitCompletion() +} + +interface JobScope : JobAndScopeMembersUnion { + /** + * A task should call this frequently during its execution, such that the timer can suspend it when necessary. + */ + suspend fun markSuspensionPoint() + + /** + * 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 [JobScope] that is responsible for [portion] part of the progress + * If [portion] is negative, the remaining progress is used + */ + fun delegateProgress(portion: Double = -1.0): JobScope +} + +inline fun JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T { + delegateProgress(portion).apply { + val result = block() + markComplete() + return result + } +} + +interface JobInternal : Job, JobScope { + /** + * Start or resumes the execution of this job + * and returns true if the job completed + * + * [worktime] is the maximum amount of time, in milliseconds, + * that this job may run for until suspension. + * + * If [worktime] is not positive, the job will complete + * without suspension and this method will always return true. + */ + fun resume(worktime: Long): Boolean +} + +/** + * An object that controls one or more jobs, ensuring that they don't stall the server too much. + * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick + * This object attempts to split that maximum amount of milliseconds equally between all jobs + */ +class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher { + // The currently registered bukkit scheduler task + private var bukkitTask: BukkitTask? = null + // The jobs. + private val _jobs = LinkedList() + override val jobs: List = _jobs + + override fun dispatch(task: JobFunction): Job { + val job: JobInternal = JobImpl(plugin, task) + + if (bukkitTask == null) { + val completed = job.resume(options.jobTime.toLong()) + if (completed) return job + bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() } + } + _jobs.addFirst(job) + return job + } + + private fun tickCoroutineJobs() { + val jobs = _jobs + if (jobs.isEmpty()) return + val tickStartTime = System.currentTimeMillis() + + val iterator = jobs.listIterator(index = 0) + while (iterator.hasNext()) { + val time = System.currentTimeMillis() + val timeElapsed = time - tickStartTime + val timeLeft = options.jobTime - timeElapsed + if (timeLeft <= 0) return + + val count = jobs.size - iterator.nextIndex() + val timePerJob = (timeLeft + count - 1) / count + val job = iterator.next() + val completed = job.resume(timePerJob) + if (completed) { + iterator.remove() + } + } + + if (jobs.isEmpty()) { + bukkitTask?.cancel() + bukkitTask = null + } + } + + override fun completeAllTasks() { + _jobs.forEach { + it.resume(-1) + } + _jobs.clear() + bukkitTask?.cancel() + bukkitTask = null + } + +} + +private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal { + override val job: CoroutineJob = scope.launch(start = LAZY) { task() } + + private var continuation: Continuation? = null + private var nextSuspensionTime: Long = 0L + private var completeForcefully = false + private var isStarted = false + + override val elapsedTime + get() = + if (job.isCompleted) startTimeOrElapsedTime + else currentTimeMillis() - startTimeOrElapsedTime + + override val isComplete get() = job.isCompleted + + private var _progress = 0.0 + override val progress get() = _progress + override var completionException: Throwable? = null; private set + + private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise + private var onProgressUpdate: JobUpdateLister? = null + private var progressUpdateInterval: Int = 0 + private var lastUpdateTime: Long = 0L + private var onCompleted: JobUpdateLister? = null + + init { + job.invokeOnCompletion { exception -> + // report any error that occurred + completionException = exception?.also { + if (it !is CancellationException) + logger.error("JobFunction generated an exception", it) + } + + // convert to elapsed time here + startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime + onCompleted?.let { it(1.0, elapsedTime) } + + onCompleted = null + onProgressUpdate = { prog, el -> } + } + } + + override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job { + onProgressUpdate?.let { throw IllegalStateException() } + if (asCompletionListener) onCompleted(block) + if (isComplete) return this + onProgressUpdate = block + progressUpdateInterval = minInterval + lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval + + return this + } + + override fun onCompleted(block: JobUpdateLister): Job { + if (isComplete) { + block(1.0, startTimeOrElapsedTime) + return this + } + + val cur = onCompleted + onCompleted = if (cur == null) { + block + } else { + fun Job.(prog: Double, el: Long) { + cur(prog, el) + block(prog, el) + } + } + return this + } + + override suspend fun markSuspensionPoint() { + if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) + suspendCoroutineUninterceptedOrReturn { cont: Continuation -> + continuation = cont + COROUTINE_SUSPENDED + } + } + + override fun setProgress(progress: Double) { + this._progress = progress + val onProgressUpdate = onProgressUpdate ?: return + val time = System.currentTimeMillis() + if (time > lastUpdateTime + progressUpdateInterval) { + onProgressUpdate(progress, elapsedTime) + lastUpdateTime = time + } + } + + override fun resume(worktime: Long): Boolean { + if (isComplete) return true + + if (worktime > 0) { + nextSuspensionTime = currentTimeMillis() + worktime + } else { + completeForcefully = true + } + + if (isStarted) { + continuation?.let { + continuation = null + it.resume(Unit) + return continuation == null + } + return true + } + + isStarted = true + startTimeOrElapsedTime = System.currentTimeMillis() + job.start() + + return continuation == null + } + + override suspend fun awaitCompletion() { + job.join() + } + + private fun delegateProgress(curPortion: Double, portion: Double): JobScope = + DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) + + override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion) + + private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope { + override val elapsedTime: Long + get() = this@JobImpl.elapsedTime + + override suspend fun markSuspensionPoint() = + this@JobImpl.markSuspensionPoint() + + override val progress: Double + get() = (this@JobImpl.progress - progressStart) / portion + + override fun setProgress(progress: Double) = + this@JobImpl.setProgress(progressStart + progress * portion) + + override fun delegateProgress(portion: Double): JobScope = + this@JobImpl.delegateProgress(this.portion, portion) + } +} diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt index feab318..bd00407 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt @@ -1,8 +1,9 @@ package io.dico.parcels2.blockvisitor -import io.dico.parcels2.util.Region -import io.dico.parcels2.util.Vec3i -import io.dico.parcels2.util.ext.clampMax +import io.dico.parcels2.util.math.Dimension +import io.dico.parcels2.util.math.Region +import io.dico.parcels2.util.math.Vec3i +import io.dico.parcels2.util.math.ext.clampMax private typealias Scope = SequenceScope @@ -54,9 +55,9 @@ sealed class RegionTraverser { 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 maxOfPrimary = size[primary] - 1 + val maxOfSecondary = size[secondary] - 1 + val maxOfTertiary = size[tertiary] - 1 val isPrimaryIncreasing = direction.isIncreasing(primary) val isSecondaryIncreasing = direction.isIncreasing(secondary) @@ -137,24 +138,6 @@ sealed class RegionTraverser { } -enum class Dimension { - X, - Y, - Z; - - fun extract(block: Vec3i) = - when (this) { - X -> block.x - Y -> block.y - Z -> block.z - } - - companion object { - private val values = values() - operator fun get(ordinal: Int) = values[ordinal] - } -} - object TraverseOrderFactory { private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3 @@ -194,15 +177,15 @@ inline class TraverseOrder(val orderNum: Int) { */ fun toArray() = arrayOf(primary, secondary, tertiary) - fun add(vec: Vec3i, dp: Int, ds: Int, dt: Int): Vec3i = + fun add(vec: Vec3i, p: Int, s: Int, t: Int): Vec3i = // optimize this, will be called lots when (orderNum) { - 0 -> vec.add(dp, ds, dt) // xyz - 1 -> vec.add(dt, dp, ds) // yzx - 2 -> vec.add(ds, dt, dp) // zxy - 3 -> vec.add(dp, dt, ds) // xzy - 4 -> vec.add(ds, dp, dt) // yxz - 5 -> vec.add(dt, ds, dp) // zyx + 0 -> vec.add(p, s, t) // xyz + 1 -> vec.add(t, p, s) // yzx + 2 -> vec.add(s, t, p) // zxy + 3 -> vec.add(p, t, s) // xzy + 4 -> vec.add(s, p, t) // yxz + 5 -> vec.add(t, s, p) // zyx else -> error("Invalid orderNum $orderNum") } } @@ -212,9 +195,9 @@ inline class TraverseDirection(val bits: Int) { fun comesFirst(current: Vec3i, block: Vec3i, dimension: Dimension): Boolean = if (isIncreasing(dimension)) - dimension.extract(block) <= dimension.extract(current) + block[dimension] <= current[dimension] else - dimension.extract(block) >= dimension.extract(current) + block[dimension] >= current[dimension] fun comesFirst(current: Vec3i, block: Vec3i) = comesFirst(current, block, Dimension.X) diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index 9f88fd9..84931b2 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -1,8 +1,8 @@ package io.dico.parcels2.blockvisitor -import io.dico.parcels2.util.Region -import io.dico.parcels2.util.Vec3i -import io.dico.parcels2.util.get +import io.dico.parcels2.util.math.Region +import io.dico.parcels2.util.math.Vec3i +import io.dico.parcels2.util.math.get import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.World @@ -24,7 +24,7 @@ class Schematic { private var isLoaded = false; private set private val traverser: RegionTraverser = RegionTraverser.upward - suspend fun WorkerScope.load(world: World, region: Region) { + suspend fun JobScope.load(world: World, region: Region) { _size = region.size val data = arrayOfNulls(region.blockCount).also { blockDatas = it } @@ -52,7 +52,7 @@ class Schematic { isLoaded = true } - suspend fun WorkerScope.paste(world: World, position: Vec3i) { + suspend fun JobScope.paste(world: World, position: Vec3i) { if (!isLoaded) throw IllegalStateException() val region = Region(position, _size!!) @@ -108,11 +108,11 @@ class Schematic { } } - fun getLoadTask(world: World, region: Region): WorkerTask = { + fun getLoadTask(world: World, region: Region): JobFunction = { load(world, region) } - fun getPasteTask(world: World, position: Vec3i): WorkerTask = { + fun getPasteTask(world: World, position: Vec3i): JobFunction = { paste(world, position) } diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorkDispatcher.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorkDispatcher.kt deleted file mode 100644 index 7162561..0000000 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorkDispatcher.kt +++ /dev/null @@ -1,339 +0,0 @@ -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 -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.bukkit.scheduler.BukkitTask -import java.lang.System.currentTimeMillis -import java.util.LinkedList -import kotlin.coroutines.Continuation -import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn -import kotlin.coroutines.resume - -typealias WorkerTask = suspend WorkerScope.() -> Unit -typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit - -data class TickWorktimeOptions(var workTime: Int, var tickInterval: Int) - -interface WorkDispatcher { - /** - * Submit a [task] that should be run synchronously, but limited such that it does not stall the server - */ - fun dispatch(task: WorkerTask): Worker - - /** - * Get a list of all workers - */ - val workers: List - - /** - * Attempts to complete any remaining tasks immediately, without suspension. - */ - fun completeAllTasks() -} - -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 : WorkerAndScopeMembersUnion { - /** - * The coroutine associated with this worker - */ - val job: Job - - /** - * true if this worker has completed - */ - val isComplete: Boolean - - /** - * If an exception was thrown during the execution of this task, - * returns that exception. Returns null otherwise. - */ - val completionException: Throwable? - - /** - * Calls the given [block] whenever the progress of this worker is updated, - * if [minInterval] milliseconds expired since the last call. - * The first call occurs after at least [minDelay] milliseconds in a likewise manner. - * Repeated invocations of this method result in an [IllegalStateException] - * - * if [asCompletionListener] is true, [onCompleted] is called with the same [block] - */ - fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: WorkerUpdateLister): Worker - - /** - * Calls the given [block] when this worker completes, with the progress value 1.0. - * Multiple listeners may be registered to this function. - */ - fun onCompleted(block: WorkerUpdateLister): Worker - - /** - * Await completion of this worker - */ - suspend fun awaitCompletion() -} - -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 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 remaining progress is used - */ - fun delegateWork(portion: Double = -1.0): WorkerScope -} - -inline fun WorkerScope.delegateWork(portion: Double = -1.0, block: WorkerScope.() -> T): T { - delegateWork(portion).apply { - val result = block() - markComplete() - return result - } -} - -interface WorkerInternal : Worker, WorkerScope { - /** - * Start or resumes the execution of this worker - * and returns true if the worker completed - * - * [worktime] is the maximum amount of time, in milliseconds, - * that this job may run for until suspension. - * - * If [worktime] is not positive, the worker will complete - * without suspension and this method will always return true. - */ - fun resume(worktime: Long): Boolean -} - -/** - * An object that controls one or more jobs, ensuring that they don't stall the server too much. - * 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 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() - override val workers: List = _workers - - override fun dispatch(task: WorkerTask): Worker { - val worker: WorkerInternal = WorkerImpl(plugin, task) - - if (bukkitTask == null) { - val completed = worker.resume(options.workTime.toLong()) - if (completed) return worker - bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickJobs() } - } - _workers.addFirst(worker) - return worker - } - - private fun tickJobs() { - val workers = _workers - if (workers.isEmpty()) return - val tickStartTime = System.currentTimeMillis() - - val iterator = workers.listIterator(index = 0) - while (iterator.hasNext()) { - val time = System.currentTimeMillis() - val timeElapsed = time - tickStartTime - val timeLeft = options.workTime - timeElapsed - if (timeLeft <= 0) return - - val count = workers.size - iterator.nextIndex() - val timePerJob = (timeLeft + count - 1) / count - val worker = iterator.next() - val completed = worker.resume(timePerJob) - if (completed) { - iterator.remove() - } - } - - if (workers.isEmpty()) { - bukkitTask?.cancel() - bukkitTask = null - } - } - - override fun completeAllTasks() { - _workers.forEach { - it.resume(-1) - } - _workers.clear() - bukkitTask?.cancel() - bukkitTask = null - } - -} - -private class WorkerImpl(scope: CoroutineScope, task: WorkerTask) : WorkerInternal { - override val job: Job = scope.launch(start = LAZY) { task() } - - private var continuation: Continuation? = null - private var nextSuspensionTime: Long = 0L - private var completeForcefully = false - private var isStarted = false - - override val elapsedTime - get() = - if (job.isCompleted) startTimeOrElapsedTime - else currentTimeMillis() - startTimeOrElapsedTime - - override val isComplete get() = job.isCompleted - - private var _progress = 0.0 - override val progress get() = _progress - override var completionException: Throwable? = 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 - - init { - job.invokeOnCompletion { exception -> - // report any error that occurred - completionException = exception?.also { - if (it !is CancellationException) - logger.error("WorkerTask generated an exception", it) - } - - // convert to elapsed time here - startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime - onCompleted?.let { it(1.0, elapsedTime) } - - onCompleted = null - onProgressUpdate = { prog, el -> } - } - } - - override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: WorkerUpdateLister): Worker { - onProgressUpdate?.let { throw IllegalStateException() } - if (asCompletionListener) onCompleted(block) - if (isComplete) return this - onProgressUpdate = block - progressUpdateInterval = minInterval - lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval - - return this - } - - override fun onCompleted(block: WorkerUpdateLister): Worker { - if (isComplete) { - block(1.0, startTimeOrElapsedTime) - return this - } - - val cur = onCompleted - onCompleted = if (cur == null) { - block - } else { - fun Worker.(prog: Double, el: Long) { - cur(prog, el) - block(prog, el) - } - } - return this - } - - override suspend fun markSuspensionPoint() { - if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) - suspendCoroutineUninterceptedOrReturn { cont: Continuation -> - continuation = cont - COROUTINE_SUSPENDED - } - } - - override fun setProgress(progress: Double) { - this._progress = progress - val onProgressUpdate = onProgressUpdate ?: return - val time = System.currentTimeMillis() - if (time > lastUpdateTime + progressUpdateInterval) { - onProgressUpdate(progress, elapsedTime) - lastUpdateTime = time - } - } - - override fun resume(worktime: Long): Boolean { - if (isComplete) return true - - if (worktime > 0) { - nextSuspensionTime = currentTimeMillis() + worktime - } else { - completeForcefully = true - } - - if (isStarted) { - continuation?.let { - continuation = null - it.resume(Unit) - return continuation == null - } - return true - } - - isStarted = true - startTimeOrElapsedTime = System.currentTimeMillis() - job.start() - - return continuation == null - } - - override suspend fun awaitCompletion() { - 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) - } -} diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index 79faf36..41f5d36 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -6,7 +6,7 @@ import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile.* import io.dico.parcels2.PrivilegeKey -import io.dico.parcels2.blockvisitor.Worker +import io.dico.parcels2.blockvisitor.Job import io.dico.parcels2.util.ext.hasPermAdminManage import io.dico.parcels2.util.ext.parcelLimit import org.bukkit.entity.Player @@ -50,7 +50,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei world.blockManager.clearParcel(parcel.id) } - protected fun Worker.reportProgressUpdates(context: ExecutionContext, action: String) { + protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String) { onProgressUpdate(1000, 1000) { progress, elapsedTime -> val alt = context.getFormat(EMessageType.NUMBER) val main = context.getFormat(EMessageType.INFORMATIVE) diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 6331b4c..c6a4d6a 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -81,14 +81,14 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("jobs") fun cmdJobs(): Any? { - val workers = plugin.workDispatcher.workers + val workers = plugin.jobDispatcher.jobs println(workers.map { it.job }.joinToString(separator = "\n")) return "Task count: ${workers.size}" } @Cmd("complete_jobs") fun cmdCompleteJobs(): Any? = cmdJobs().also { - plugin.launch { plugin.workDispatcher.completeAllTasks() } + plugin.launch { plugin.jobDispatcher.completeAllTasks() } } @Cmd("message") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index 2852ff0..05a10bd 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -16,8 +16,8 @@ import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.ext.floor +import io.dico.parcels2.util.math.Vec2i +import io.dico.parcels2.util.math.ext.floor import org.bukkit.command.CommandSender import org.bukkit.entity.Player diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt index 7ccb458..25d76e3 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -3,12 +3,12 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* import io.dico.parcels2.blockvisitor.* import io.dico.parcels2.options.DefaultGeneratorOptions -import io.dico.parcels2.util.Region -import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.Vec3i -import io.dico.parcels2.util.ext.even -import io.dico.parcels2.util.ext.umod -import io.dico.parcels2.util.get +import io.dico.parcels2.util.math.Region +import io.dico.parcels2.util.math.Vec2i +import io.dico.parcels2.util.math.Vec3i +import io.dico.parcels2.util.math.ext.even +import io.dico.parcels2.util.math.ext.umod +import io.dico.parcels2.util.math.get import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart.UNDISPATCHED import kotlinx.coroutines.launch @@ -121,9 +121,9 @@ class DefaultParcelGenerator( worldId: ParcelWorldId, container: ParcelContainer, coroutineScope: CoroutineScope, - workDispatcher: WorkDispatcher + jobDispatcher: JobDispatcher ): Pair { - return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, workDispatcher) + return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, jobDispatcher) } private inline fun convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? { @@ -158,7 +158,7 @@ class DefaultParcelGenerator( private inner class ParcelBlockManagerImpl( val worldId: ParcelWorldId, coroutineScope: CoroutineScope, - override val workDispatcher: WorkDispatcher + override val jobDispatcher: JobDispatcher ) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope { override val world: World = this@DefaultParcelGenerator.world override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight) @@ -177,7 +177,10 @@ class DefaultParcelGenerator( override fun getRegion(parcel: ParcelId): Region { val bottom = getBottomBlock(parcel) - return Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight, o.parcelSize)) + return Region( + Vec3i(bottom.x, 0, bottom.z), + Vec3i(o.parcelSize, maxHeight, o.parcelSize) + ) } private fun getRegionConsideringWorld(parcel: ParcelId): Region { @@ -234,12 +237,12 @@ class DefaultParcelGenerator( return world.getParcelById(parcelId) } - override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: WorkerTask): Worker { + override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job { val parcels = parcelIds.mapNotNull { getParcel(it) } - if (parcels.isEmpty()) return workDispatcher.dispatch(task) + if (parcels.isEmpty()) return jobDispatcher.dispatch(task) if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor") - val worker = workDispatcher.dispatch(task) + val worker = jobDispatcher.dispatch(task) for (parcel in parcels) { launch(start = UNDISPATCHED) { @@ -252,7 +255,7 @@ class DefaultParcelGenerator( return worker } - override fun setBiome(parcel: ParcelId, biome: Biome): Worker = submitBlockVisitor(parcel) { + override fun setBiome(parcel: ParcelId, biome: Biome): Job = submitBlockVisitor(parcel) { val world = world val b = getBottomBlock(parcel) val parcelSize = o.parcelSize @@ -264,7 +267,7 @@ class DefaultParcelGenerator( } } - override fun clearParcel(parcel: ParcelId): Worker = submitBlockVisitor(parcel) { + override fun clearParcel(parcel: ParcelId): Job = submitBlockVisitor(parcel) { val region = getRegion(parcel) val blocks = parcelTraverser.traverseRegion(region) val blockCount = region.blockCount.toDouble() @@ -288,7 +291,7 @@ class DefaultParcelGenerator( } } - override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker = submitBlockVisitor(parcel1, parcel2) { + override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Job = submitBlockVisitor(parcel1, parcel2) { var region1 = getRegionConsideringWorld(parcel1) var region2 = getRegionConsideringWorld(parcel2) diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index 243ec19..65378bf 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -3,7 +3,7 @@ package io.dico.parcels2.defaultimpl import io.dico.dicore.Formatting import io.dico.parcels2.* import io.dico.parcels2.Privilege.* -import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.math.Vec2i import io.dico.parcels2.util.ext.alsoIfTrue import org.bukkit.Material import org.joda.time.DateTime diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt index ff2fcd4..1edf849 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -58,8 +58,10 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { if (worldExists) Bukkit.getWorld(worldName)!! 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.workDispatcher) + parcelWorld = ParcelWorldImpl( + bukkitWorld, generator, worldOptions.runtime, plugin.storage, + plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.jobDispatcher + ) if (!worldExists) { val time = DateTime.now() @@ -95,11 +97,18 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { logger.info("Loading all parcel data...") val channel = plugin.storage.transmitAllParcelData() - do { - val pair = channel.receiveOrNull() ?: break - val parcel = getParcelById(pair.first) ?: continue - pair.second?.let { parcel.copyDataIgnoringDatabase(it) } - } while (true) + while (true) { + val (id, data) = channel.receiveOrNull() ?: break + val parcel = getParcelById(id) ?: continue + data?.let { parcel.copyDataIgnoringDatabase(it) } + } + + val channel2 = plugin.storage.transmitAllGlobalPrivileges() + while (true) { + val (profile, data) = channel2.receiveOrNull() ?: break + val key = profile as? PrivilegeKey ?: continue + (plugin.globalPrivileges[key] as PrivilegesHolder).copyPrivilegesFrom(data) + } logger.info("Loading data completed") _dataIsLoaded = true diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt index 24bad79..78257c3 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -3,7 +3,7 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.WorkDispatcher +import io.dico.parcels2.blockvisitor.JobDispatcher 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, - workDispatcher: WorkDispatcher) + jobDispatcher: JobDispatcher) : 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, workDispatcher) + val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, jobDispatcher) locator = pair.first blockManager = pair.second diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index a62f569..ff96536 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -7,6 +7,12 @@ import io.dico.dicore.RegistratorListener import io.dico.parcels2.* import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.ext.* +import io.dico.parcels2.util.math.Dimension +import io.dico.parcels2.util.math.Vec3d +import io.dico.parcels2.util.math.Vec3i +import io.dico.parcels2.util.math.ext.clampMax +import io.dico.parcels2.util.math.ext.clampMin +import org.bukkit.Location import org.bukkit.Material.* import org.bukkit.World import org.bukkit.block.Biome @@ -52,6 +58,7 @@ class ParcelListeners( return world to world.getParcelAt(block) } + /* * Prevents players from entering plots they are banned from */ @@ -59,12 +66,36 @@ class ParcelListeners( val onPlayerMoveEvent = RegistratorListener l@{ event -> val user = event.player if (user.hasPermBanBypass) return@l - val parcel = parcelProvider.getParcelAt(event.to) ?: return@l + val toLoc = event.to + val parcel = parcelProvider.getParcelAt(toLoc) ?: return@l + if (!parcel.canEnterFast(user)) { - parcelProvider.getParcelAt(event.from)?.also { - user.teleport(it.homeLocation) + val region = parcel.world.blockManager.getRegion(parcel.id) + val dimension = region.getFirstUncontainedDimensionOf(Vec3i(event.from)) + + if (dimension == null) { + user.teleport(parcel.homeLocation) user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") - } ?: run { event.to = event.from } + + } else { + val speed = getPlayerSpeed(user) + val from = Vec3d(event.from) + val to = Vec3d(toLoc).with(dimension, from[dimension]) + + var newTo = to + dimension.otherDimensions.forEach { + val delta = to[it] - from[it] + newTo = newTo.add(it, delta * 100 * if (it == Dimension.Y) 0.5 else speed) + } + + + + event.to = Location( + toLoc.world, + newTo.x, newTo.y.clampMin(0.0).clampMax(255.0), newTo.z, + toLoc.yaw, toLoc.pitch + ) + } } } @@ -607,4 +638,16 @@ class ParcelListeners( } } + + private fun getPlayerSpeed(player: Player): Double = + if (player.isFlying) { + player.flySpeed * if (player.isSprinting) 21.6 else 10.92 + } else { + player.walkSpeed * when { + player.isSprinting -> 5.612 + player.isSneaking -> 1.31 + else -> 4.317 + } / 1.5 //? + } / 20.0 + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/Options.kt b/src/main/kotlin/io/dico/parcels2/options/Options.kt index 79dbb46..dc80db9 100644 --- a/src/main/kotlin/io/dico/parcels2/options/Options.kt +++ b/src/main/kotlin/io/dico/parcels2/options/Options.kt @@ -1,6 +1,6 @@ package io.dico.parcels2.options -import io.dico.parcels2.blockvisitor.TickWorktimeOptions +import io.dico.parcels2.blockvisitor.TickJobtimeOptions import org.bukkit.GameMode import org.bukkit.Material import java.io.Reader @@ -11,7 +11,7 @@ class Options { var worlds: Map = hashMapOf() private set var storage: StorageOptions = StorageOptions() - var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) + var tickJobtime: TickJobtimeOptions = TickJobtimeOptions(20, 1) var migration = MigrationOptionsHolder() fun addWorld(name: String, diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 6f71471..2e0a8ee 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -61,9 +61,9 @@ interface Backing { fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) - fun transmitAllGlobalAddedData(channel: SendChannel>) + fun transmitAllGlobalPrivileges(channel: SendChannel>) - fun readGlobalPrivileges(owner: PlayerProfile): MutablePrivilegeMap + fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 334d010..abd9f41 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -14,7 +14,7 @@ import java.util.UUID import kotlin.coroutines.CoroutineContext typealias DataPair = Pair -typealias AddedDataPair = Pair +typealias PrivilegePair = Pair interface Storage { val name: String @@ -55,9 +55,9 @@ interface Storage { fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration): Job - fun transmitAllGlobalAddedData(): ReceiveChannel> + fun transmitAllGlobalPrivileges(): ReceiveChannel> - fun readGlobalPrivileges(owner: PlayerProfile): Deferred + fun readGlobalPrivileges(owner: PlayerProfile): Deferred fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job @@ -104,9 +104,9 @@ class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineSco override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) = b.launchJob { b.setParcelOptionsInteractConfig(parcel, config) } - override fun transmitAllGlobalAddedData(): ReceiveChannel> = b.openChannel { b.transmitAllGlobalAddedData(it) } + override fun transmitAllGlobalPrivileges(): ReceiveChannel> = b.openChannel { b.transmitAllGlobalPrivileges(it) } - override fun readGlobalPrivileges(owner: PlayerProfile): Deferred = b.launchFuture { b.readGlobalPrivileges(owner) } + override fun readGlobalPrivileges(owner: PlayerProfile): Deferred = b.launchFuture { b.readGlobalPrivileges(owner) } override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index d0b1296..e3ed21b 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -6,7 +6,7 @@ import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.* import io.dico.parcels2.PlayerProfile.Star.name import io.dico.parcels2.storage.* -import io.dico.parcels2.util.ext.clampMax +import io.dico.parcels2.util.math.ext.clampMax import io.dico.parcels2.util.ext.synchronized import kotlinx.coroutines.* import kotlinx.coroutines.channels.ArrayChannel @@ -193,6 +193,10 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi PrivilegesLocalT.setPrivilege(parcel, profile, privilege) } + data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege -> + PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege) + } + setParcelOptionsInteractConfig(parcel, data.interactableConfig) } @@ -242,13 +246,13 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi } } - override fun transmitAllGlobalAddedData(channel: SendChannel>) { - PrivilegesGlobalT.sendAllAddedData(channel) + override fun transmitAllGlobalPrivileges(channel: SendChannel>) { + PrivilegesGlobalT.sendAllPrivilegesH(channel) channel.close() } - override fun readGlobalPrivileges(owner: PlayerProfile): MutablePrivilegeMap { - return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf()) + override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? { + return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null) } override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) { @@ -267,7 +271,10 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size)) } - privilegeMap = PrivilegesLocalT.readPrivileges(id) + val privileges = PrivilegesLocalT.readPrivileges(id) + if (privileges != null) { + copyPrivilegesFrom(privileges) + } } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index d22316a..6a0a41b 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -15,7 +15,7 @@ object ParcelOptionsT : Table("parcel_options") { val interact_bitmask = binary("interact_bitmask", 4) } -typealias PrivilegesSendChannel = SendChannel> +typealias PrivilegesSendChannel = SendChannel> sealed class PrivilegesTable(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) @@ -43,32 +43,32 @@ sealed class PrivilegesTable(name: String, val idTable: IdTransactionsT } } - fun readPrivileges(id: Int): MutablePrivilegeMap { + fun readPrivileges(id: Int): PrivilegesHolder? { val list = slice(profile_id, privilege).select { attach_id eq id } - val result = MutablePrivilegeMap() + val result = PrivilegesHolder() for (row in list) { val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue - result[profile] = Privilege.getByNumber(row[privilege]) ?: continue + result.setRawStoredPrivilege(profile, Privilege.getByNumber(row[privilege]) ?: continue) } return result } - fun sendAllAddedData(channel: PrivilegesSendChannel) { + fun sendAllPrivilegesH(channel: PrivilegesSendChannel) { val iterator = selectAll().orderBy(attach_id).iterator() if (iterator.hasNext()) { val firstRow = iterator.next() var id: Int = firstRow[attach_id] var attach: AttachT? = null - var map: MutablePrivilegeMap? = null + var map: PrivilegesHolder? = null fun initAttachAndMap() { attach = idTable.getItem(id) - map = attach?.let { mutableMapOf() } + map = attach?.let { PrivilegesHolder() } } fun sendIfPresent() { - if (attach != null && map != null && map!!.isNotEmpty()) { + if (attach != null && map != null) { channel.offer(attach!! to map!!) } attach = null @@ -91,7 +91,7 @@ sealed class PrivilegesTable(name: String, val idTable: IdTransactionsT val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue val privilege = Privilege.getByNumber(row[privilege]) ?: continue - map!![profile] = privilege + map!!.setRawStoredPrivilege(profile, privilege) } sendIfPresent() diff --git a/src/main/kotlin/io/dico/parcels2/util/Region.kt b/src/main/kotlin/io/dico/parcels2/util/Region.kt deleted file mode 100644 index 1801b4e..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/Region.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.dico.parcels2.util - -data class Region(val origin: Vec3i, val size: Vec3i) { - val blockCount: Int get() = size.x * size.y * size.z - - val center: Vec3d - get() { - val x = (origin.x + size.x) / 2.0 - val y = (origin.y + size.y) / 2.0 - val z = (origin.z + size.z) / 2.0 - return Vec3d(x, y, z) - } - - fun withSize(size: Vec3i): Region { - if (size == this.size) return this - return Region(origin, size) - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt deleted file mode 100644 index 62ac97f..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.dico.parcels2.util - -data class Vec2i( - val x: Int, - val z: Int -) - -data class Region2i( - val bottom: Vec2i, - val top: Vec2i -) \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt deleted file mode 100644 index 8608e13..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt +++ /dev/null @@ -1,96 +0,0 @@ -package io.dico.parcels2.util - -import io.dico.parcels2.util.ext.clampMax -import org.bukkit.World -import org.bukkit.block.Block -import org.bukkit.block.BlockFace - -data class Vec3d( - val x: Double, - val y: Double, - val z: Double -) { - operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z) - operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z) - infix fun addX(o: Double) = Vec3d(x + o, y, z) - infix fun addY(o: Double) = Vec3d(x, y + o, z) - infix fun addZ(o: Double) = Vec3d(x, y, z + o) - infix fun withX(o: Double) = Vec3d(o, y, z) - infix fun withY(o: Double) = Vec3d(x, o, z) - infix fun withZ(o: Double) = Vec3d(x, y, o) - fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz) -} - -data class Vec3i( - val x: Int, - val y: Int, - 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) - infix fun withX(o: Int) = Vec3i(o, y, z) - infix fun withY(o: Int) = Vec3i(x, o, z) - infix fun withZ(o: Int) = Vec3i(x, y, o) - fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz) - fun neg() = Vec3i(-x, -y, -z) - fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z)) - - companion object { - private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ) - val down = Vec3i(BlockFace.DOWN) - val up = Vec3i(BlockFace.UP) - val north = Vec3i(BlockFace.NORTH) - val east = Vec3i(BlockFace.EAST) - val south = Vec3i(BlockFace.SOUTH) - val west = Vec3i(BlockFace.WEST) - - fun convert(face: BlockFace) = when (face) { - BlockFace.DOWN -> down - BlockFace.UP -> up - BlockFace.NORTH -> north - BlockFace.EAST -> east - BlockFace.SOUTH -> south - BlockFace.WEST -> west - else -> Vec3i(face) - } - } -} - -@Suppress("NOTHING_TO_INLINE") -inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) - -/* -private /*inline */class IVec3i(private val data: Long) { - - private companion object { - const val mask = 0x001F_FFFF - const val max: Int = 0x000F_FFFF // +1048575 - const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000 - - @Suppress("NOTHING_TO_INLINE") - inline fun Int.compressIntoLong(offset: Int): Long { - if (this !in min..max) throw IllegalArgumentException() - return and(mask).toLong().shl(offset) - } - - @Suppress("NOTHING_TO_INLINE") - inline fun Long.extractInt(offset: Int): Int { - val result = ushr(offset).toInt().and(mask) - return if (result > max) result or mask.inv() else result - } - } - - constructor(x: Int, y: Int, z: Int) : this( - x.compressIntoLong(42) - or y.compressIntoLong(21) - or z.compressIntoLong(0)) - - val x: Int get() = data.extractInt(42) - val y: Int get() = data.extractInt(21) - val z: Int get() = data.extractInt(0) - -} -*/ diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt deleted file mode 100644 index a123bcf..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.dico.parcels2.util.ext - -fun Double.floor(): Int { - val down = toInt() - if (down.toDouble() != this && (java.lang.Double.doubleToRawLongBits(this).ushr(63).toInt()) == 1) { - return down - 1 - } - return down -} - -infix fun Int.umod(divisor: Int): Int { - val out = this % divisor - if (out < 0) { - return out + divisor - } - return out -} - -val Int.even: Boolean get() = and(1) == 0 - -fun IntRange.clamp(min: Int, max: Int): IntRange { - if (first < min) { - if (last > max) { - return IntRange(min, max) - } - return IntRange(min, last) - } - if (last > max) { - return IntRange(first, max) - } - return this -} - -// 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 { - return -Math.floorDiv(-this, divisor) -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt index 4df4d52..a826687 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt @@ -77,3 +77,8 @@ class EditLoopScope(val _map: MutableMap) { operator fun Formatting.plus(other: Formatting) = toString() + other operator fun Formatting.plus(other: String) = toString() + other + +inline fun Pair.forEach(block: (T) -> Unit) { + block(first) + block(second) +} diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt b/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt new file mode 100644 index 0000000..cf67148 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt @@ -0,0 +1,19 @@ +package io.dico.parcels2.util.math + +enum class Dimension { + X, + Y, + Z; + + val otherDimensions + get() = when (this) { + X -> Y to Z + Y -> X to Z + Z -> X to Y + } + + companion object { + private val values = values() + operator fun get(ordinal: Int) = values[ordinal] + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Region.kt b/src/main/kotlin/io/dico/parcels2/util/math/Region.kt new file mode 100644 index 0000000..cdbd497 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/math/Region.kt @@ -0,0 +1,37 @@ +package io.dico.parcels2.util.math + +data class Region(val origin: Vec3i, val size: Vec3i) { + val blockCount: Int get() = size.x * size.y * size.z + + val center: Vec3d + get() { + val x = (origin.x + size.x) / 2.0 + val y = (origin.y + size.y) / 2.0 + val z = (origin.z + size.z) / 2.0 + return Vec3d(x, y, z) + } + + val end: Vec3i + get() = origin + size + + val max: Vec3i + get() = Vec3i(origin.x + size.x - 1, origin.y + size.y - 1, origin.z + size.z - 1) + + fun withSize(size: Vec3i): Region { + if (size == this.size) return this + return Region(origin, size) + } + + operator fun contains(loc: Vec3i): Boolean = getFirstUncontainedDimensionOf(loc) == null + + fun getFirstUncontainedDimensionOf(loc: Vec3i): Dimension? { + val max = max + return when { + loc.x !in origin.x..max.x -> Dimension.X + loc.z !in origin.z..max.z -> Dimension.Z + loc.y !in origin.y..max.y -> Dimension.Y + else -> null + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt new file mode 100644 index 0000000..9385535 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt @@ -0,0 +1,6 @@ +package io.dico.parcels2.util.math + +data class Vec2i( + val x: Int, + val z: Int +) diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt new file mode 100644 index 0000000..45e6216 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt @@ -0,0 +1,54 @@ +package io.dico.parcels2.util.math + +import io.dico.parcels2.util.math.ext.floor +import org.bukkit.Location +import kotlin.math.sqrt + +data class Vec3d( + val x: Double, + val y: Double, + val z: Double +) { + constructor(loc: Location) : this(loc.x, loc.y, loc.z) + + operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z) + operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z) + infix fun addX(o: Double) = Vec3d(x + o, y, z) + infix fun addY(o: Double) = Vec3d(x, y + o, z) + infix fun addZ(o: Double) = Vec3d(x, y, z + o) + infix fun withX(o: Double) = Vec3d(o, y, z) + infix fun withY(o: Double) = Vec3d(x, o, z) + infix fun withZ(o: Double) = Vec3d(x, y, o) + fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz) + fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor()) + + fun distanceSquared(o: Vec3d): Double { + val dx = o.x - x + val dy = o.y - y + val dz = o.z - z + return dx * dx + dy * dy + dz * dz + } + + fun distance(o: Vec3d) = sqrt(distanceSquared(o)) + + operator fun get(dimension: Dimension) = + when (dimension) { + Dimension.X -> x + Dimension.Y -> y + Dimension.Z -> z + } + + fun with(dimension: Dimension, value: Double) = + when (dimension) { + Dimension.X -> withX(value) + Dimension.Y -> withY(value) + Dimension.Z -> withZ(value) + } + + fun add(dimension: Dimension, value: Double) = + when (dimension) { + Dimension.X -> addX(value) + Dimension.Y -> addY(value) + Dimension.Z -> addZ(value) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt new file mode 100644 index 0000000..418fd0f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt @@ -0,0 +1,104 @@ +package io.dico.parcels2.util.math + +import io.dico.parcels2.util.math.ext.clampMax +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Block +import org.bukkit.block.BlockFace + +data class Vec3i( + val x: Int, + val y: Int, + val z: Int +) { + constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ) + + 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) + infix fun withX(o: Int) = Vec3i(o, y, z) + infix fun withY(o: Int) = Vec3i(x, o, z) + infix fun withZ(o: Int) = Vec3i(x, y, o) + fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz) + fun neg() = Vec3i(-x, -y, -z) + fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z)) + + operator fun get(dimension: Dimension) = + when (dimension) { + Dimension.X -> x + Dimension.Y -> y + Dimension.Z -> z + } + + fun with(dimension: Dimension, value: Int) = + when (dimension) { + Dimension.X -> withX(value) + Dimension.Y -> withY(value) + Dimension.Z -> withZ(value) + } + + fun add(dimension: Dimension, value: Int) = + when (dimension) { + Dimension.X -> addX(value) + Dimension.Y -> addY(value) + Dimension.Z -> addZ(value) + } + + companion object { + private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ) + val down = Vec3i(BlockFace.DOWN) + val up = Vec3i(BlockFace.UP) + val north = Vec3i(BlockFace.NORTH) + val east = Vec3i(BlockFace.EAST) + val south = Vec3i(BlockFace.SOUTH) + val west = Vec3i(BlockFace.WEST) + + fun convert(face: BlockFace) = when (face) { + BlockFace.DOWN -> down + BlockFace.UP -> up + BlockFace.NORTH -> north + BlockFace.EAST -> east + BlockFace.SOUTH -> south + BlockFace.WEST -> west + else -> Vec3i(face) + } + } +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) + +/* +private /*inline */class IVec3i(private val data: Long) { + + private companion object { + const val mask = 0x001F_FFFF + const val max: Int = 0x000F_FFFF // +1048575 + const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000 + + @Suppress("NOTHING_TO_INLINE") + inline fun Int.compressIntoLong(offset: Int): Long { + if (this !in min..max) throw IllegalArgumentException() + return and(mask).toLong().shl(offset) + } + + @Suppress("NOTHING_TO_INLINE") + inline fun Long.extractInt(offset: Int): Int { + val result = ushr(offset).toInt().and(mask) + return if (result > max) result or mask.inv() else result + } + } + + constructor(x: Int, y: Int, z: Int) : this( + x.compressIntoLong(42) + or y.compressIntoLong(21) + or z.compressIntoLong(0)) + + val x: Int get() = data.extractInt(42) + val y: Int get() = data.extractInt(21) + val z: Int get() = data.extractInt(0) + +} +*/ diff --git a/src/main/kotlin/io/dico/parcels2/util/math/ext/Math.kt b/src/main/kotlin/io/dico/parcels2/util/math/ext/Math.kt new file mode 100644 index 0000000..1211894 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/math/ext/Math.kt @@ -0,0 +1,42 @@ +package io.dico.parcels2.util.math.ext + +fun Double.floor(): Int { + val down = toInt() + if (down.toDouble() != this && (java.lang.Double.doubleToRawLongBits(this).ushr(63).toInt()) == 1) { + return down - 1 + } + return down +} + +infix fun Int.umod(divisor: Int): Int { + val out = this % divisor + if (out < 0) { + return out + divisor + } + return out +} + +val Int.even: Boolean get() = and(1) == 0 + +fun IntRange.clamp(min: Int, max: Int): IntRange { + if (first < min) { + if (last > max) { + return IntRange(min, max) + } + return IntRange(min, last) + } + if (last > max) { + return IntRange(first, max) + } + return this +} + +// the name coerceAtMost is bad +fun Int.clampMax(max: Int) = coerceAtMost(max) +fun Double.clampMin(min: Double) = coerceAtLeast(min) +fun Double.clampMax(max: Double) = coerceAtMost(max) + +// Why does this not exist? +infix fun Int.ceilDiv(divisor: Int): Int { + return -Math.floorDiv(-this, divisor) +} \ No newline at end of file -- cgit v1.2.3