From 08bec14df94987cf1f0707ccfdfd6cb93475d342 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 17 Sep 2019 23:38:04 +0200 Subject: [PATCH] Generation task seperation - Added interface GenerationTask - added method to GenerationManager that creates a task depending on the server type - added generation task for paper and spigot (to profit from async chunk loading) --- build.gradle | 23 ++++- .../net/trivernis/chunkmaster/Chunkmaster.kt | 2 + .../chunkmaster/lib/GenerationManager.kt | 22 ++++- .../chunkmaster/lib/GenerationTask.kt | 82 ++++------------ .../chunkmaster/lib/GenerationTaskPaper.kt | 97 +++++++++++++++++++ .../chunkmaster/lib/GenerationTaskSpigot.kt | 74 ++++++++++++++ 6 files changed, 233 insertions(+), 67 deletions(-) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskPaper.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskSpigot.kt diff --git a/build.gradle b/build.gradle index 55728e3..9930870 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,12 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2" + } +} plugins { id 'idea' @@ -13,7 +22,7 @@ idea { } group "net.trivernis" -version "1.0-SNAPSHOT" +version "0.10-beta" sourceCompatibility = 1.8 @@ -28,6 +37,10 @@ repositories { maven { url 'https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc' } + maven { + name 'papermc' + url 'https://papermc.io/repo/repository/maven-public/' + } } dependencies { @@ -36,16 +49,24 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' compileOnly "org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT" compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' + compile "io.papermc:paperlib:1.0.2" +} + +apply plugin: "com.github.johnrengelman.shadow" +shadowJar { + relocate 'io.papermc.lib', 'net.trivernis.chunkmaster.paperlib' } jar { from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + compileKotlin { kotlinOptions { jvmTarget = "1.8" } } + compileTestKotlin { kotlinOptions { jvmTarget = "1.8" diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index af8ca59..ca705db 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -1,5 +1,6 @@ package net.trivernis.chunkmaster +import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.commands.* import net.trivernis.chunkmaster.lib.GenerationManager import net.trivernis.chunkmaster.lib.SqlUpdateManager @@ -21,6 +22,7 @@ class Chunkmaster: JavaPlugin() { * On enable of the plugin */ override fun onEnable() { + PaperLib.suggestPaper(this) configure() initDatabase() generationManager = GenerationManager(this, server) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt index 0a87897..3d0bb3b 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt @@ -1,11 +1,10 @@ package net.trivernis.chunkmaster.lib -import javafx.concurrent.Task +import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.Chunkmaster import org.bukkit.Chunk import org.bukkit.Server import org.bukkit.World -import org.bukkit.scheduler.BukkitTask import java.lang.Exception import java.lang.NullPointerException @@ -20,7 +19,8 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server */ fun addTask(world: World, stopAfter: Int = -1): Int { val centerChunk = world.getChunkAt(world.spawnLocation) - val generationTask = GenerationTask(chunkmaster, world, centerChunk, centerChunk, stopAfter) + val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter) + val insertStatement = chunkmaster.sqliteConnection.prepareStatement(""" INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after) values (?, ?, ?, ?, ?, ?) @@ -59,7 +59,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int, stopAfter: Int = -1) { if (!paused) { chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"") - val generationTask = GenerationTask(chunkmaster, world, center, last, stopAfter) + val generationTask = createGenerationTask(world, center, last, stopAfter) val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, chunkmaster.config.getLong("generation.period")) tasks.add(TaskEntry(id, task, generationTask)) @@ -174,7 +174,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val genTask = task.generationTask server.consoleSender.sendMessage("""Task #${task.id} running for "${genTask.world.name}". |Progress ${task.generationTask.count} chunks - |${if (task.generationTask.stopAfter > 0)"(${(task.generationTask.count.toDouble()/task.generationTask.stopAfter.toDouble())*100}%)." else ""} + |${if (task.generationTask.stopAfter > 0)"(${(task.generationTask.count.toDouble()/task.generationTask.stopAfter.toDouble())*100}%)" else ""}. |Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ')) val updateStatement = chunkmaster.sqliteConnection.prepareStatement(""" UPDATE generation_tasks SET last_x = ?, last_z = ? @@ -195,4 +195,16 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server } } } + + /** + * Creates a new generation task. This method is used to create a task depending + * on the server type (Paper/Spigot). + */ + private fun createGenerationTask(world: World, center: Chunk, start: Chunk, stopAfter: Int): GenerationTask { + return if (PaperLib.isPaper()) { + GenerationTaskPaper(chunkmaster, world, center, start, stopAfter) + } else { + GenerationTaskSpigot(chunkmaster, world, center, start, stopAfter) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt index da58b1e..230d843 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt @@ -3,73 +3,33 @@ package net.trivernis.chunkmaster.lib import net.trivernis.chunkmaster.Chunkmaster import org.bukkit.Chunk import org.bukkit.World -import org.bukkit.scheduler.BukkitRunnable -import org.bukkit.scheduler.BukkitTask -class GenerationTask(private val plugin: Chunkmaster, val world: World, - centerChunk: Chunk, private val startChunk: Chunk, - val stopAfter: Int = -1): Runnable { - private val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) - private val loadedChunks: HashSet = HashSet() - private val chunkSkips = plugin.config.getInt("generation.chunks-skips-per-step") - private val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold") +/** + * Interface for generation tasks. + */ +abstract class GenerationTask(plugin: Chunkmaster, centerChunk: Chunk, startChunk: Chunk) : Runnable { - var count = 0 - private set - var lastChunk: Chunk = startChunk - private set - var endReached: Boolean = false - private set + abstract val stopAfter: Int + abstract val world: World + abstract val count: Int + abstract val lastChunk: Chunk + abstract val endReached: Boolean - /** - * Runs the generation task. Every Iteration the next chunk will be generated if - * it hasn't been generated already. - * After 10 chunks have been generated, they will all be unloaded and saved. - */ - override fun run() { - if (plugin.mspt < msptThreshold) { // pause when tps < 2 - if (loadedChunks.size > 10) { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - chunk.unload(true) - } - } - } else { - if (!world.worldBorder.isInside(lastChunk.getBlock(8, 0, 8).location) || (stopAfter in 1..count)) { - endReached = true - return - } - var nextChunkCoords = spiral.next() - var chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second) + protected val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) + protected val loadedChunks: HashSet = HashSet() + protected val chunkSkips = plugin.config.getInt("generation.chunks-skips-per-step") + protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold") - for (i in 1 until chunkSkips) { - if (world.isChunkGenerated(chunk.x, chunk.z)) { - nextChunkCoords = spiral.next() // if the chunk is generated skip 10 chunks per step - chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second) - } else { - break - } - } + abstract override fun run() + abstract fun cancel() - if (!world.isChunkGenerated(chunk.x, chunk.z)) { - chunk.load(true) - loadedChunks.add(chunk) - } - lastChunk = chunk - count = spiral.count // set the count to the more accurate spiral count - } + val nextChunk: Chunk + get() { + val nextChunkCoords = spiral.next() + return world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second) } - } - /** - * Cancels the generation task. - * This unloads all chunks that were generated but not unloaded yet. - */ - fun cancel() { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - chunk.unload(true) - } - } + protected fun borderReached(): Boolean { + return !world.worldBorder.isInside(lastChunk.getBlock(8, 0, 8).location) || (stopAfter in 1..count) } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskPaper.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskPaper.kt new file mode 100644 index 0000000..4703008 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskPaper.kt @@ -0,0 +1,97 @@ +package net.trivernis.chunkmaster.lib + +import net.trivernis.chunkmaster.Chunkmaster +import org.bukkit.Chunk +import org.bukkit.World +import java.util.concurrent.CompletableFuture +import io.papermc.lib.PaperLib + +class GenerationTaskPaper( + private val plugin: Chunkmaster, override val world: World, + centerChunk: Chunk, private val startChunk: Chunk, + override val stopAfter: Int = -1 +) : GenerationTask(plugin, centerChunk, startChunk) { + + private val pendingChunks = HashSet>() + + override var count = 0 + private set + override var lastChunk: Chunk = startChunk + private set + override var endReached: Boolean = false + private set + + /** + * Runs the generation task. Every Iteration the next chunk will be generated if + * it hasn't been generated already. + * After 10 chunks have been generated, they will all be unloaded and saved. + */ + override fun run() { + if (plugin.mspt < msptThreshold) { // pause when tps < 2 + if (loadedChunks.size > 10) { + for (chunk in loadedChunks) { + if (chunk.isLoaded) { + chunk.unload(true) + } + } + } else if (pendingChunks.size < 10) { // if more than 10 chunks are pending, wait. + if (borderReached()) { + endReached = true + return + } + + var chunk = nextChunk + for (i in 1 until chunkSkips) { + if (PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { + chunk = nextChunk + } else { + break + } + } + + if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { + pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true)) + } + lastChunk = chunk + count = spiral.count // set the count to the more accurate spiral count + } + } + checkChunksLoaded() + } + + /** + * Cancels the generation task. + * This unloads all chunks that were generated but not unloaded yet. + */ + override fun cancel() { + for (pendingChunk in pendingChunks) { + if (pendingChunk.isDone) { + loadedChunks.add(pendingChunk.get()) + } else { + pendingChunk.cancel(true) + } + } + pendingChunks.clear() + for (chunk in loadedChunks) { + if (chunk.isLoaded) { + chunk.unload(true) + } + } + } + + /** + * Checks if some chunks have been loaded and adds them to the loaded chunk set. + */ + private fun checkChunksLoaded() { + val completedEntrys = HashSet>() + for (pendingChunk in pendingChunks) { + if (pendingChunk.isDone) { + completedEntrys.add(pendingChunk) + loadedChunks.add(pendingChunk.get()) + } else if (pendingChunk.isCompletedExceptionally || pendingChunk.isCancelled) { + completedEntrys.add(pendingChunk) + } + } + pendingChunks.removeAll(completedEntrys) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskSpigot.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskSpigot.kt new file mode 100644 index 0000000..9aa831f --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTaskSpigot.kt @@ -0,0 +1,74 @@ +package net.trivernis.chunkmaster.lib + +import net.trivernis.chunkmaster.Chunkmaster +import org.bukkit.Chunk +import org.bukkit.World +import org.bukkit.scheduler.BukkitRunnable +import org.bukkit.scheduler.BukkitTask +import java.util.concurrent.CompletableFuture + +class GenerationTaskSpigot( + private val plugin: Chunkmaster, override val world: World, + centerChunk: Chunk, private val startChunk: Chunk, + override val stopAfter: Int = -1 +) : GenerationTask(plugin, centerChunk, startChunk) { + + + override var count = 0 + private set + override var lastChunk: Chunk = startChunk + private set + override var endReached: Boolean = false + private set + + /** + * Runs the generation task. Every Iteration the next chunk will be generated if + * it hasn't been generated already. + * After 10 chunks have been generated, they will all be unloaded and saved. + */ + override fun run() { + if (plugin.mspt < msptThreshold) { // pause when tps < 2 + if (loadedChunks.size > 10) { + for (chunk in loadedChunks) { + if (chunk.isLoaded) { + chunk.unload(true) + } + } + } else { + if (borderReached()) { + endReached = true + return + } + + var chunk = nextChunk + + for (i in 1 until chunkSkips) { + if (world.isChunkGenerated(chunk.x, chunk.z)) { + chunk = nextChunk + } else { + break + } + } + + if (!world.isChunkGenerated(chunk.x, chunk.z)) { + chunk.load(true) + loadedChunks.add(chunk) + } + lastChunk = chunk + count = spiral.count // set the count to the more accurate spiral 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) { + chunk.unload(true) + } + } + } +} \ No newline at end of file