diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt index 9f0a6e0..e6ff93e 100644 --- a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt @@ -7,20 +7,25 @@ import cloud.kubelet.foundation.heimdall.buffer.EventBuffer import cloud.kubelet.foundation.heimdall.event.BlockBreak import cloud.kubelet.foundation.heimdall.event.BlockPlace import cloud.kubelet.foundation.heimdall.event.PlayerPosition +import cloud.kubelet.foundation.heimdall.event.PlayerSession import cloud.kubelet.foundation.heimdall.model.HeimdallConfig import com.charleskorn.kaml.Yaml import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.block.BlockPlaceEvent -import org.bukkit.event.entity.EntityDeathEvent +import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerMoveEvent +import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.plugin.java.JavaPlugin import org.jetbrains.exposed.sql.Database import org.postgresql.Driver -import java.lang.Exception +import java.time.Instant +import java.util.* +import java.util.concurrent.ConcurrentHashMap import kotlin.io.path.inputStream class FoundationHeimdallPlugin : JavaPlugin(), Listener { @@ -31,6 +36,8 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener { private val buffer = EventBuffer() private val bufferFlushThread = BufferFlushThread(this, buffer) + private val playerJoinTimes = ConcurrentHashMap() + override fun onEnable() { val foundation = server.pluginManager.getPlugin("Foundation") as FoundationCorePlugin @@ -91,7 +98,25 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener { @EventHandler fun onBlockBroken(event: BlockBreakEvent) = buffer.push(BlockBreak(event)) + @EventHandler + fun onPlayerJoin(event: PlayerJoinEvent) { + playerJoinTimes[event.player.uniqueId] = Instant.now() + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerQuit(event: PlayerQuitEvent) { + val startTime = playerJoinTimes.remove(event.player.uniqueId) ?: return + val endTime = Instant.now() + buffer.push(PlayerSession(event.player.uniqueId, event.player.name, startTime, endTime)) + } + override fun onDisable() { bufferFlushThread.stop() + val endTime = Instant.now() + for (playerId in playerJoinTimes.keys().toList()) { + val startTime = playerJoinTimes.remove(playerId) ?: continue + buffer.push(PlayerSession(playerId, server.getPlayer(playerId)?.name ?: "__unknown__", startTime, endTime)) + } + bufferFlushThread.flush() } } diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/buffer/BufferFlushThread.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/buffer/BufferFlushThread.kt index 4f6eb85..29fcf34 100644 --- a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/buffer/BufferFlushThread.kt +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/buffer/BufferFlushThread.kt @@ -6,22 +6,14 @@ import java.util.concurrent.atomic.AtomicBoolean class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventBuffer) { private val running = AtomicBoolean(false) + private var thread: Thread? = null 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) - } + flush() Thread.sleep(5000) } plugin.slF4JLogger.info("Buffer Flusher Stopped") @@ -29,9 +21,24 @@ class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventB thread.name = "Heimdall Buffer Flush" thread.isDaemon = false thread.start() + this.thread = thread } fun stop() { running.set(false) + thread?.join() + } + + fun flush() { + 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) + } } } diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt index cf6f9c4..6332a0f 100644 --- a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt @@ -15,7 +15,7 @@ class BlockPlace( val location: Location, val material: Material, val timestamp: Instant = Instant.now() - ) : HeimdallEvent() { +) : HeimdallEvent() { constructor(event: BlockPlaceEvent) : this(event.player.uniqueId, event.block.location, event.block.type) override fun store(transaction: Transaction) { diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/PlayerSession.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/PlayerSession.kt new file mode 100644 index 0000000..80af280 --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/PlayerSession.kt @@ -0,0 +1,25 @@ +package cloud.kubelet.foundation.heimdall.event + +import cloud.kubelet.foundation.heimdall.table.PlayerSessionTable +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.insert +import java.time.Instant +import java.util.* + +class PlayerSession( + val playerUniqueIdentity: UUID, + val playerName: String, + val startTimeInstant: Instant, + val endTimeInstant: Instant +) : HeimdallEvent() { + override fun store(transaction: Transaction) { + transaction.apply { + PlayerSessionTable.insert { + it[player] = playerUniqueIdentity + it[name] = playerName + it[startTime] = startTimeInstant + it[endTime] = endTimeInstant + } + } + } +} diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/table/PlayerSessionTable.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/table/PlayerSessionTable.kt new file mode 100644 index 0000000..2f1cdab --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/table/PlayerSessionTable.kt @@ -0,0 +1,11 @@ +package cloud.kubelet.foundation.heimdall.table + +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.javatime.timestamp + +object PlayerSessionTable : Table("player_sessions") { + val player = uuid("player") + val name = text("name") + val startTime = timestamp("start") + val endTime = timestamp("end") +} diff --git a/foundation-heimdall/src/main/resources/init.sql b/foundation-heimdall/src/main/resources/init.sql index 88f41d3..79c140d 100644 --- a/foundation-heimdall/src/main/resources/init.sql +++ b/foundation-heimdall/src/main/resources/init.sql @@ -44,3 +44,13 @@ create table if not exists heimdall.block_places ( ); -- select create_hypertable('heimdall.block_places', 'time', 'player', 4, if_not_exists => TRUE); +-- +create table if not exists heimdall.player_sessions ( + player uuid not null, + name text not null, + "start" timestamp not null, + "end" timestamp not null, + PRIMARY KEY (player, start) +); +-- +select create_hypertable('heimdall.player_sessions', 'start', 'player', 4, if_not_exists => TRUE);