Add WorktimeLimiter API, basic /parcel clear functionality
This commit is contained in:
@@ -2,7 +2,7 @@ package io.dico.parcels2
|
|||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import io.dico.parcels2.blockvisitor.BlockVisitorOptions
|
import io.dico.parcels2.blockvisitor.TickWorktimeOptions
|
||||||
import io.dico.parcels2.storage.Storage
|
import io.dico.parcels2.storage.Storage
|
||||||
import io.dico.parcels2.storage.StorageFactory
|
import io.dico.parcels2.storage.StorageFactory
|
||||||
import io.dico.parcels2.storage.yamlObjectMapper
|
import io.dico.parcels2.storage.yamlObjectMapper
|
||||||
@@ -19,6 +19,7 @@ class Options {
|
|||||||
var worlds: Map<String, WorldOptions> = HashMap()
|
var worlds: Map<String, WorldOptions> = HashMap()
|
||||||
private set
|
private set
|
||||||
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
|
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
|
||||||
|
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(30, 1)
|
||||||
|
|
||||||
fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options)
|
fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options)
|
||||||
|
|
||||||
@@ -40,8 +41,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
|
|||||||
var blockMobSpawning: Boolean = true,
|
var blockMobSpawning: Boolean = true,
|
||||||
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
|
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
|
||||||
var axisLimit: Int = 10,
|
var axisLimit: Int = 10,
|
||||||
var generator: GeneratorOptions = DefaultGeneratorOptions(),
|
var generator: GeneratorOptions = DefaultGeneratorOptions()) {
|
||||||
var blockVisitor: BlockVisitorOptions = BlockVisitorOptions()) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,8 @@ class StorageOptions(val dialect: String,
|
|||||||
val options: Any) {
|
val options: Any) {
|
||||||
|
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
val factory = StorageFactory.getFactory(dialect) ?: throw IllegalArgumentException("Invalid storage dialect: $dialect")
|
val factory = StorageFactory.getFactory(dialect)
|
||||||
|
?: throw IllegalArgumentException("Invalid storage dialect: $dialect")
|
||||||
|
|
||||||
fun newStorageInstance(): Storage = factory.newStorageInstance(dialect, options)
|
fun newStorageInstance(): Storage = factory.newStorageInstance(dialect, options)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import kotlin.coroutines.experimental.buildSequence
|
|||||||
import kotlin.reflect.jvm.javaMethod
|
import kotlin.reflect.jvm.javaMethod
|
||||||
import kotlin.reflect.jvm.kotlinFunction
|
import kotlin.reflect.jvm.kotlinFunction
|
||||||
|
|
||||||
class Worlds(private val plugin: ParcelsPlugin) {
|
class Worlds(val plugin: ParcelsPlugin) {
|
||||||
val worlds: Map<String, ParcelWorld> get() = _worlds
|
val worlds: Map<String, ParcelWorld> get() = _worlds
|
||||||
private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
|
private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package io.dico.parcels2
|
|||||||
import io.dico.dicore.Registrator
|
import io.dico.dicore.Registrator
|
||||||
import io.dico.dicore.command.EOverridePolicy
|
import io.dico.dicore.command.EOverridePolicy
|
||||||
import io.dico.dicore.command.ICommandDispatcher
|
import io.dico.dicore.command.ICommandDispatcher
|
||||||
|
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
|
||||||
|
import io.dico.parcels2.blockvisitor.WorktimeLimiter
|
||||||
import io.dico.parcels2.command.getParcelCommands
|
import io.dico.parcels2.command.getParcelCommands
|
||||||
import io.dico.parcels2.listener.ParcelEntityTracker
|
import io.dico.parcels2.listener.ParcelEntityTracker
|
||||||
import io.dico.parcels2.listener.ParcelListeners
|
import io.dico.parcels2.listener.ParcelListeners
|
||||||
@@ -29,8 +31,7 @@ class ParcelsPlugin : JavaPlugin() {
|
|||||||
lateinit var entityTracker: ParcelEntityTracker; private set
|
lateinit var entityTracker: ParcelEntityTracker; private set
|
||||||
private var listeners: ParcelListeners? = null
|
private var listeners: ParcelListeners? = null
|
||||||
private var cmdDispatcher: ICommandDispatcher? = null
|
private var cmdDispatcher: ICommandDispatcher? = null
|
||||||
|
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options) }
|
||||||
val mainThreadDispatcher = Executor { server.scheduler.runTask(this, it) }.asCoroutineDispatcher()
|
|
||||||
|
|
||||||
override fun onEnable() {
|
override fun onEnable() {
|
||||||
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
|
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package io.dico.parcels2
|
package io.dico.parcels2
|
||||||
|
|
||||||
import io.dico.parcels2.util.Vec2i
|
import io.dico.parcels2.blockvisitor.JobData
|
||||||
import io.dico.parcels2.util.clamp
|
import io.dico.parcels2.blockvisitor.RegionTraversal
|
||||||
import io.dico.parcels2.util.even
|
import io.dico.parcels2.util.*
|
||||||
import io.dico.parcels2.util.umod
|
|
||||||
import org.bukkit.*
|
import org.bukkit.*
|
||||||
import org.bukkit.Bukkit.createBlockData
|
import org.bukkit.Bukkit.createBlockData
|
||||||
import org.bukkit.block.Biome
|
import org.bukkit.block.Biome
|
||||||
@@ -51,6 +50,8 @@ abstract class ParcelGenerator : ChunkGenerator(), ParcelProvider {
|
|||||||
|
|
||||||
abstract fun getBlocks(parcel: Parcel, yRange: IntRange = 0..255): Iterator<Block>
|
abstract fun getBlocks(parcel: Parcel, yRange: IntRange = 0..255): Iterator<Block>
|
||||||
|
|
||||||
|
abstract fun clearParcel(parcel: Parcel): JobData
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GeneratorFactory {
|
interface GeneratorFactory {
|
||||||
@@ -78,6 +79,9 @@ interface GeneratorFactory {
|
|||||||
class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
|
class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
|
||||||
override val world: ParcelWorld by lazy { worlds.getWorld(name)!! }
|
override val world: ParcelWorld by lazy { worlds.getWorld(name)!! }
|
||||||
override val factory = Factory
|
override val factory = Factory
|
||||||
|
val worktimeLimiter = worlds.plugin.worktimeLimiter
|
||||||
|
val maxHeight by lazy { world.world.maxHeight }
|
||||||
|
val airType = worlds.plugin.server.createBlockData(Material.AIR)
|
||||||
|
|
||||||
companion object Factory : GeneratorFactory {
|
companion object Factory : GeneratorFactory {
|
||||||
override val name get() = "default"
|
override val name get() = "default"
|
||||||
@@ -260,4 +264,28 @@ class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearParcel(parcel: Parcel) = worktimeLimiter.submit {
|
||||||
|
val bottom = getBottomCoord(parcel)
|
||||||
|
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
|
||||||
|
val blocks = RegionTraversal.XZY.regionTraverser(region)
|
||||||
|
val blockCount = region.blockCount.toDouble()
|
||||||
|
|
||||||
|
val world = world.world
|
||||||
|
val floorHeight = o.floorHeight
|
||||||
|
val airType = airType; val floorType = o.floorType; val fillType = o.fillType
|
||||||
|
|
||||||
|
for ((index, vec) in blocks.withIndex()) {
|
||||||
|
markSuspensionPoint()
|
||||||
|
val y = vec.y
|
||||||
|
val blockType = when {
|
||||||
|
y > floorHeight -> airType
|
||||||
|
y == floorHeight -> floorType
|
||||||
|
else -> fillType
|
||||||
|
}
|
||||||
|
world[vec].blockData = blockType
|
||||||
|
setProgress((index + 1) / blockCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
67
src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
Normal file
67
src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package io.dico.parcels2.blockvisitor
|
||||||
|
|
||||||
|
import org.bukkit.Material
|
||||||
|
import org.bukkit.Material.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val attachables: Set<Material> = EnumSet.of(
|
||||||
|
ACACIA_DOOR,
|
||||||
|
ACTIVATOR_RAIL,
|
||||||
|
BIRCH_DOOR,
|
||||||
|
BROWN_MUSHROOM,
|
||||||
|
CACTUS,
|
||||||
|
CAKE,
|
||||||
|
WHITE_CARPET, ORANGE_CARPET, MAGENTA_CARPET, LIGHT_BLUE_CARPET, YELLOW_CARPET, LIME_CARPET, PINK_CARPET, GRAY_CARPET, LIGHT_GRAY_CARPET, CYAN_CARPET, PURPLE_CARPET, BLUE_CARPET, BROWN_CARPET, GREEN_CARPET, RED_CARPET, BLACK_CARPET,
|
||||||
|
CARROT,
|
||||||
|
COCOA,
|
||||||
|
WHEAT,
|
||||||
|
DARK_OAK_DOOR,
|
||||||
|
DEAD_BUSH,
|
||||||
|
DETECTOR_RAIL,
|
||||||
|
REPEATER,
|
||||||
|
TALL_GRASS, TALL_SEAGRASS,
|
||||||
|
DRAGON_EGG,
|
||||||
|
FIRE,
|
||||||
|
FLOWER_POT,
|
||||||
|
OAK_PRESSURE_PLATE, BIRCH_PRESSURE_PLATE, SPRUCE_PRESSURE_PLATE, JUNGLE_PRESSURE_PLATE, ACACIA_PRESSURE_PLATE, DARK_OAK_PRESSURE_PLATE,
|
||||||
|
STONE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE,
|
||||||
|
IRON_DOOR,
|
||||||
|
OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
|
||||||
|
OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
|
||||||
|
STONE_BUTTON,
|
||||||
|
OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR,
|
||||||
|
IRON_TRAPDOOR,
|
||||||
|
LADDER,
|
||||||
|
LEVER,
|
||||||
|
MELON_STEM,
|
||||||
|
NETHER_WART,
|
||||||
|
PISTON,
|
||||||
|
STICKY_PISTON,
|
||||||
|
NETHER_PORTAL,
|
||||||
|
POTATO,
|
||||||
|
POWERED_RAIL,
|
||||||
|
PUMPKIN_STEM,
|
||||||
|
RAIL,
|
||||||
|
COMPARATOR,
|
||||||
|
REDSTONE_TORCH,
|
||||||
|
REDSTONE_WIRE,
|
||||||
|
RED_MUSHROOM,
|
||||||
|
SUNFLOWER,
|
||||||
|
FLOWER_POT,
|
||||||
|
CHORUS_FLOWER,
|
||||||
|
OAK_SAPLING, BIRCH_SAPLING, SPRUCE_SAPLING, JUNGLE_SAPLING, ACACIA_SAPLING, DARK_OAK_SAPLING,
|
||||||
|
SIGN,
|
||||||
|
SNOW,
|
||||||
|
SPRUCE_DOOR,
|
||||||
|
STONE_BUTTON,
|
||||||
|
SUGAR_CANE,
|
||||||
|
TORCH,
|
||||||
|
TRIPWIRE,
|
||||||
|
TRIPWIRE_HOOK,
|
||||||
|
VINE,
|
||||||
|
WHITE_BANNER, ORANGE_BANNER, MAGENTA_BANNER, LIGHT_BLUE_BANNER, YELLOW_BANNER, LIME_BANNER, PINK_BANNER, GRAY_BANNER, LIGHT_GRAY_BANNER, CYAN_BANNER, PURPLE_BANNER, BLUE_BANNER, BROWN_BANNER, GREEN_BANNER, RED_BANNER, BLACK_BANNER,
|
||||||
|
WHITE_WALL_BANNER, ORANGE_WALL_BANNER, MAGENTA_WALL_BANNER, LIGHT_BLUE_WALL_BANNER, YELLOW_WALL_BANNER, LIME_WALL_BANNER, PINK_WALL_BANNER, GRAY_WALL_BANNER, LIGHT_GRAY_WALL_BANNER, CYAN_WALL_BANNER, PURPLE_WALL_BANNER, BLUE_WALL_BANNER, BROWN_WALL_BANNER, GREEN_WALL_BANNER, RED_WALL_BANNER, BLACK_WALL_BANNER,
|
||||||
|
WALL_SIGN,
|
||||||
|
LILY_PAD,
|
||||||
|
DANDELION
|
||||||
|
);
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package io.dico.parcels2.blockvisitor
|
|
||||||
|
|
||||||
import io.dico.dicore.task.IteratorTask
|
|
||||||
|
|
||||||
abstract class BlockVisitor<T>(iterator: Iterator<T>?) : IteratorTask<T>(iterator)
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package io.dico.parcels2.blockvisitor
|
|
||||||
|
|
||||||
import io.dico.parcels2.util.MutableVec3i
|
|
||||||
import io.dico.parcels2.util.Region
|
|
||||||
import kotlinx.coroutines.experimental.Deferred
|
|
||||||
import org.bukkit.block.Block
|
|
||||||
import org.bukkit.plugin.Plugin
|
|
||||||
import kotlin.coroutines.experimental.SequenceBuilder
|
|
||||||
import kotlin.coroutines.experimental.buildIterator
|
|
||||||
|
|
||||||
typealias BlockProcessor = (Block) -> Boolean
|
|
||||||
|
|
||||||
class BlockVisitorManager(val plugin: Plugin, var options: BlockVisitorOptions) {
|
|
||||||
|
|
||||||
|
|
||||||
fun doOperationSynchronously(region: Region, processor: BlockProcessor): Deferred<Unit> {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class RegionOperation(val region: Region, val processor: BlockProcessor) {
|
|
||||||
|
|
||||||
fun process(maxMillis: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class RegionTraversal(private val builder: suspend SequenceBuilder<MutableVec3i>.(Region) -> Unit) {
|
|
||||||
XZY({ region ->
|
|
||||||
val origin = region.origin
|
|
||||||
val result = MutableVec3i(origin.x, origin.y, origin.z)
|
|
||||||
|
|
||||||
val size = region.size
|
|
||||||
|
|
||||||
repeat(size.y) { y ->
|
|
||||||
repeat()
|
|
||||||
|
|
||||||
result.y++
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
fun regionTraverser(region: Region) = Iterable { buildIterator { builder(region) } }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package io.dico.parcels2.blockvisitor
|
|
||||||
|
|
||||||
data class BlockVisitorOptions(var pauseTicks: Int = 1, var workMillis: Int = 30)
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package io.dico.parcels2.blockvisitor
|
||||||
|
|
||||||
|
import io.dico.parcels2.util.Region
|
||||||
|
import io.dico.parcels2.util.Vec3i
|
||||||
|
import kotlin.coroutines.experimental.SequenceBuilder
|
||||||
|
import kotlin.coroutines.experimental.buildIterator
|
||||||
|
|
||||||
|
enum class RegionTraversal(private val builder: suspend SequenceBuilder<Vec3i>.(Region) -> Unit) {
|
||||||
|
XZY({ region ->
|
||||||
|
val origin = region.origin
|
||||||
|
val size = region.size
|
||||||
|
|
||||||
|
repeat(size.y) { y ->
|
||||||
|
repeat(size.z) { z ->
|
||||||
|
repeat(size.x) { x ->
|
||||||
|
yield(origin.add(x, y, z))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
fun regionTraverser(region: Region) = Iterable { buildIterator { builder(region) } }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +1,52 @@
|
|||||||
package io.dico.parcels2.blockvisitor
|
package io.dico.parcels2.blockvisitor
|
||||||
|
|
||||||
|
import io.dico.parcels2.util.Region
|
||||||
import io.dico.parcels2.util.Vec3i
|
import io.dico.parcels2.util.Vec3i
|
||||||
|
import io.dico.parcels2.util.get
|
||||||
|
import org.bukkit.World
|
||||||
import org.bukkit.block.data.BlockData
|
import org.bukkit.block.data.BlockData
|
||||||
|
|
||||||
class Schematic(val origin: Vec3i, val size: Vec3i) {
|
class Schematic {
|
||||||
private var data: Array<BlockData>? = null
|
val size: Vec3i get() = _size!!
|
||||||
|
private var _size: Vec3i? = null
|
||||||
|
set(value) {
|
||||||
|
field?.let { throw IllegalStateException() }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _data: Array<BlockData?>? = null
|
||||||
|
//private var extra: Map<Vec3i, (Block) -> Unit>? = null
|
||||||
|
private var isLoaded = false; private set
|
||||||
|
|
||||||
|
fun getLoadTask(world: World, region: Region): TimeLimitedTask = {
|
||||||
|
val size = region.size.also { _size = it }
|
||||||
|
val data = arrayOfNulls<BlockData>(region.blockCount).also { _data = it }
|
||||||
|
//val extra = mutableMapOf<Vec3i, (Block) -> Unit>().also { extra = it }
|
||||||
|
val blocks = RegionTraversal.XZY.regionTraverser(region)
|
||||||
|
|
||||||
|
for ((index, vec) in blocks.withIndex()) {
|
||||||
|
markSuspensionPoint()
|
||||||
|
val block = world[vec]
|
||||||
|
if (block.y > 255) continue
|
||||||
|
val blockData = block.blockData
|
||||||
|
data[index] = blockData
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
|
||||||
|
if (!isLoaded) throw IllegalStateException()
|
||||||
|
val region = Region(position, _size!!)
|
||||||
|
val blocks = RegionTraversal.XZY.regionTraverser(region)
|
||||||
|
val data = _data!!
|
||||||
|
|
||||||
|
for ((index, vec) in blocks.withIndex()) {
|
||||||
|
markSuspensionPoint()
|
||||||
|
val block = world[vec]
|
||||||
|
if (block.y > 255) continue
|
||||||
|
data[index]?.let { block.blockData = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
207
src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
Normal file
207
src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package io.dico.parcels2.blockvisitor
|
||||||
|
|
||||||
|
import io.dico.parcels2.Options
|
||||||
|
import kotlinx.coroutines.experimental.CoroutineStart
|
||||||
|
import kotlinx.coroutines.experimental.Job
|
||||||
|
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.experimental.launch
|
||||||
|
import org.bukkit.plugin.Plugin
|
||||||
|
import org.bukkit.scheduler.BukkitTask
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.ContinuationInterceptor
|
||||||
|
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
|
||||||
|
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineUninterceptedOrReturn
|
||||||
|
|
||||||
|
interface WorktimeLimiter {
|
||||||
|
/**
|
||||||
|
* Submit a task that should be run synchronously, but limited such that it does not stall the server
|
||||||
|
* a bunch
|
||||||
|
*/
|
||||||
|
fun submit(job: TimeLimitedTask): JobData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias TimeLimitedTask = suspend WorktimeLimiter.() -> Unit
|
||||||
|
|
||||||
|
interface JobData {
|
||||||
|
val job: Job?
|
||||||
|
val isComplete: Boolean
|
||||||
|
val progress: Double?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the given [block] whenever the progress 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]
|
||||||
|
*/
|
||||||
|
fun onProgressUpdate(minDelay: Int, minInterval: Int, block: JobUpdateListener): JobData
|
||||||
|
val isUpdateBlockPresent: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the given [block] when this job completes.
|
||||||
|
*/
|
||||||
|
fun onCompleted(block: JobUpdateListener): JobData
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias JobUpdateListener = JobData.(Double) -> Unit
|
||||||
|
|
||||||
|
class JobDataImpl(val task: TimeLimitedTask) : JobData {
|
||||||
|
override var job: Job? = null
|
||||||
|
set(value) {
|
||||||
|
field?.let { throw IllegalStateException() }
|
||||||
|
field = value!!
|
||||||
|
value.invokeOnCompletion { onCompletedBlock?.invoke(this, 1.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var next: Continuation<Unit>? = null
|
||||||
|
|
||||||
|
override var progress: Double? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
doProgressUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doProgressUpdate() {
|
||||||
|
val progressUpdate = progressUpdateBlock ?: return
|
||||||
|
val time = System.currentTimeMillis()
|
||||||
|
if (time > lastUpdateTime + progressUpdateInterval) {
|
||||||
|
progressUpdate(progress!!)
|
||||||
|
lastUpdateTime = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isUpdateBlockPresent get() = progressUpdateBlock != null
|
||||||
|
private var progressUpdateBlock: JobUpdateListener? = null
|
||||||
|
private var progressUpdateInterval: Int = 0
|
||||||
|
private var lastUpdateTime: Long = 0L
|
||||||
|
override fun onProgressUpdate(minDelay: Int, minInterval: Int, block: JobUpdateListener): JobDataImpl {
|
||||||
|
progressUpdateBlock?.let { throw IllegalStateException() }
|
||||||
|
progressUpdateBlock = block
|
||||||
|
progressUpdateInterval = minInterval
|
||||||
|
lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isComplete get() = job?.isCompleted == true
|
||||||
|
private var onCompletedBlock: JobUpdateListener? = null
|
||||||
|
override fun onCompleted(block: JobUpdateListener): JobDataImpl {
|
||||||
|
onCompletedBlock?.let { throw IllegalStateException() }
|
||||||
|
onCompletedBlock = block
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that controls one or more jobs, ensuring that they don't stall the server too much.
|
||||||
|
* The amount of milliseconds that can accumulate each server tick is configurable
|
||||||
|
*/
|
||||||
|
class TickWorktimeLimiter(private val plugin: Plugin, private val optionsRoot: Options) : WorktimeLimiter {
|
||||||
|
// Coroutine dispatcher for jobs
|
||||||
|
private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher()
|
||||||
|
// union of Continuation<Unit> and suspend WorktimeLimited.() -> Unit
|
||||||
|
private var jobs = LinkedList<JobDataImpl>()
|
||||||
|
// The currently registered bukkit scheduler task
|
||||||
|
private var task: BukkitTask? = null
|
||||||
|
// The data associated with the task that is currently being executed
|
||||||
|
private var curJobData: JobDataImpl? = null
|
||||||
|
// Used to keep track of when the current task should end
|
||||||
|
private var curJobEndTime = 0L
|
||||||
|
// Tick work time options
|
||||||
|
private inline val options get() = optionsRoot.tickWorktime
|
||||||
|
|
||||||
|
override fun submit(job: TimeLimitedTask): JobData {
|
||||||
|
val jobData = JobDataImpl(job)
|
||||||
|
jobs.addFirst(jobData)
|
||||||
|
if (task == null) task = plugin.server.scheduler.runTaskTimer(plugin, ::tickJobs, 0, options.tickInterval.toLong())
|
||||||
|
return jobData
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun markSuspensionPoint() {
|
||||||
|
if (System.currentTimeMillis() >= curJobEndTime)
|
||||||
|
suspendCoroutineUninterceptedOrReturn(::scheduleContinuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setProgress(progress: Double) {
|
||||||
|
curJobData!!.progress = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tickJobs() {
|
||||||
|
if (jobs.isEmpty()) return
|
||||||
|
val tickStartTime = System.currentTimeMillis()
|
||||||
|
val jobs = this.jobs; this.jobs = LinkedList()
|
||||||
|
|
||||||
|
var count = jobs.size
|
||||||
|
|
||||||
|
while (!jobs.isEmpty()) {
|
||||||
|
val job = jobs.poll()
|
||||||
|
val time = System.currentTimeMillis()
|
||||||
|
val timeElapsed = time - tickStartTime
|
||||||
|
val timeLeft = options.workTime - timeElapsed
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
this.jobs.addAll(0, jobs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val timePerJob = (timeLeft + count - 1) / count
|
||||||
|
tickJob(job, time + timePerJob)
|
||||||
|
count--
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobs.isEmpty() && this.jobs.isEmpty()) {
|
||||||
|
task?.cancel()
|
||||||
|
task = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun tickJob(job: JobDataImpl, endTime: Long) {
|
||||||
|
curJobData = job
|
||||||
|
curJobEndTime = endTime
|
||||||
|
try {
|
||||||
|
val next = job.next
|
||||||
|
if (next == null) startJob(job)
|
||||||
|
else next.resume(Unit)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
curJobData = null
|
||||||
|
curJobEndTime = 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startJob(job: JobDataImpl) {
|
||||||
|
job.job = launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) { job.task(this@TickWorktimeLimiter) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleContinuation(continuation: Continuation<Unit>): Any? {
|
||||||
|
curJobData!!.next = continuation
|
||||||
|
jobs.addLast(curJobData)
|
||||||
|
return COROUTINE_SUSPENDED
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TickWorktimeOptions(var workTime: Int, var tickInterval: Int)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
package io.dico.parcels2.command
|
package io.dico.parcels2.command
|
||||||
|
|
||||||
|
import io.dico.dicore.command.ExecutionContext
|
||||||
import io.dico.dicore.command.annotation.Cmd
|
import io.dico.dicore.command.annotation.Cmd
|
||||||
import io.dico.dicore.command.annotation.Desc
|
import io.dico.dicore.command.annotation.Desc
|
||||||
import io.dico.dicore.command.annotation.RequireParameters
|
import io.dico.dicore.command.annotation.RequireParameters
|
||||||
import io.dico.parcels2.ParcelOwner
|
import io.dico.parcels2.ParcelOwner
|
||||||
import io.dico.parcels2.ParcelsPlugin
|
import io.dico.parcels2.ParcelsPlugin
|
||||||
|
import io.dico.parcels2.blockvisitor.JobUpdateListener
|
||||||
import io.dico.parcels2.command.NamedParcelDefaultValue.FIRST_OWNED
|
import io.dico.parcels2.command.NamedParcelDefaultValue.FIRST_OWNED
|
||||||
import io.dico.parcels2.storage.getParcelBySerializedValue
|
import io.dico.parcels2.storage.getParcelBySerializedValue
|
||||||
import io.dico.parcels2.util.hasAdminManage
|
import io.dico.parcels2.util.hasAdminManage
|
||||||
import io.dico.parcels2.util.hasParcelHomeOthers
|
import io.dico.parcels2.util.hasParcelHomeOthers
|
||||||
import io.dico.parcels2.util.uuid
|
import io.dico.parcels2.util.uuid
|
||||||
|
import kotlinx.coroutines.experimental.Job
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
//@Suppress("unused")
|
//@Suppress("unused")
|
||||||
@@ -77,5 +80,13 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
|||||||
return "Enjoy your new parcel!"
|
return "Enjoy your new parcel!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Cmd("clear")
|
||||||
|
@ParcelRequire(owner = true)
|
||||||
|
fun ParcelScope.cmdClear(player: Player, context: ExecutionContext) {
|
||||||
|
val onProgressUpdate: JobUpdateListener = { progress -> context.sendMessage("[Clearing] Progress: %.06f%%".format(progress * 100)) }
|
||||||
|
world.generator.clearParcel(parcel)
|
||||||
|
.onProgressUpdate(1000, 1500, onProgressUpdate)
|
||||||
|
.onCompleted(onProgressUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,30 +5,9 @@ import org.bukkit.Material.*
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
colors:
|
colors:
|
||||||
WHITE_$,
|
WHITE_$, ORANGE_$, MAGENTA_$, LIGHT_BLUE_$, YELLOW_$, LIME_$, PINK_$, GRAY_$, LIGHT_GRAY_$, CYAN_$, PURPLE_$, BLUE_$, BROWN_$, GREEN_$, RED_$, BLACK_$,
|
||||||
ORANGE_$,
|
|
||||||
MAGENTA_$,
|
|
||||||
LIGHT_BLUE_$,
|
|
||||||
YELLOW_$,
|
|
||||||
LIME_$,
|
|
||||||
PINK_$,
|
|
||||||
GRAY_$,
|
|
||||||
LIGHT_GRAY_$,
|
|
||||||
CYAN_$,
|
|
||||||
PURPLE_$,
|
|
||||||
BLUE_$,
|
|
||||||
BROWN_$,
|
|
||||||
GREEN_$,
|
|
||||||
RED_$,
|
|
||||||
BLACK_$,
|
|
||||||
|
|
||||||
wood:
|
wood:
|
||||||
OAK_$,
|
OAK_$, BIRCH_$, SPRUCE_$, JUNGLE_$, ACACIA_$, DARK_OAK_$,
|
||||||
BIRCH_$,
|
|
||||||
SPRUCE_$,
|
|
||||||
JUNGLE_$,
|
|
||||||
ACACIA_$,
|
|
||||||
DARK_OAK_$,
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val Material.isBed get() = when(this) {
|
val Material.isBed get() = when(this) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
package io.dico.parcels2.util
|
package io.dico.parcels2.util
|
||||||
|
|
||||||
data class Region(val origin: Vec3i, val size: Vec3i)
|
data class Region(val origin: Vec3i, val size: Vec3i) {
|
||||||
|
val blockCount: Int get() = size.x * size.y * size.z
|
||||||
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
package io.dico.parcels2.util
|
package io.dico.parcels2.util
|
||||||
|
|
||||||
|
import org.bukkit.World
|
||||||
|
import org.bukkit.block.Block
|
||||||
|
|
||||||
data class Vec3i(
|
data class Vec3i(
|
||||||
val x: Int,
|
val x: Int,
|
||||||
val y: Int,
|
val y: Int,
|
||||||
val z: Int
|
val z: Int
|
||||||
)
|
) {
|
||||||
|
operator fun plus(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)
|
||||||
|
fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
|
||||||
|
}
|
||||||
|
|
||||||
data class MutableVec3i(
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
var x: Int,
|
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
|
||||||
var y: Int,
|
|
||||||
var z: Int
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user