Version 1.3.0 (#85)
* Feature/async chunkmaster (#81) * Change generation to be asynchronous At this stage it will most likely crash the server at a certain point. Pausing and resuming isn't stable. Saving the progress isn't stable as well. Chunks are being unloaded in the main thread by an unloader class. * Switch to native threads - Use thread instead of async tasks - Store pending paper chunks in the database - Interrupt the thread when it should be stopped * Fix insertion of pending chunks Fix an error that is thrown when the sql for inserting pending chunks doesn't have any chunks to insert. * Add task states Add states to differentiate between generating and validating tasks as well as a field in the database to store this information. A task will first generate a world until the required radius or the worldborder is reached. Then it validates that each chunk has been generated. * Add object representation of world_properties table * Add DAO for pending_chunks table * Add DAO for generation_tasks table * Add state updating to periodic save * Fix loading of world properties * Add states to tasks and fix completion handling * Fix progress report and spiral shape * Modify the paper generation task so it works with spigot This change is being made because normal chunk generation doesn't allow chunks to be requested from a different thread. With PaperLib this issue can be solved. * Add workarounds for spigot problems * Fix some blocking issues and update README * Add locking to ChunkUnloader class * Add total chunk count to list command (closes #79) (#82) * Fix shape beign stuck (#83) * Add autostart config parameter (closes #78) (#84) * Add circleci badge to readme * Add codefactor badgepull/86/head v1.3.0
parent
400295ac73
commit
4d4107aaf3
@ -0,0 +1,14 @@
|
||||
package net.trivernis.chunkmaster.lib.database
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
|
||||
import net.trivernis.chunkmaster.lib.generation.TaskState
|
||||
|
||||
data class GenerationTaskData(
|
||||
val id: Int,
|
||||
val world: String,
|
||||
val radius: Int,
|
||||
val shape: String,
|
||||
val state: TaskState,
|
||||
val center: ChunkCoordinates,
|
||||
val last: ChunkCoordinates
|
||||
)
|
@ -0,0 +1,103 @@
|
||||
package net.trivernis.chunkmaster.lib.database
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
|
||||
import net.trivernis.chunkmaster.lib.generation.TaskState
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class GenerationTasks(private val sqliteManager: SqliteManager) {
|
||||
/**
|
||||
* Returns all stored generation tasks
|
||||
*/
|
||||
fun getGenerationTasks(): CompletableFuture<List<GenerationTaskData>> {
|
||||
val completableFuture = CompletableFuture<List<GenerationTaskData>>()
|
||||
|
||||
sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res ->
|
||||
val tasks = ArrayList<GenerationTaskData>()
|
||||
|
||||
while (res!!.next()) {
|
||||
val id = res.getInt("id")
|
||||
val world = res.getString("world")
|
||||
val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z"))
|
||||
val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z"))
|
||||
val radius = res.getInt("radius")
|
||||
val shape = res.getString("shape")
|
||||
val state = stringToState(res.getString("state"))
|
||||
val taskData = GenerationTaskData(id, world, radius, shape, state, center, last)
|
||||
if (tasks.find { it.id == id } == null) {
|
||||
tasks.add(taskData)
|
||||
}
|
||||
}
|
||||
completableFuture.complete(tasks)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a generation task to the database
|
||||
*/
|
||||
fun addGenerationTask(world: String, center: ChunkCoordinates, radius: Int, shape: String): CompletableFuture<Int> {
|
||||
val completableFuture = CompletableFuture<Int>()
|
||||
sqliteManager.executeStatement("""
|
||||
INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, radius, shape)
|
||||
values (?, ?, ?, ?, ?, ?, ?)""",
|
||||
hashMapOf(
|
||||
1 to center.x,
|
||||
2 to center.z,
|
||||
3 to center.x,
|
||||
4 to center.z,
|
||||
5 to world,
|
||||
6 to radius,
|
||||
7 to shape
|
||||
)
|
||||
) {
|
||||
sqliteManager.executeStatement(
|
||||
"""
|
||||
SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1
|
||||
""".trimIndent(), HashMap()
|
||||
) {
|
||||
it!!.next()
|
||||
completableFuture.complete(it.getInt("id"))
|
||||
}
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a generationTask from the database
|
||||
*/
|
||||
fun deleteGenerationTask(id: Int): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
sqliteManager.executeStatement("DELETE FROM generation_tasks WHERE id = ?;", hashMapOf(1 to id)) {
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
fun updateGenerationTask(id: Int, last: ChunkCoordinates, state: TaskState): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
sqliteManager.executeStatement(
|
||||
"""
|
||||
UPDATE generation_tasks SET last_x = ?, last_z = ?, state = ?
|
||||
WHERE id = ?
|
||||
""".trimIndent(),
|
||||
hashMapOf(1 to last.x, 2 to last.z, 3 to state.toString(), 4 to id)
|
||||
) {
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string into a task state
|
||||
*/
|
||||
private fun stringToState(stringState: String): TaskState {
|
||||
TaskState.valueOf(stringState)
|
||||
return when (stringState) {
|
||||
"GENERATING" -> TaskState.GENERATING
|
||||
"VALIDATING" -> TaskState.VALIDATING
|
||||
"PAUSING" -> TaskState.PAUSING
|
||||
"CORRECTING" -> TaskState.CORRECTING
|
||||
else -> TaskState.GENERATING
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package net.trivernis.chunkmaster.lib.database
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class PendingChunks(private val sqliteManager: SqliteManager) {
|
||||
/**
|
||||
* Returns a list of pending chunks for a taskId
|
||||
*/
|
||||
fun getPendingChunks(taskId: Int): CompletableFuture<List<ChunkCoordinates>> {
|
||||
val completableFuture = CompletableFuture<List<ChunkCoordinates>>()
|
||||
sqliteManager.executeStatement("SELECT * FROM pending_chunks WHERE task_id = ?", hashMapOf(1 to taskId)) {
|
||||
val pendingChunks = ArrayList<ChunkCoordinates>()
|
||||
while (it!!.next()) {
|
||||
pendingChunks.add(ChunkCoordinates(it.getInt("chunk_x"), it.getInt("chunk_z")))
|
||||
}
|
||||
completableFuture.complete(pendingChunks)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all pending chunks of a task
|
||||
*/
|
||||
fun clearPendingChunks(taskId: Int): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
sqliteManager.executeStatement("DELETE FROM pending_chunks WHERE task_id = ?", hashMapOf(1 to taskId)) {
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds pending chunks for a taskid
|
||||
*/
|
||||
fun addPendingChunks(taskId: Int, pendingChunks: List<ChunkCoordinates>): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
if (pendingChunks.isEmpty()) {
|
||||
completableFuture.complete(null)
|
||||
} else {
|
||||
var sql = "INSERT INTO pending_chunks (task_id, chunk_x, chunk_z) VALUES"
|
||||
var index = 1
|
||||
val valueMap = HashMap<Int, Any>()
|
||||
|
||||
for (coordinates in pendingChunks) {
|
||||
sql += "(?, ?, ?),"
|
||||
valueMap[index++] = taskId
|
||||
valueMap[index++] = coordinates.x
|
||||
valueMap[index++] = coordinates.z
|
||||
}
|
||||
sqliteManager.executeStatement(sql.removeSuffix(","), valueMap) {
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package net.trivernis.chunkmaster.lib.database
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class WorldProperties(private val sqliteManager: SqliteManager) {
|
||||
|
||||
private val properties = HashMap<String, Pair<Int, Int>>()
|
||||
|
||||
/**
|
||||
* Returns the world center for one world
|
||||
*/
|
||||
fun getWorldCenter(worldName: String): CompletableFuture<Pair<Int, Int>?> {
|
||||
val completableFuture = CompletableFuture<Pair<Int, Int>?>()
|
||||
|
||||
if (properties[worldName] != null) {
|
||||
completableFuture.complete(properties[worldName])
|
||||
} else {
|
||||
sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", hashMapOf(1 to worldName)) {
|
||||
if (it != null && it.next()) {
|
||||
completableFuture.complete(Pair(it.getInt("center_x"), it.getInt("center_z")))
|
||||
} else {
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the center of a world
|
||||
*/
|
||||
fun setWorldCenter(worldName: String, center: Pair<Int, Int>): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
|
||||
getWorldCenter(worldName).thenAccept {
|
||||
if (it != null) {
|
||||
updateWorldProperties(worldName, center).thenAccept {completableFuture.complete(null) }
|
||||
} else {
|
||||
insertWorldProperties(worldName, center).thenAccept { completableFuture.complete(null) }
|
||||
}
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entry in the world properties
|
||||
*/
|
||||
private fun updateWorldProperties(worldName: String, center: Pair<Int, Int>): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?",
|
||||
hashMapOf(
|
||||
1 to center.first,
|
||||
2 to center.second,
|
||||
3 to worldName
|
||||
)
|
||||
) {
|
||||
properties[worldName] = center
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts into the world properties
|
||||
*/
|
||||
private fun insertWorldProperties(worldName: String, center: Pair<Int, Int>): CompletableFuture<Void> {
|
||||
val completableFuture = CompletableFuture<Void>()
|
||||
sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)",
|
||||
hashMapOf(
|
||||
1 to worldName,
|
||||
2 to center.first,
|
||||
3 to center.second
|
||||
)
|
||||
) {
|
||||
properties[worldName] = center
|
||||
completableFuture.complete(null)
|
||||
}
|
||||
return completableFuture
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
import net.trivernis.chunkmaster.Chunkmaster
|
||||
import org.bukkit.Chunk
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class ChunkUnloader(private val plugin: Chunkmaster): Runnable {
|
||||
private val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks")
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
private var unloadingQueue = Vector<Chunk>(maxLoadedChunks)
|
||||
val isFull: Boolean
|
||||
get() {
|
||||
return pendingSize == maxLoadedChunks
|
||||
}
|
||||
|
||||
val pendingSize: Int
|
||||
get() {
|
||||
lock.readLock().lock()
|
||||
val size = unloadingQueue.size
|
||||
lock.readLock().unlock()
|
||||
return size
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all chunks in the unloading queue with each run
|
||||
*/
|
||||
override fun run() {
|
||||
lock.writeLock().lock()
|
||||
try {
|
||||
val chunkToUnload = unloadingQueue.toHashSet()
|
||||
|
||||
for (chunk in chunkToUnload) {
|
||||
try {
|
||||
chunk.unload(true)
|
||||
} catch (e: Exception) {
|
||||
plugin.logger.severe(e.toString())
|
||||
}
|
||||
}
|
||||
unloadingQueue.clear()
|
||||
} finally {
|
||||
lock.writeLock().unlock()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a chunk to unload to the queue
|
||||
*/
|
||||
fun add(chunk: Chunk) {
|
||||
lock.writeLock().lockInterruptibly()
|
||||
try {
|
||||
unloadingQueue.add(chunk)
|
||||
} finally {
|
||||
lock.writeLock().unlock()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
import io.papermc.lib.PaperLib
|
||||
import net.trivernis.chunkmaster.Chunkmaster
|
||||
import net.trivernis.chunkmaster.lib.shapes.Shape
|
||||
import org.bukkit.World
|
||||
import java.util.concurrent.*
|
||||
|
||||
class DefaultGenerationTask(
|
||||
private val plugin: Chunkmaster,
|
||||
unloader: ChunkUnloader,
|
||||
world: World,
|
||||
startChunk: ChunkCoordinates,
|
||||
override val radius: Int = -1,
|
||||
shape: Shape,
|
||||
missingChunks: HashSet<ChunkCoordinates>,
|
||||
state: TaskState
|
||||
) : GenerationTask(plugin, world, unloader, startChunk, shape, missingChunks, state) {
|
||||
|
||||
private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
|
||||
val pendingChunks = ArrayBlockingQueue<PendingChunkEntry>(maxPendingChunks)
|
||||
|
||||
override var count = 0
|
||||
override var endReached: Boolean = false
|
||||
|
||||
init {
|
||||
updateGenerationAreaMarker()
|
||||
count = shape.count
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the generation task. Every Iteration the next chunks will be generated if
|
||||
* they haven't been generated already
|
||||
* After a configured number of chunks chunks have been generated, they will all be unloaded and saved.
|
||||
*/
|
||||
override fun generate() {
|
||||
generateMissing()
|
||||
seekGenerated()
|
||||
generateUntilBorder()
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all chunks have been generated or generates missing ones
|
||||
*/
|
||||
override fun validate() {
|
||||
this.shape.reset()
|
||||
val missedChunks = HashSet<ChunkCoordinates>()
|
||||
|
||||
while (!cancelRun && !borderReached()) {
|
||||
val chunkCoordinates = nextChunkCoordinates
|
||||
triggerDynmapRender(chunkCoordinates)
|
||||
if (!PaperLib.isChunkGenerated(world, chunkCoordinates.x, chunkCoordinates.z)) {
|
||||
missedChunks.add(chunkCoordinates)
|
||||
}
|
||||
}
|
||||
this.missingChunks.addAll(missedChunks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates chunks that are missing
|
||||
*/
|
||||
override fun generateMissing() {
|
||||
val missing = this.missingChunks.toHashSet()
|
||||
this.count = 0
|
||||
|
||||
while (missing.size > 0 && !cancelRun) {
|
||||
if (plugin.mspt < msptThreshold && !unloader.isFull) {
|
||||
val chunk = missing.first()
|
||||
missing.remove(chunk)
|
||||
this.requestGeneration(chunk)
|
||||
this.count++
|
||||
} else {
|
||||
Thread.sleep(50L)
|
||||
}
|
||||
}
|
||||
if (!cancelRun) {
|
||||
this.joinPending()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks until it encounters a chunk that hasn't been generated yet
|
||||
*/
|
||||
private fun seekGenerated() {
|
||||
do {
|
||||
lastChunkCoords = nextChunkCoordinates
|
||||
count = shape.count
|
||||
} while (PaperLib.isChunkGenerated(world, lastChunkCoords.x, lastChunkCoords.z) && !borderReached())
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the world until it encounters the worlds border
|
||||
*/
|
||||
private fun generateUntilBorder() {
|
||||
var chunkCoordinates: ChunkCoordinates
|
||||
|
||||
while (!cancelRun && !borderReached()) {
|
||||
if (plugin.mspt < msptThreshold && !unloader.isFull) {
|
||||
chunkCoordinates = nextChunkCoordinates
|
||||
requestGeneration(chunkCoordinates)
|
||||
|
||||
lastChunkCoords = chunkCoordinates
|
||||
count = shape.count
|
||||
} else {
|
||||
Thread.sleep(50L)
|
||||
}
|
||||
}
|
||||
if (!cancelRun) {
|
||||
joinPending()
|
||||
}
|
||||
}
|
||||
|
||||
private fun joinPending() {
|
||||
while (!this.pendingChunks.isEmpty()) {
|
||||
Thread.sleep(msptThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the generation of a chunk
|
||||
*/
|
||||
private fun requestGeneration(chunkCoordinates: ChunkCoordinates) {
|
||||
if (!PaperLib.isChunkGenerated(world, chunkCoordinates.x, chunkCoordinates.z) || PaperLib.isSpigot()) {
|
||||
val pendingChunkEntry = PendingChunkEntry(
|
||||
chunkCoordinates,
|
||||
PaperLib.getChunkAtAsync(world, chunkCoordinates.x, chunkCoordinates.z, true)
|
||||
)
|
||||
this.pendingChunks.put(pendingChunkEntry)
|
||||
pendingChunkEntry.chunk.thenAccept {
|
||||
this.unloader.add(it)
|
||||
this.pendingChunks.remove(pendingChunkEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the generation task.
|
||||
* This unloads all chunks that were generated but not unloaded yet.
|
||||
*/
|
||||
override fun cancel() {
|
||||
this.cancelRun = true
|
||||
this.pendingChunks.forEach { it.chunk.cancel(false) }
|
||||
updateGenerationAreaMarker(true)
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
import net.trivernis.chunkmaster.Chunkmaster
|
||||
import net.trivernis.chunkmaster.lib.shapes.Shape
|
||||
import org.bukkit.Chunk
|
||||
import org.bukkit.World
|
||||
import java.lang.Exception
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class GenerationTaskPaper(
|
||||
private val plugin: Chunkmaster,
|
||||
override val world: World,
|
||||
startChunk: ChunkCoordinates,
|
||||
override val radius: Int = -1,
|
||||
shape: Shape
|
||||
) : GenerationTask(plugin, startChunk, shape) {
|
||||
|
||||
private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
|
||||
|
||||
private val pendingChunks = HashSet<CompletableFuture<Chunk>>()
|
||||
|
||||
override var count = 0
|
||||
override var endReached: Boolean = false
|
||||
|
||||
init {
|
||||
updateGenerationAreaMarker()
|
||||
count = shape.count
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the generation task. Every Iteration the next chunks will be generated if
|
||||
* they haven't been generated already
|
||||
* After a configured number of chunks chunks have been generated, they will all be unloaded and saved.
|
||||
*/
|
||||
override fun run() {
|
||||
if (plugin.mspt < msptThreshold) {
|
||||
if (loadedChunks.size > maxLoadedChunks) {
|
||||
unloadLoadedChunks()
|
||||
} else if (pendingChunks.size < maxPendingChunks) {
|
||||
if (borderReachedCheck()) return
|
||||
|
||||
var chunk = nextChunkCoordinates
|
||||
for (i in 0 until chunkSkips) {
|
||||
if (world.isChunkGenerated(chunk.x, chunk.z)) {
|
||||
chunk = nextChunkCoordinates
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
|
||||
for (i in 0 until chunksPerStep) {
|
||||
if (borderReached()) break
|
||||
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
|
||||
pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true))
|
||||
}
|
||||
chunk = nextChunkCoordinates
|
||||
}
|
||||
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
|
||||
pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true))
|
||||
}
|
||||
}
|
||||
lastChunkCoords = chunk
|
||||
count = shape.count
|
||||
}
|
||||
}
|
||||
checkChunksLoaded()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the generation task.
|
||||
* This unloads all chunks that were generated but not unloaded yet.
|
||||
*/
|
||||
override fun cancel() {
|
||||
updateGenerationAreaMarker(true)
|
||||
updateLastChunkMarker(true)
|
||||
unloadAllChunks()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all pending chunks and unloads all loaded chunks.
|
||||
*/
|
||||
private fun unloadAllChunks() {
|
||||
for (pendingChunk in pendingChunks) {
|
||||
if (pendingChunk.isDone) {
|
||||
loadedChunks.add(pendingChunk.get())
|
||||
} else {
|
||||
pendingChunk.cancel(true)
|
||||
}
|
||||
}
|
||||
pendingChunks.clear()
|
||||
if (loadedChunks.isNotEmpty()) {
|
||||
lastChunkCoords = ChunkCoordinates(loadedChunks.last().x, loadedChunks.last().z)
|
||||
}
|
||||
for (chunk in loadedChunks) {
|
||||
if (chunk.isLoaded) {
|
||||
try {
|
||||
chunk.unload(true);
|
||||
} catch (e: Exception){
|
||||
plugin.logger.severe(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if some chunks have been loaded and adds them to the loaded chunk set.
|
||||
*/
|
||||
private fun checkChunksLoaded() {
|
||||
val completedEntries = HashSet<CompletableFuture<Chunk>>()
|
||||
for (pendingChunk in pendingChunks) {
|
||||
if (pendingChunk.isDone) {
|
||||
completedEntries.add(pendingChunk)
|
||||
loadedChunks.add(pendingChunk.get())
|
||||
} else if (pendingChunk.isCompletedExceptionally || pendingChunk.isCancelled) {
|
||||
completedEntries.add(pendingChunk)
|
||||
}
|
||||
}
|
||||
pendingChunks.removeAll(completedEntries)
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
import net.trivernis.chunkmaster.Chunkmaster
|
||||
import net.trivernis.chunkmaster.lib.shapes.Shape
|
||||
import org.bukkit.World
|
||||
import java.lang.Exception
|
||||
|
||||
class GenerationTaskSpigot(
|
||||
private val plugin: Chunkmaster,
|
||||
override val world: World,
|
||||
startChunk: ChunkCoordinates,
|
||||
override val radius: Int = -1,
|
||||
shape: Shape
|
||||
) : GenerationTask(plugin, startChunk, shape) {
|
||||
|
||||
|
||||
override var count = 0
|
||||
override var endReached: Boolean = false
|
||||
|
||||
init {
|
||||
updateGenerationAreaMarker()
|
||||
count = shape.count
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the generation task. Every Iteration the next chunks will be generated if
|
||||
* they haven't been generated already
|
||||
* After a configured number of chunks chunks have been generated, they will all be unloaded and saved.
|
||||
*/
|
||||
override fun run() {
|
||||
if (plugin.mspt < msptThreshold) {
|
||||
if (loadedChunks.size > maxLoadedChunks) {
|
||||
unloadLoadedChunks()
|
||||
} else {
|
||||
if (borderReachedCheck()) return
|
||||
|
||||
var chunk = nextChunkCoordinates
|
||||
for (i in 0 until chunksPerStep) {
|
||||
if (borderReached()) break
|
||||
val chunkInstance = world.getChunkAt(chunk.x, chunk.z)
|
||||
chunkInstance.load(true)
|
||||
loadedChunks.add(chunkInstance)
|
||||
chunk = nextChunkCoordinates
|
||||
}
|
||||
val chunkInstance = world.getChunkAt(chunk.x, chunk.z)
|
||||
chunkInstance.load(true)
|
||||
loadedChunks.add(chunkInstance)
|
||||
|
||||
lastChunkCoords = chunk
|
||||
count = shape.count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the generation task.
|
||||
* This unloads all chunks that were generated but not unloaded yet.
|
||||
*/
|
||||
override fun cancel() {
|
||||
for (chunk in loadedChunks) {
|
||||
if (chunk.isLoaded) {
|
||||
try {
|
||||
chunk.unload(true)
|
||||
} catch (e: Exception) {
|
||||
plugin.logger.severe(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
updateGenerationAreaMarker(true)
|
||||
updateLastChunkMarker(true)
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
class PausedTaskEntry(
|
||||
override val id: Int,
|
||||
override val generationTask: GenerationTask
|
||||
) : TaskEntry {
|
||||
override fun cancel() {
|
||||
generationTask.cancel()
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
|
||||
import org.bukkit.Chunk
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class PendingChunkEntry(val coordinates: ChunkCoordinates, val chunk: CompletableFuture<Chunk>) {
|
||||
val isDone: Boolean
|
||||
get() = chunk.isDone
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
|
||||
/**
|
||||
* Generic task entry
|
||||
*/
|
||||
interface TaskEntry {
|
||||
val id: Int
|
||||
val generationTask: GenerationTask
|
||||
|
||||
fun cancel()
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.trivernis.chunkmaster.lib.generation
|
||||
|
||||
enum class TaskState {
|
||||
GENERATING {
|
||||
override fun toString(): String {
|
||||
return "GENERATING"
|
||||
}
|
||||
},
|
||||
VALIDATING {
|
||||
override fun toString(): String {
|
||||
return "VALIDATING"
|
||||
}
|
||||
},
|
||||
CORRECTING {
|
||||
override fun toString(): String {
|
||||
return "CORRECTING"
|
||||
}
|
||||
},
|
||||
PAUSING {
|
||||
override fun toString(): String {
|
||||
return "PAUSING"
|
||||
}
|
||||
},
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.trivernis.chunkmaster.lib.generation.taskentry
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.GenerationTask
|
||||
|
||||
class PausedTaskEntry(
|
||||
override val id: Int,
|
||||
override val generationTask: GenerationTask
|
||||
) : TaskEntry {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package net.trivernis.chunkmaster.lib.generation.taskentry
|
||||
|
||||
import net.trivernis.chunkmaster.lib.generation.GenerationTask
|
||||
|
||||
/**
|
||||
* Generic task entry
|
||||
*/
|
||||
interface TaskEntry {
|
||||
val id: Int
|
||||
val generationTask: GenerationTask
|
||||
}
|
Loading…
Reference in New Issue