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
No known key found for this signature in database
GPG Key ID: C4E68E5647420E10
14 changed files with 293 additions and 3 deletions

View File

@ -13,18 +13,18 @@ import net.dv8tion.jda.api.entities.TextChannel
import net.dv8tion.jda.api.events.GenericEvent import net.dv8tion.jda.api.events.GenericEvent
import net.dv8tion.jda.api.events.ReadyEvent import net.dv8tion.jda.api.events.ReadyEvent
import net.dv8tion.jda.api.events.message.MessageReceivedEvent import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.hooks.EventListener import net.dv8tion.jda.api.hooks.EventListener as DiscordEventListener
import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component
import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.TextComponent
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.Listener import org.bukkit.event.Listener as BukkitEventListener
import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import java.awt.Color import java.awt.Color
import kotlin.io.path.inputStream import kotlin.io.path.inputStream
class FoundationBifrostPlugin : JavaPlugin(), EventListener, Listener { class FoundationBifrostPlugin : JavaPlugin(), DiscordEventListener, BukkitEventListener {
private lateinit var config: BifrostConfig private lateinit var config: BifrostConfig
private lateinit var jda: JDA private lateinit var jda: JDA
private var isDev = false private var isDev = false

View File

@ -0,0 +1,7 @@
dependencies {
implementation("org.postgresql:postgresql:42.3.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.36.2")
implementation("org.jetbrains.exposed:exposed-java-time:0.36.2")
implementation("com.zaxxer:HikariCP:5.0.0")
compileOnly(project(":foundation-core"))
}

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")
}

View File

@ -0,0 +1,11 @@
# Whether Heimdall should be enabled for tracking events.
enabled: false
# Database connection information.
db:
# JDBC URL
url: "jdbc:postgresql://localhost/foundation"
# JDBC Username
username: "foundation"
# JDBC Password
password: "foundation"

View File

@ -0,0 +1,20 @@
--
create extension if not exists "uuid-ossp";
--
create extension if not exists timescaledb;
--
create schema if not exists heimdall;
--
create table if not exists heimdall.player_positions (
time timestamp not null,
player uuid not null,
world uuid not null,
x double precision not null,
y double precision not null,
z double precision not null,
pitch double precision not null,
yaw double precision not null,
PRIMARY KEY (time, player, world)
);
--
select create_hypertable('heimdall.player_positions', 'time', 'player', 4, if_not_exists => TRUE);

View File

@ -0,0 +1,10 @@
name: Foundation-Heimdall
version: '${version}'
main: cloud.kubelet.foundation.heimdall.FoundationHeimdallPlugin
api-version: 1.18
prefix: Foundation-Heimdall
load: STARTUP
depend:
- Foundation
authors:
- kubelet

View File

@ -3,4 +3,5 @@ rootProject.name = "foundation"
include( include(
":foundation-core", ":foundation-core",
":foundation-bifrost", ":foundation-bifrost",
":foundation-heimdall",
) )