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 classpull/82/head
parent
1ebf3c96a8
commit
acf302e8c1
@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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