Initial Rough Cut of Heimdall Tracking System

This commit is contained in:
Kenneth Endfinger
2021-12-24 00:08:38 -05:00
parent 78566d08ad
commit e0183127b4
14 changed files with 293 additions and 3 deletions

View File

@ -0,0 +1,21 @@
package cloud.kubelet.foundation.heimdall
fun String.sqlSplitStatements(): List<String> {
val statements = mutableListOf<String>()
val buffer = StringBuilder()
fun flush() {
val trimmed = buffer.toString().trim()
if (trimmed.isNotEmpty()) {
statements.add(trimmed)
}
}
for (line in lines()) {
if (line.trim() == "--") {
flush()
} else {
buffer.append(line).append("\n")
}
}
flush()
return statements
}

View File

@ -0,0 +1,90 @@
package cloud.kubelet.foundation.heimdall
import cloud.kubelet.foundation.core.FoundationCorePlugin
import cloud.kubelet.foundation.core.Util
import cloud.kubelet.foundation.heimdall.buffer.BufferFlushThread
import cloud.kubelet.foundation.heimdall.buffer.EventBuffer
import cloud.kubelet.foundation.heimdall.event.PlayerPositionEvent
import cloud.kubelet.foundation.heimdall.model.HeimdallConfig
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
import com.charleskorn.kaml.Yaml
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.plugin.java.JavaPlugin
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
import org.postgresql.Driver
import java.lang.Exception
import java.time.Instant
import kotlin.io.path.inputStream
class FoundationHeimdallPlugin : JavaPlugin(), Listener {
private lateinit var config: HeimdallConfig
private lateinit var pool: HikariDataSource
internal lateinit var db: Database
private val buffer = EventBuffer()
private val bufferFlushThread = BufferFlushThread(this, buffer)
override fun onEnable() {
val foundation = server.pluginManager.getPlugin("Foundation") as FoundationCorePlugin
val configPath = Util.copyDefaultConfig<FoundationHeimdallPlugin>(
slF4JLogger,
foundation.pluginDataPath,
"heimdall.yaml"
)
config = Yaml.default.decodeFromStream(HeimdallConfig.serializer(), configPath.inputStream())
if (!config.enabled) {
slF4JLogger.info("Heimdall is not enabled.")
return
}
slF4JLogger.info("Heimdall is enabled.")
if (!Driver.isRegistered()) {
Driver.register()
}
pool = HikariDataSource(HikariConfig().apply {
jdbcUrl = config.db.url
username = config.db.username
password = config.db.password
schema = "heimdall"
})
val initMigrationContent = FoundationHeimdallPlugin::class.java.getResourceAsStream(
"/init.sql"
)?.readAllBytes()?.decodeToString() ?: throw RuntimeException("Unable to find Heimdall init.sql")
val statements = initMigrationContent.sqlSplitStatements()
pool.connection.use { conn ->
conn.autoCommit = false
try {
for (statementAsString in statements) {
conn.prepareStatement(statementAsString).use {
it.execute()
}
}
conn.commit()
} catch (e: Exception) {
conn.rollback()
throw e
} finally {
conn.autoCommit = true
}
}
db = Database.connect(pool)
server.pluginManager.registerEvents(this, this)
bufferFlushThread.start()
}
@EventHandler
fun onPlayerMove(event: PlayerMoveEvent) = buffer.push(PlayerPositionEvent(event))
override fun onDisable() {
bufferFlushThread.stop()
}
}

View File

@ -0,0 +1,37 @@
package cloud.kubelet.foundation.heimdall.buffer
import cloud.kubelet.foundation.heimdall.FoundationHeimdallPlugin
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.concurrent.atomic.AtomicBoolean
class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventBuffer) {
private val running = AtomicBoolean(false)
fun start() {
running.set(true)
val thread = Thread {
plugin.slF4JLogger.info("Buffer Flusher Started")
while (running.get()) {
try {
transaction(plugin.db) {
val count = buffer.flush(this)
if (count > 0) {
plugin.slF4JLogger.info("Flushed $count Events")
}
}
} catch (e: Exception) {
plugin.slF4JLogger.warn("Failed to flush buffer.", e)
}
Thread.sleep(5000)
}
plugin.slF4JLogger.info("Buffer Flusher Stopped")
}
thread.name = "Heimdall Buffer Flush"
thread.isDaemon = false
thread.start()
}
fun stop() {
running.set(false)
}
}

View File

@ -0,0 +1,24 @@
package cloud.kubelet.foundation.heimdall.buffer
import cloud.kubelet.foundation.heimdall.event.HeimdallEvent
import org.jetbrains.exposed.sql.Transaction
class EventBuffer {
private var events = mutableListOf<HeimdallEvent>()
fun flush(transaction: Transaction): Long {
val referenceOfEvents = events
this.events = mutableListOf()
var count = 0L
while (referenceOfEvents.isNotEmpty()) {
val event = referenceOfEvents.removeAt(0)
event.store(transaction)
count++
}
return count
}
fun push(event: HeimdallEvent) {
events.add(event)
}
}

View File

@ -0,0 +1,7 @@
package cloud.kubelet.foundation.heimdall.event
import org.jetbrains.exposed.sql.Transaction
abstract class HeimdallEvent {
abstract fun store(transaction: Transaction)
}

View File

@ -0,0 +1,31 @@
package cloud.kubelet.foundation.heimdall.event
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
import org.bukkit.Location
import org.bukkit.event.player.PlayerMoveEvent
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.insert
import java.time.Instant
import java.util.*
class PlayerPositionEvent(
val playerUniqueIdentity: UUID,
val location: Location
) : HeimdallEvent() {
constructor(event: PlayerMoveEvent) : this(event.player.uniqueId, event.to)
override fun store(transaction: Transaction) {
transaction.apply {
PlayerPositionTable.insert {
it[time] = Instant.now()
it[player] = playerUniqueIdentity
it[world] = location.world.uid
it[x] = location.x
it[y] = location.y
it[z] = location.z
it[pitch] = location.pitch.toDouble()
it[yaw] = location.yaw.toDouble()
}
}
}
}

View File

@ -0,0 +1,16 @@
package cloud.kubelet.foundation.heimdall.model
import kotlinx.serialization.Serializable
@Serializable
data class HeimdallConfig(
val enabled: Boolean = false,
val db: DbConfig
)
@Serializable
data class DbConfig(
val url: String,
val username: String,
val password: String
)

View File

@ -0,0 +1,15 @@
package cloud.kubelet.foundation.heimdall.table
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.javatime.timestamp
object PlayerPositionTable : Table("player_positions") {
val time = timestamp("time")
val world = uuid("world")
val player = uuid("player")
val x = double("x")
val y = double("y")
val z = double("z")
val pitch = double("pitch")
val yaw = double("yaw")
}