Merge pull request #1 from Trivernis/release/beta-0.10

Release/beta 0.10
release/0.11-beta v0.10-beta
Trivernis 5 years ago committed by GitHub
commit d456f40124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,11 +22,24 @@ All features can be accessed with the command `/chunkmaster` or the aliases `/ch
```yaml ```yaml
generation: generation:
# The maximum amount of chunks that are loaded before unloading and saving them.
# Higher values mean higher generation speed but greater memory usage.
# The value should be a positive integer.
max-loaded-chunks: 10
# Paper Only
# The maximum amount of requested chunks with the asynchronous paper chunk
# loading method. Higher values mean faster generation but more memory usage
# (and probably bigger performance impact).
# The value should be a positive integer.
max-pending-chunks: 10
# The period (in ticks) in which a generation step is run. # The period (in ticks) in which a generation step is run.
# Higher values mean less performance impact but slower generation. # Higher values mean less performance impact but slower generation.
# The value should be a positive integer. # The value should be a positive integer.
period: 2 period: 2
# Paper Only
# The number of already generated chunks that will be skipped for each step. # The number of already generated chunks that will be skipped for each step.
# Notice that these still have a performance impact because the server needs to check # Notice that these still have a performance impact because the server needs to check
# if the chunk is generated. # if the chunk is generated.
@ -48,3 +61,9 @@ generation:
# The value should be a boolean <true/false> # The value should be a boolean <true/false>
pause-on-join: true pause-on-join: true
``` ```
## Spigot and Paper
The plugin works on spigot and paper servers but is significantly faster on paper servers
(because it profits from asynchronous chunk loading an the better implementation of the
isChunkGenerated method).

@ -1,3 +1,12 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2"
}
}
plugins { plugins {
id 'idea' id 'idea'
@ -13,7 +22,7 @@ idea {
} }
group "net.trivernis" group "net.trivernis"
version "1.0-SNAPSHOT" version "0.10-beta"
sourceCompatibility = 1.8 sourceCompatibility = 1.8
@ -28,6 +37,10 @@ repositories {
maven { maven {
url 'https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc' url 'https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc'
} }
maven {
name 'papermc'
url 'https://papermc.io/repo/repository/maven-public/'
}
} }
dependencies { dependencies {
@ -36,16 +49,24 @@ dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'junit', name: 'junit', version: '4.12'
compileOnly "org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT" compileOnly "org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT"
compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' 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 { jar {
from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
} }
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
} }
compileTestKotlin { compileTestKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"

@ -1,7 +1,8 @@
package net.trivernis.chunkmaster package net.trivernis.chunkmaster
import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.commands.* import net.trivernis.chunkmaster.commands.*
import net.trivernis.chunkmaster.lib.GenerationManager import net.trivernis.chunkmaster.lib.generation.GenerationManager
import net.trivernis.chunkmaster.lib.SqlUpdateManager import net.trivernis.chunkmaster.lib.SqlUpdateManager
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
@ -21,6 +22,7 @@ class Chunkmaster: JavaPlugin() {
* On enable of the plugin * On enable of the plugin
*/ */
override fun onEnable() { override fun onEnable() {
PaperLib.suggestPaper(this)
configure() configure()
initDatabase() initDatabase()
generationManager = GenerationManager(this, server) generationManager = GenerationManager(this, server)
@ -54,9 +56,11 @@ class Chunkmaster: JavaPlugin() {
private fun configure() { private fun configure() {
dataFolder.mkdir() dataFolder.mkdir()
config.addDefault("generation.period", 2L) config.addDefault("generation.period", 2L)
config.addDefault("generation.chunks-skips-per-step", 4) config.addDefault("generation.chunks-skips-per-step", 10)
config.addDefault("generation.mspt-pause-threshold", 500L) config.addDefault("generation.mspt-pause-threshold", 500L)
config.addDefault("generation.pause-on-join", true) config.addDefault("generation.pause-on-join", true)
config.addDefault("generation.max-pending-chunks", 10)
config.addDefault("generation.max-loaded-chunks", 10)
config.options().copyDefaults(true) config.options().copyDefaults(true)
saveConfig() saveConfig()
} }

@ -6,17 +6,19 @@ import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.event.player.PlayerQuitEvent
class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server): Listener { class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server) : Listener {
private val pauseOnJoin = chunkmaster.config.getBoolean("generation.pause-on-join") private val pauseOnJoin = chunkmaster.config.getBoolean("generation.pause-on-join")
/** /**
* Autostart generation tasks * Autostart generation tasks
*/ */
@EventHandler fun onPlayerQuit(event: PlayerQuitEvent) { @EventHandler
fun onPlayerQuit(event: PlayerQuitEvent) {
if (pauseOnJoin) { if (pauseOnJoin) {
if (server.onlinePlayers.size == 1 && server.onlinePlayers.contains(event.player) || if (server.onlinePlayers.size == 1 && server.onlinePlayers.contains(event.player) ||
server.onlinePlayers.isEmpty()) { server.onlinePlayers.isEmpty()
) {
if (!chunkmaster.generationManager.paused) { if (!chunkmaster.generationManager.paused) {
chunkmaster.generationManager.startAll() chunkmaster.generationManager.startAll()
chunkmaster.logger.info("Server is empty. Starting chunk generation tasks.") chunkmaster.logger.info("Server is empty. Starting chunk generation tasks.")
@ -28,7 +30,8 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server
/** /**
* Autostop generation tasks * Autostop generation tasks
*/ */
@EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { @EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
if (pauseOnJoin) { if (pauseOnJoin) {
if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) { if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) {
chunkmaster.generationManager.stopAll() chunkmaster.generationManager.stopAll()

@ -1,75 +0,0 @@
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,
private val centerChunk: Chunk, private val startChunk: Chunk,
private val stopAfter: Int = -1): Runnable {
private val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z))
private val loadedChunks: HashSet<Chunk> = HashSet()
private val chunkSkips = plugin.config.getInt("generation.chunks-skips-per-step")
private val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold")
var count = 0
private set
var lastChunk: Chunk = startChunk
private set
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 (!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)
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
}
}
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.
*/
fun cancel() {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
chunk.unload(true)
}
}
}
}

@ -62,7 +62,7 @@ class SqlUpdateManager(private val connnection: Connection, private val chunkmas
stmt.execute() stmt.execute()
stmt.close() stmt.close()
} catch (err: Exception) { } catch (err: Exception) {
chunkmaster.logger.severe("Error creating table $table."); chunkmaster.logger.severe("Error creating table $table.")
} }
} }
for (table in needUpdate) { for (table in needUpdate) {

@ -0,0 +1,10 @@
package net.trivernis.chunkmaster.lib.generation
import org.bukkit.Location
import org.bukkit.World
class ChunkCoordinates(val x: Int, val z: Int) {
fun getCenterLocation(world: World): Location {
return Location(world, ((x*16) + 8).toDouble(), 1.0, ((x*16) + 8).toDouble())
}
}

@ -1,11 +1,10 @@
package net.trivernis.chunkmaster.lib package net.trivernis.chunkmaster.lib.generation
import javafx.concurrent.Task import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Server import org.bukkit.Server
import org.bukkit.World import org.bukkit.World
import org.bukkit.scheduler.BukkitTask
import java.lang.Exception import java.lang.Exception
import java.lang.NullPointerException 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 { fun addTask(world: World, stopAfter: Int = -1): Int {
val centerChunk = world.getChunkAt(world.spawnLocation) 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(""" val insertStatement = chunkmaster.sqliteConnection.prepareStatement("""
INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after) INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after)
values (?, ?, ?, ?, ?, ?) 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) { private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int, stopAfter: Int = -1) {
if (!paused) { if (!paused) {
chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"") 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, val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10,
chunkmaster.config.getLong("generation.period")) chunkmaster.config.getLong("generation.period"))
tasks.add(TaskEntry(id, task, generationTask)) tasks.add(TaskEntry(id, task, generationTask))
@ -172,8 +172,10 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
for (task in tasks) { for (task in tasks) {
try { try {
val genTask = task.generationTask val genTask = task.generationTask
server.consoleSender.sendMessage("Task #${task.id} running for \"${genTask.world.name}\". " + server.consoleSender.sendMessage("""Task #${task.id} running for "${genTask.world.name}".
"Progress ${task.generationTask.count} chunks. Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}") |Progress ${task.generationTask.count} chunks
|${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(""" val updateStatement = chunkmaster.sqliteConnection.prepareStatement("""
UPDATE generation_tasks SET last_x = ?, last_z = ? UPDATE generation_tasks SET last_x = ?, last_z = ?
WHERE id = ? WHERE id = ?
@ -193,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)
}
}
} }

@ -0,0 +1,51 @@
package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Spiral
import org.bukkit.Chunk
import org.bukkit.World
/**
* Interface for generation tasks.
*/
abstract class GenerationTask(plugin: Chunkmaster, centerChunk: Chunk, startChunk: Chunk) : Runnable {
abstract val stopAfter: Int
abstract val world: World
abstract val count: Int
abstract val endReached: Boolean
protected val spiral: Spiral =
Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z))
protected val loadedChunks: HashSet<Chunk> = HashSet()
protected var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z)
protected val chunkSkips = plugin.config.getInt("generation.chunks-skips-per-step")
protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold")
protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks")
abstract override fun run()
abstract fun cancel()
val nextChunkCoordinates: ChunkCoordinates
get() {
val nextChunkCoords = spiral.next()
return ChunkCoordinates(nextChunkCoords.first, nextChunkCoords.second)
}
var lastChunk: Chunk = startChunk
get() {
return world.getChunkAt(lastChunkCoords.x, lastChunkCoords.z)
}
private set
val nextChunk: Chunk
get() {
val next = nextChunkCoordinates
return world.getChunkAt(next.x, next.z)
}
/**
* Checks if the World border or the maximum chunk setting for the task is reached.
*/
protected fun borderReached(): Boolean {
return !world.worldBorder.isInside(lastChunkCoords.getCenterLocation(world)) || (stopAfter in 1..count)
}
}

@ -0,0 +1,98 @@
package net.trivernis.chunkmaster.lib.generation
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 maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
private val pendingChunks = HashSet<CompletableFuture<Chunk>>()
override var count = 0
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 > maxLoadedChunks) {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
chunk.unload(true)
}
}
loadedChunks.clear()
} else if (pendingChunks.size < maxPendingChunks) { // if more than 10 chunks are pending, wait.
if (borderReached()) {
endReached = true
return
}
var chunk = nextChunkCoordinates
for (i in 1 until chunkSkips) {
if (PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) {
chunk = nextChunkCoordinates
} else {
break
}
}
if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) {
pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true))
}
lastChunkCoords = 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<CompletableFuture<Chunk>>()
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)
}
}

@ -0,0 +1,63 @@
package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk
import org.bukkit.World
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 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 > maxLoadedChunks) {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
chunk.unload(true)
}
}
loadedChunks.clear()
} else {
if (borderReached()) {
endReached = true
return
}
val chunk = nextChunkCoordinates
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
val chunkInstance = world.getChunkAt(chunk.x, chunk.z)
chunkInstance.load(true)
loadedChunks.add(chunkInstance)
}
lastChunkCoords = 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)
}
}
}
}

@ -1,6 +1,5 @@
package net.trivernis.chunkmaster.lib package net.trivernis.chunkmaster.lib.generation
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
data class TaskEntry(val id: Int, val task: BukkitTask, val generationTask: GenerationTask) { data class TaskEntry(val id: Int, val task: BukkitTask, val generationTask: GenerationTask)
}

@ -1,6 +1,6 @@
main: net.trivernis.chunkmaster.Chunkmaster main: net.trivernis.chunkmaster.Chunkmaster
name: Chunkmaster name: Chunkmaster
version: '1.0 SNAPSHOT' version: '0.10-beta'
description: Chunk commands plugin. description: Chunk commands plugin.
author: Trivernis author: Trivernis
website: trivernis.net website: trivernis.net

Loading…
Cancel
Save