mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-03 21:41:32 +00:00
Gjallarhorn: Implement render loop.
This commit is contained in:
@ -1,15 +1,13 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.commands
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.render.BlockDiversityRenderer
|
||||
import cloud.kubelet.foundation.gjallarhorn.render.BlockHeightMapRenderer
|
||||
import cloud.kubelet.foundation.gjallarhorn.render.PlayerLocationShareRenderer
|
||||
import cloud.kubelet.foundation.gjallarhorn.render.BlockImageRenderer
|
||||
import cloud.kubelet.foundation.gjallarhorn.render.*
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.*
|
||||
import cloud.kubelet.foundation.gjallarhorn.util.compose
|
||||
import cloud.kubelet.foundation.gjallarhorn.util.savePngFile
|
||||
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import com.github.ajalt.clikt.core.requireObject
|
||||
import com.github.ajalt.clikt.parameters.options.default
|
||||
import com.github.ajalt.clikt.parameters.options.flag
|
||||
import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.options.required
|
||||
@ -25,6 +23,7 @@ import java.awt.Font
|
||||
import java.awt.font.TextLayout
|
||||
import java.awt.image.BufferedImage
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
|
||||
class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name = "block-change-timelapse") {
|
||||
@ -47,15 +46,33 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
||||
private val fromCoordinate by option("--trim-from", help = "Trim From Coordinate")
|
||||
private val toCoordinate by option("--trim-to", help = "Trim To Coordinate")
|
||||
|
||||
private val parallelPoolSize by option("--pool-size", help = "Task Pool Size").int().default(8)
|
||||
private val inMemoryRender by option("--in-memory-render", help = "Render Images to Memory").flag()
|
||||
private val shouldRenderLoop by option("--loop-render", help = "Loop Render").flag()
|
||||
private val quadPixelNoop by option("--quad-pixel-noop", help = "Disable Quad Pixel Render").flag()
|
||||
|
||||
private val logger = LoggerFactory.getLogger(BlockChangeTimelapseCommand::class.java)
|
||||
|
||||
override fun run() {
|
||||
val threadPoolExecutor = ScheduledThreadPoolExecutor(16)
|
||||
if (quadPixelNoop) {
|
||||
BlockGridRenderer.globalQuadPixelNoop = true
|
||||
}
|
||||
val threadPoolExecutor = ScheduledThreadPoolExecutor(parallelPoolSize)
|
||||
if (shouldRenderLoop) {
|
||||
while (true) {
|
||||
perform(threadPoolExecutor)
|
||||
}
|
||||
} else {
|
||||
perform(threadPoolExecutor)
|
||||
}
|
||||
threadPoolExecutor.shutdown()
|
||||
}
|
||||
|
||||
private fun perform(threadPoolExecutor: ScheduledThreadPoolExecutor) {
|
||||
val trim = maybeBuildTrim()
|
||||
val filter = compose(
|
||||
combine = { a, b -> a and b },
|
||||
{ trim?.first?.x != null } to { BlockChangeView.x greaterEq trim!!.first.x },
|
||||
{ trim?.first?.x != null } to { BlockChangeView.x greaterEq trim!!.first.x },
|
||||
{ trim?.first?.z != null } to { BlockChangeView.z greaterEq trim!!.first.z },
|
||||
{ trim?.second?.x != null } to { BlockChangeView.x lessEq trim!!.second.x },
|
||||
{ trim?.second?.z != null } to { BlockChangeView.z lessEq trim!!.second.z }
|
||||
@ -77,6 +94,12 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
||||
|
||||
val imagePadCount = slices.size.toString().length
|
||||
|
||||
val inMemoryPool = if (inMemoryRender) {
|
||||
ConcurrentHashMap<ChangelogSlice, BufferedImage>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val pool = BlockMapRenderPool(
|
||||
changelog = changelog,
|
||||
blockTrackMode = if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete,
|
||||
@ -96,16 +119,19 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
||||
layout.draw(graphics, 60f, 60f)
|
||||
graphics.dispose()
|
||||
val index = slices.indexOf(slice) + 1
|
||||
val suffix = "-${index.toString().padStart(imagePadCount, '0')}"
|
||||
result.savePngFile("${render.id}${suffix}.png")
|
||||
if (inMemoryRender) {
|
||||
inMemoryPool?.put(slice, result)
|
||||
} else {
|
||||
val suffix = "-${index.toString().padStart(imagePadCount, '0')}"
|
||||
result.savePngFile("${render.id}${suffix}.png")
|
||||
}
|
||||
logger.info("Rendered Timelapse Slice $index")
|
||||
}
|
||||
|
||||
pool.render(slices)
|
||||
threadPoolExecutor.shutdown()
|
||||
}
|
||||
|
||||
fun maybeBuildTrim(): Pair<BlockCoordinate, BlockCoordinate>? {
|
||||
private fun maybeBuildTrim(): Pair<BlockCoordinate, BlockCoordinate>? {
|
||||
if (fromCoordinate == null || toCoordinate == null) {
|
||||
return null
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.render
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockStateMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.ChangelogSlice
|
||||
import cloud.kubelet.foundation.gjallarhorn.util.BlockColorKey
|
||||
import cloud.kubelet.foundation.gjallarhorn.util.defaultBlockColorMap
|
||||
@ -12,7 +12,7 @@ class BlockDiversityRenderer(val expanse: BlockExpanse, quadPixelSize: Int = def
|
||||
BlockGridRenderer(quadPixelSize) {
|
||||
private val blockColorKey = BlockColorKey(defaultBlockColorMap)
|
||||
|
||||
override fun render(slice: ChangelogSlice, map: BlockMap): BufferedImage = buildPixelQuadImage(expanse) { graphics, x, z ->
|
||||
override fun render(slice: ChangelogSlice, map: BlockStateMap): BufferedImage = buildPixelQuadImage(expanse) { graphics, x, z ->
|
||||
val maybeYBlocks = map.blocks[x]?.get(z)
|
||||
if (maybeYBlocks == null) {
|
||||
setPixelQuad(graphics, x, z, Color.white)
|
||||
|
@ -8,6 +8,9 @@ import java.awt.image.BufferedImage
|
||||
|
||||
abstract class BlockGridRenderer(val quadPixelSize: Int = defaultQuadPixelSize) : BlockImageRenderer {
|
||||
protected fun setPixelQuad(graphics: Graphics2D, x: Long, z: Long, color: Color) {
|
||||
if (globalQuadPixelNoop) {
|
||||
return
|
||||
}
|
||||
drawSquare(graphics, x * quadPixelSize, z * quadPixelSize, quadPixelSize.toLong(), color)
|
||||
}
|
||||
|
||||
@ -39,5 +42,6 @@ abstract class BlockGridRenderer(val quadPixelSize: Int = defaultQuadPixelSize)
|
||||
|
||||
companion object {
|
||||
const val defaultQuadPixelSize = 4
|
||||
var globalQuadPixelNoop = false
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.render
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockStateMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.ChangelogSlice
|
||||
import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp
|
||||
import java.awt.image.BufferedImage
|
||||
|
||||
class BlockHeightMapRenderer(val expanse: BlockExpanse, quadPixelSize: Int = defaultQuadPixelSize) :
|
||||
BlockHeatMapRenderer(quadPixelSize) {
|
||||
override fun render(slice: ChangelogSlice, map: BlockMap): BufferedImage {
|
||||
override fun render(slice: ChangelogSlice, map: BlockStateMap): BufferedImage {
|
||||
val yMin = map.blocks.minOf { xSection -> xSection.value.minOf { zSection -> zSection.value.minOf { it.key } } }
|
||||
val yMax = map.blocks.maxOf { xSection -> xSection.value.maxOf { zSection -> zSection.value.maxOf { it.key } } }
|
||||
val clamp = FloatClamp(yMin, yMax)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.render
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockStateMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.ChangelogSlice
|
||||
|
||||
interface BlockMapRenderer<T> {
|
||||
fun render(slice: ChangelogSlice, map: BlockMap): T
|
||||
fun render(slice: ChangelogSlice, map: BlockStateMap): T
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.render
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockCoordinate
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.ChangelogSlice
|
||||
import cloud.kubelet.foundation.gjallarhorn.state.*
|
||||
import cloud.kubelet.foundation.gjallarhorn.util.BlockColorKey
|
||||
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
@ -12,37 +9,44 @@ import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.awt.Color
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.*
|
||||
|
||||
class PlayerLocationShareRenderer(
|
||||
val expanse: BlockExpanse,
|
||||
val db: Database,
|
||||
quadPixelSize: Int = defaultQuadPixelSize) : BlockGridRenderer(quadPixelSize) {
|
||||
quadPixelSize: Int = defaultQuadPixelSize
|
||||
) : BlockGridRenderer(quadPixelSize) {
|
||||
private val colorKey = BlockColorKey(mapOf())
|
||||
|
||||
override fun render(slice: ChangelogSlice, map: BlockMap): BufferedImage {
|
||||
override fun render(slice: ChangelogSlice, map: BlockStateMap): BufferedImage {
|
||||
val start = slice.relativeChangeRange.start
|
||||
val end = slice.relativeChangeRange.endInclusive
|
||||
|
||||
val playersToUniquePositions = transaction(db) {
|
||||
val playerSparseMap = BlockCoordinateSparseMap<MutableList<UUID>>()
|
||||
val allPlayerIds = HashSet<UUID>()
|
||||
transaction(db) {
|
||||
PlayerPositionTable.select {
|
||||
(PlayerPositionTable.time greater start) and
|
||||
(PlayerPositionTable.time lessEq end)
|
||||
}.map {
|
||||
}.forEach {
|
||||
val x = it[PlayerPositionTable.x].toLong()
|
||||
val y = it[PlayerPositionTable.y].toLong()
|
||||
val z = it[PlayerPositionTable.z].toLong()
|
||||
val coordinate = expanse.offset.applyAsOffset(BlockCoordinate(x, y, z))
|
||||
it[PlayerPositionTable.player] to coordinate
|
||||
}.distinct()
|
||||
val player = it[PlayerPositionTable.player]
|
||||
playerSparseMap.createOrModify(
|
||||
coordinate,
|
||||
create = { mutableListOf(player) },
|
||||
modify = { players -> players.add(player) })
|
||||
allPlayerIds.add(player)
|
||||
}
|
||||
}
|
||||
|
||||
val colorOfPlayers = playersToUniquePositions.map { it.first }
|
||||
.distinct()
|
||||
.associateWith { colorKey.map(it.toString()) }
|
||||
val colorOfPlayers = allPlayerIds.associateWith { colorKey.map(it.toString()) }
|
||||
|
||||
return buildPixelQuadImage(expanse) { g, x, z ->
|
||||
val players = playersToUniquePositions.filter { it.second.x == x && it.second.z == z }.map { it.first }.distinct()
|
||||
if (players.isNotEmpty()) {
|
||||
val players = playerSparseMap.getVerticalSection(x, z)?.flatMap { it.value }?.distinct()
|
||||
if (players != null) {
|
||||
setPixelQuad(g, x, z, colorOfPlayers[players.first()]!!)
|
||||
} else {
|
||||
setPixelQuad(g, x, z, Color.white)
|
||||
|
@ -0,0 +1,28 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.state
|
||||
|
||||
import java.util.*
|
||||
|
||||
open class BlockCoordinateSparseMap<T> {
|
||||
val blocks = TreeMap<Long, TreeMap<Long, TreeMap<Long, T>>>()
|
||||
|
||||
fun get(position: BlockCoordinate): T? = blocks[position.x]?.get(position.z)?.get(position.z)
|
||||
fun getVerticalSection(x: Long, z: Long): Map<Long, T>? = blocks[x]?.get(z)
|
||||
fun getXSection(x: Long): Map<Long, Map<Long, T>>? = blocks[x]
|
||||
|
||||
fun put(position: BlockCoordinate, value: T) {
|
||||
blocks.getOrPut(position.x) {
|
||||
TreeMap()
|
||||
}.getOrPut(position.z) {
|
||||
TreeMap()
|
||||
}[position.y] = value
|
||||
}
|
||||
|
||||
fun createOrModify(position: BlockCoordinate, create: () -> T, modify: (T) -> Unit) {
|
||||
val existing = get(position)
|
||||
if (existing == null) {
|
||||
put(position, create())
|
||||
} else {
|
||||
modify(existing)
|
||||
}
|
||||
}
|
||||
}
|
@ -39,8 +39,8 @@ class BlockLogTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOn
|
||||
fun isEmpty() = blocks.isEmpty()
|
||||
fun isNotEmpty() = !isEmpty()
|
||||
|
||||
fun buildBlockMap(offset: BlockCoordinate = BlockCoordinate.zero): BlockMap {
|
||||
val map = BlockMap()
|
||||
fun buildBlockMap(offset: BlockCoordinate = BlockCoordinate.zero): BlockStateMap {
|
||||
val map = BlockStateMap()
|
||||
blocks.forEach { (position, state) ->
|
||||
val realPosition = offset.applyAsOffset(position)
|
||||
map.put(realPosition, state)
|
||||
|
@ -1,15 +0,0 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.state
|
||||
|
||||
import java.util.*
|
||||
|
||||
class BlockMap {
|
||||
val blocks = TreeMap<Long, TreeMap<Long, TreeMap<Long, BlockState>>>()
|
||||
|
||||
fun put(position: BlockCoordinate, state: BlockState) {
|
||||
blocks.getOrPut(position.x) {
|
||||
TreeMap()
|
||||
}.getOrPut(position.z) {
|
||||
TreeMap()
|
||||
}[position.y] = state
|
||||
}
|
||||
}
|
@ -2,7 +2,9 @@ package cloud.kubelet.foundation.gjallarhorn.state
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.render.BlockMapRenderer
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
|
||||
class BlockMapRenderPool<T>(
|
||||
val changelog: BlockChangelog,
|
||||
@ -51,7 +53,11 @@ class BlockMapRenderPool<T>(
|
||||
delegate.onAllPlaybackComplete(this, trackers)
|
||||
|
||||
for (future in renderJobFutures.values) {
|
||||
future.get()
|
||||
try {
|
||||
future.get()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to render slice.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.state
|
||||
|
||||
class BlockStateMap : BlockCoordinateSparseMap<BlockState>()
|
Reference in New Issue
Block a user