Fixes and readme update

- fixed stop and resuming on player join/leave
- fixed stop command
pull/4/head v1.0-alpha
Trivernis 5 years ago
parent d69740851e
commit 29720e8451

@ -1,5 +1,18 @@
# chunkmaster
Work in Progress. Wait for first release.
This plugin can be used to pre-generate the region of a world around the spawn chunk. The plugin provides the commands
Goal is a plugin that pregenerates chunks around the world spawn.
- /generate - Pre-generates chunks in the current world
- /listgentasks - Lists all running generation tasks (and their ids)
- /removegentask - Removes a generation task (stops it permanently)
The generation automatically pauses when a player joins the server (assuming the server was empty before)
and resumes when the server is empty again. The generation also auto-resumes after a server
restart. The plugin tracks the ticks per second and pauses the generation when the tps
is lower than 2.
## Future Features
- pause generation tasks until restarted by command
- configure the tps pause limit
- specify how many chunks to generate for a task

@ -2,9 +2,11 @@ package net.trivernis.chunkmaster
import net.trivernis.chunkmaster.commands.CommandGenerate
import net.trivernis.chunkmaster.commands.CommandListGenTasks
import net.trivernis.chunkmaster.commands.CommandRemoveGenTask
import net.trivernis.chunkmaster.lib.GenerationManager
import net.trivernis.chunkmaster.lib.Spiral
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask
import java.lang.Exception
import java.sql.Connection
import java.sql.DriverManager
@ -14,7 +16,13 @@ class Chunkmaster: JavaPlugin() {
lateinit var sqliteConnection: Connection
var dbname: String? = null
lateinit var generationManager: GenerationManager
private lateinit var tpsTask: BukkitTask
var mspt = 50L // keep track of the milliseconds per tick
get() = field
/**
* On enable of the plugin
*/
override fun onEnable() {
configure()
initDatabase()
@ -22,15 +30,28 @@ class Chunkmaster: JavaPlugin() {
generationManager.init()
getCommand("generate")?.setExecutor(CommandGenerate(this))
getCommand("listgentasks")?.setExecutor(CommandListGenTasks(this))
getCommand("removegentask")?.setExecutor(CommandRemoveGenTask(this))
server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this)
tpsTask = server.scheduler.runTaskTimer(this, Runnable {
val start = System.currentTimeMillis()
server.scheduler.runTaskLater(this, Runnable {
mspt = System.currentTimeMillis() - start
}, 1)
}, 1, 300)
}
/**
* Stop all tasks and close database connection on disable
*/
override fun onDisable() {
logger.info("Stopping all generation tasks...")
generationManager.stopAll()
sqliteConnection.close()
}
/**
* Cofigure the config file
*/
private fun configure() {
dataFolder.mkdir()
config.options().copyDefaults(true)
@ -52,7 +73,8 @@ class Chunkmaster: JavaPlugin() {
center_z integer NOT NULL DEFAULT 0,
last_x integer NOT NULL DEFAULT 0,
last_z integer NOT NULL DEFAULT 0,
world text UNIQUE NOT NULL DEFAULT 'world'
world text UNIQUE NOT NULL DEFAULT 'world',
autostart integer DEFAULT 1
);
""".trimIndent())
createTableStatement.execute()

@ -1,6 +1,7 @@
package net.trivernis.chunkmaster
import org.bukkit.Server
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent
@ -10,7 +11,7 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server
/**
* Autostart generation tasks
*/
fun onPlayerQuit(event: PlayerQuitEvent) {
@EventHandler fun onPlayerQuit(event: PlayerQuitEvent) {
if (server.onlinePlayers.size == 1 && server.onlinePlayers.contains(event.player) ||
server.onlinePlayers.isEmpty()) {
chunkmaster.generationManager.startAll()
@ -21,7 +22,7 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server
/**
* Autostop generation tasks
*/
fun onPlayerJoin(event: PlayerJoinEvent) {
@EventHandler fun onPlayerJoin(event: PlayerJoinEvent) {
if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) {
chunkmaster.generationManager.stopAll()
chunkmaster.logger.info("Stopping generation tasks because of player join.")

@ -15,7 +15,7 @@ class CommandListGenTasks(private val chunkmaster: Chunkmaster): CommandExecutor
val runningTasks = chunkmaster.generationManager.tasks
val response = ComponentBuilder("Currently running generation tasks").color(ChatColor.BLUE)
for (task in runningTasks) {
response.append("\n - #${task.id}: ${task.generationTask.world}, Progress ${task.generationTask.count}")
response.append("\n - #${task.id}: ${task.generationTask.world.name}, Progress ${task.generationTask.count}")
}
response.color(ChatColor.GREEN)
sender.spigot().sendMessage(*response.create())

@ -0,0 +1,19 @@
package net.trivernis.chunkmaster.commands
import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
class CommandRemoveGenTask(private val chunkmaster: Chunkmaster): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
return if (command.testPermission(sender) && args.size == 1) {
chunkmaster.generationManager.removeTask(args[1].toInt())
sender.sendMessage("Task ${args[1].toInt()} canceled.")
true
} else {
sender.sendMessage("Invalid argument.")
false
}
}
}

@ -47,13 +47,33 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
/**
* Resumes a generation task
*/
fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int) {
private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int) {
chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"")
val generationTask = GenerationTask(chunkmaster, world, center, last)
val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, 2)
tasks.add(TaskEntry(id, task, generationTask))
}
/**
* Stops a running generation task.
*/
fun removeTask(id: Int) {
val taskEntry = this.tasks.find {it.id == id}
if (taskEntry != null) {
taskEntry.generationTask.cancel()
taskEntry.task.cancel()
if (taskEntry.task.isCancelled) {
tasks.remove(taskEntry)
}
val setAutostart = chunkmaster.sqliteConnection.prepareStatement("""
UPDATE TABLE generation_tasks SET autostart = 0 WHERE id = ?
""".trimIndent())
setAutostart.setInt(1, id)
setAutostart.execute()
setAutostart.close()
}
}
/**
* Init
* Loads tasks from the database and resumes them
@ -75,6 +95,10 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
for (task in tasks) {
task.generationTask.cancel()
task.task.cancel()
if (task.task.isCancelled) {
tasks.remove(task)
}
chunkmaster.logger.info("Canceled task #${task.id}")
}
}
@ -88,11 +112,15 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
val res = savedTasksStatement.resultSet
while (res.next()) {
try {
val id = res.getInt("id")
val world = server.getWorld(res.getString("world"))
val center = world!!.getChunkAt(res.getInt("center_x"), res.getInt("center_z"))
val last = world.getChunkAt(res.getInt("last_x"), res.getInt("last_z"))
resumeTask(world, center, last, id)
if (res.getBoolean("autostart")) {
val id = res.getInt("id")
val world = server.getWorld(res.getString("world"))
val center = world!!.getChunkAt(res.getInt("center_x"), res.getInt("center_z"))
val last = world.getChunkAt(res.getInt("last_x"), res.getInt("last_z"))
if (this.tasks.find {it.id == id} == null) {
resumeTask(world, center, last, id)
}
}
} catch (error: NullPointerException) {
server.consoleSender.sendMessage("Failed to load Task ${res.getInt("id")}.")
}

@ -15,26 +15,36 @@ class GenerationTask(private val plugin: Chunkmaster, val world: World,
var lastChunk: Chunk = startChunk
get() = field
/**
* Runs the generation task. Every Iteration the next chunk will be generated if
* it hasn't been generated already.
*/
override fun run() {
if (loadedChunks.size > 10) {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
chunk.unload(true)
if (plugin.mspt < 500L) { // pause when tps < 2
if (loadedChunks.size > 10) {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
chunk.unload(true)
}
}
}
} else {
val nextChunkCoords = spiral.next()
val chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second)
} else {
val nextChunkCoords = spiral.next()
val chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second)
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
chunk.load(true)
loadedChunks.add(chunk)
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
chunk.load(true)
loadedChunks.add(chunk)
}
lastChunk = chunk
count++
}
lastChunk = chunk
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) {

@ -2,7 +2,7 @@ package net.trivernis.chunkmaster.lib
import kotlin.math.abs
class Spiral(private val center: Pair<Int, Int>, private val start: Pair<Int, Int>) {
class Spiral(private val center: Pair<Int, Int>, start: Pair<Int, Int>) {
var currentPos = start
var direction = 0
var count = 0
@ -12,15 +12,12 @@ class Spiral(private val center: Pair<Int, Int>, private val start: Pair<Int, In
*/
fun next(): Pair<Int, Int> {
if (count == 0 && currentPos != center) {
val distances = getDistances(center, currentPos)
if (distances.second < distances.first && distances.first > 0) {
direction = 1
} else if (distances.first > distances.second && distances.second < 0) {
direction = 2
} else if (distances.second > distances.first && distances.first < 0) {
direction = 3
}
// simulate the spiral to get the correct direction
// TODO: Improve performance of this workaround (replace it with acutal stuff)
val simSpiral = Spiral(center, center)
while (simSpiral.next() != currentPos);
direction = simSpiral.direction
count = simSpiral.count
}
if (count == 1) { // because of the center behaviour
count ++

@ -14,16 +14,24 @@ commands:
description: Lists all generation tasks
permission: chunkmaster.listgentasks
usage: /listgentasks
removegentask:
description: Removes the generation task with the specified id
permission: chunkmaster.removegentask
usage: /removegentask {task-id}
permissions:
cunkmaster.generate:
description: Allows generate command
description: Allows generate command.
default: op
chunkmaster.listgentasks:
description: Lists all generation tasks
description: Allows the listgentask command.
default: op
chunkmaster.removegentask:
description: Allows the removegentask command.
default: op
chunkmaster.*:
description: Wildcard permission
default: op
children:
- chunkmaster.generate
- chunkmaster.listgentasks
- chunkmaster.listgentasks
- chunkmaster.removegentask
Loading…
Cancel
Save