mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-02 13:10:55 +00:00
Gjallarhorn: Parallel Rendering and Quad Image Improvements
This commit is contained in:
parent
bc2d3e28ae
commit
10cf0cadac
@ -3,6 +3,9 @@ package cloud.kubelet.foundation.gjallarhorn
|
|||||||
import com.github.ajalt.clikt.core.CliktCommand
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
import com.github.ajalt.clikt.parameters.options.default
|
import com.github.ajalt.clikt.parameters.options.default
|
||||||
import com.github.ajalt.clikt.parameters.options.option
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
|
import com.github.ajalt.clikt.parameters.types.int
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
|
||||||
class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) {
|
class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) {
|
||||||
@ -15,8 +18,16 @@ class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) {
|
|||||||
private val jdbcConnectionPassword by option("-p", "--connection-password", help = "JDBC Connection Password")
|
private val jdbcConnectionPassword by option("-p", "--connection-password", help = "JDBC Connection Password")
|
||||||
.default("jdbc:postgresql://localhost/foundation")
|
.default("jdbc:postgresql://localhost/foundation")
|
||||||
|
|
||||||
|
private val dbPoolSize by option("--db-pool-size", help = "JDBC Pool Size").int().default(8)
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val db = Database.connect(jdbcConnectionUrl, user = jdbcConnectionUsername, password = jdbcConnectionPassword)
|
val pool = HikariDataSource(HikariConfig().apply {
|
||||||
|
jdbcUrl = jdbcConnectionUrl
|
||||||
|
username = jdbcConnectionUsername
|
||||||
|
password = jdbcConnectionPassword
|
||||||
|
maximumPoolSize = dbPoolSize
|
||||||
|
})
|
||||||
|
val db = Database.connect(pool)
|
||||||
currentContext.findOrSetObject { db }
|
currentContext.findOrSetObject { db }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package cloud.kubelet.foundation.gjallarhorn.commands
|
|||||||
|
|
||||||
import cloud.kubelet.foundation.gjallarhorn.compose
|
import cloud.kubelet.foundation.gjallarhorn.compose
|
||||||
import cloud.kubelet.foundation.gjallarhorn.render.*
|
import cloud.kubelet.foundation.gjallarhorn.render.*
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.savePngFile
|
import cloud.kubelet.foundation.gjallarhorn.util.savePngFile
|
||||||
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
|
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
|
||||||
import com.github.ajalt.clikt.core.CliktCommand
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
@ -18,12 +19,15 @@ import org.slf4j.LoggerFactory
|
|||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") {
|
class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") {
|
||||||
private val db by requireObject<Database>()
|
private val db by requireObject<Database>()
|
||||||
private val exactTimeAsString by option("--time", help = "Replay Time")
|
private val exactTimeAsString by option("--time", help = "Replay Time")
|
||||||
private val timeLapseMode by option("--timelapse", help = "Timelapse Mode").enum<TimeLapseMode> { it.id }
|
private val timelapseMode by option("--timelapse", help = "Timelapse Mode").enum<TimelapseMode> { it.id }
|
||||||
private val render by option("--render", help = "Render Top Down Image").enum<RenderType> { it.id }.required()
|
private val render by option("--render", help = "Render Top Down Image").enum<RenderType> { it.id }.required()
|
||||||
|
|
||||||
private val considerAirBlocks by option("--consider-air-blocks", help = "Enable Air Block Consideration").flag()
|
private val considerAirBlocks by option("--consider-air-blocks", help = "Enable Air Block Consideration").flag()
|
||||||
@ -31,7 +35,7 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
private val logger = LoggerFactory.getLogger(BlockLogReplay::class.java)
|
private val logger = LoggerFactory.getLogger(BlockLogReplay::class.java)
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (timeLapseMode != null) {
|
if (timelapseMode != null) {
|
||||||
val (start, end) = transaction(db) {
|
val (start, end) = transaction(db) {
|
||||||
val minTimeColumn = BlockChangeView.time.min().notNull
|
val minTimeColumn = BlockChangeView.time.min().notNull
|
||||||
val maxTimeColumn = BlockChangeView.time.max().notNull
|
val maxTimeColumn = BlockChangeView.time.max().notNull
|
||||||
@ -43,40 +47,60 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
var current = start
|
var current = start
|
||||||
while (!current.isAfter(end)) {
|
while (!current.isAfter(end)) {
|
||||||
intervals.add(current)
|
intervals.add(current)
|
||||||
current = current.plus(timeLapseMode!!.interval)
|
current = current.plus(timelapseMode!!.interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
val trackers = mutableMapOf<Int, BlockStateTracker>()
|
val trackerPool = ScheduledThreadPoolExecutor(8)
|
||||||
|
val trackers = ConcurrentHashMap<Int, BlockStateTracker>()
|
||||||
for (time in intervals) {
|
for (time in intervals) {
|
||||||
val index = intervals.indexOf(time) + 1
|
trackerPool.submit {
|
||||||
val tracker = buildTrackerState(time, "Timelapse-${index}")
|
val index = intervals.indexOf(time) + 1
|
||||||
if (tracker.isEmpty()) {
|
val tracker = buildTrackerState(time, "Timelapse-${index}")
|
||||||
continue
|
if (tracker.isEmpty()) {
|
||||||
|
return@submit
|
||||||
|
}
|
||||||
|
trackers[index] = tracker
|
||||||
}
|
}
|
||||||
trackers[index] = tracker
|
|
||||||
}
|
}
|
||||||
|
trackerPool.shutdown()
|
||||||
|
if (!trackerPool.awaitTermination(12, TimeUnit.HOURS)) {
|
||||||
|
throw RuntimeException("Failed to wait for tracker pool.")
|
||||||
|
}
|
||||||
|
logger.info("State Tracking Completed")
|
||||||
val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() }
|
val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() }
|
||||||
val globalBlockOffset = BlockPosition.maxOf(allBlockOffsets.asSequence())
|
val globalBlockOffset = BlockPosition.maxOf(allBlockOffsets.asSequence())
|
||||||
val allBlockMaxes = trackers.map { it.value.calculateZeroBlockOffset() }
|
val allBlockMaxes = trackers.map { it.value.calculateZeroBlockOffset() }
|
||||||
val globalBlockMax = BlockPosition.maxOf(allBlockMaxes.asSequence())
|
val globalBlockMax = BlockPosition.maxOf(allBlockMaxes.asSequence())
|
||||||
val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax)
|
val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax)
|
||||||
|
|
||||||
|
logger.info("Calculations Completed")
|
||||||
|
|
||||||
|
val renderState = render.createState()
|
||||||
|
val renderPool = ScheduledThreadPoolExecutor(8)
|
||||||
for ((i, tracker) in trackers.entries) {
|
for ((i, tracker) in trackers.entries) {
|
||||||
saveRenderImage(tracker, globalBlockExpanse, "-${i}")
|
renderPool.submit {
|
||||||
|
val count = trackers.size.toString().length
|
||||||
|
saveRenderImage(renderState, tracker, globalBlockExpanse, "-${i.toString().padStart(count, '0')}")
|
||||||
|
logger.info("Rendered Timelapse $i")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
renderPool.shutdown()
|
||||||
|
if (!renderPool.awaitTermination(12, TimeUnit.HOURS)) {
|
||||||
|
throw RuntimeException("Failed to wait for render pool.")
|
||||||
|
}
|
||||||
|
logger.info("Rendering Completed")
|
||||||
} else {
|
} else {
|
||||||
val time = if (exactTimeAsString != null) Instant.parse(exactTimeAsString) else null
|
val time = if (exactTimeAsString != null) Instant.parse(exactTimeAsString) else null
|
||||||
val tracker = buildTrackerState(time, "Single-Time")
|
val tracker = buildTrackerState(time, "Single-Time")
|
||||||
val expanse = BlockExpanse.offsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock())
|
val expanse = BlockExpanse.offsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock())
|
||||||
saveRenderImage(tracker, expanse)
|
saveRenderImage(render.createState(), tracker, expanse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveRenderImage(tracker: BlockStateTracker, expanse: BlockExpanse, suffix: String = "") {
|
fun saveRenderImage(renderState: Any, tracker: BlockStateTracker, expanse: BlockExpanse, suffix: String = "") {
|
||||||
val state = BlockStateImage()
|
val state = BlockStateImage()
|
||||||
tracker.populateStateImage(state, expanse.offset)
|
tracker.populateStateImage(state, expanse.offset)
|
||||||
val image = render.renderBufferedImage(state, expanse)
|
val image = render.renderBufferedImage(renderState, state, expanse)
|
||||||
image.savePngFile("${render.id}${suffix}.png")
|
image.savePngFile("${render.id}${suffix}.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,12 +142,18 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
return tracker
|
return tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class RenderType(val id: String, val renderBufferedImage: (BlockStateImage, BlockExpanse) -> BufferedImage) {
|
@Suppress("unused")
|
||||||
TopDown("top-down", { image, expanse -> image.buildTopDownImage(expanse) }),
|
enum class RenderType(val id: String, val createState: () -> Any, val renderBufferedImage: (Any, BlockStateImage, BlockExpanse) -> BufferedImage) {
|
||||||
HeightMap("height-map", { image, expanse -> image.buildHeightMapImage(expanse) })
|
TopDown("top-down",
|
||||||
|
{ TopDownState(RandomColorKey()) },
|
||||||
|
{ state, image, expanse -> image.buildTopDownImage(expanse, (state as TopDownState).randomColorKey) }),
|
||||||
|
HeightMap("height-map", { }, { _, image, expanse -> image.buildHeightMapImage(expanse) })
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class TimeLapseMode(val id: String, val interval: Duration) {
|
class TopDownState(val randomColorKey: RandomColorKey)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
enum class TimelapseMode(val id: String, val interval: Duration) {
|
||||||
ByHour("hours", Duration.ofHours(1)),
|
ByHour("hours", Duration.ofHours(1)),
|
||||||
ByDay("days", Duration.ofDays(1))
|
ByDay("days", Duration.ofDays(1))
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import cloud.kubelet.foundation.gjallarhorn.util.ColorGradient
|
|||||||
import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp
|
import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
|
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
|
import java.awt.Rectangle
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -18,8 +19,7 @@ class BlockStateImage {
|
|||||||
}[position.y] = state
|
}[position.y] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildTopDownImage(expanse: BlockExpanse): BufferedImage {
|
fun buildTopDownImage(expanse: BlockExpanse, randomColorKey: RandomColorKey): BufferedImage {
|
||||||
val colorKey = RandomColorKey()
|
|
||||||
return buildPixelQuadImage(expanse) { x, z ->
|
return buildPixelQuadImage(expanse) { x, z ->
|
||||||
val maybeYBlocks = blocks[x]?.get(z)
|
val maybeYBlocks = blocks[x]?.get(z)
|
||||||
if (maybeYBlocks == null) {
|
if (maybeYBlocks == null) {
|
||||||
@ -32,7 +32,7 @@ class BlockStateImage {
|
|||||||
return@buildPixelQuadImage
|
return@buildPixelQuadImage
|
||||||
}
|
}
|
||||||
|
|
||||||
val color = colorKey.map(maxBlockState.type)
|
val color = randomColorKey.map(maxBlockState.type)
|
||||||
setPixelQuad(x, z, color.rgb)
|
setPixelQuad(x, z, color.rgb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,16 +59,20 @@ class BlockStateImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun BufferedImage.setPixelQuad(x: Long, z: Long, rgb: Int) {
|
private fun BufferedImage.setPixelQuad(x: Long, z: Long, rgb: Int) {
|
||||||
setRGB(x.toInt() * 2, z.toInt() * 2, rgb)
|
drawSquare(x * quadImageSize, z * quadImageSize, quadImageSize.toLong(), rgb)
|
||||||
setRGB((x.toInt() * 2) + 1, z.toInt() * 2, rgb)
|
}
|
||||||
setRGB(x.toInt() * 2, (z.toInt() * 2) + 1, rgb)
|
|
||||||
setRGB((x.toInt() * 2) + 1, (z.toInt() * 2) + 1, rgb)
|
private fun BufferedImage.drawSquare(x: Long, y: Long, side: Long, rgb: Int) {
|
||||||
|
val graphics = createGraphics()
|
||||||
|
graphics.color = Color(rgb)
|
||||||
|
graphics.fill(Rectangle(x.toInt(), y.toInt(), side.toInt(), side.toInt()))
|
||||||
|
graphics.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPixelQuadImage(expanse: BlockExpanse, callback: BufferedImage.(Long, Long) -> Unit): BufferedImage {
|
private fun buildPixelQuadImage(expanse: BlockExpanse, callback: BufferedImage.(Long, Long) -> Unit): BufferedImage {
|
||||||
val width = expanse.size.x
|
val width = expanse.size.x
|
||||||
val height = expanse.size.z
|
val height = expanse.size.z
|
||||||
val bufferedImage = BufferedImage(width.toInt() * 2, height.toInt() * 2, BufferedImage.TYPE_4BYTE_ABGR)
|
val bufferedImage = BufferedImage(width.toInt() * quadImageSize, height.toInt() * quadImageSize, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
for (x in 0 until width) {
|
for (x in 0 until width) {
|
||||||
for (z in 0 until height) {
|
for (z in 0 until height) {
|
||||||
@ -77,4 +81,8 @@ class BlockStateImage {
|
|||||||
}
|
}
|
||||||
return bufferedImage
|
return bufferedImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val quadImageSize = 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.util
|
package cloud.kubelet.foundation.gjallarhorn.util
|
||||||
|
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class RandomColorKey {
|
class RandomColorKey {
|
||||||
private val colors = mutableMapOf<String, Color>()
|
private val colors = ConcurrentHashMap<String, Color>()
|
||||||
|
|
||||||
fun map(key: String) = colors.getOrPut(key) { findUniqueColor() }
|
fun map(key: String) = colors.computeIfAbsent(key) { findUniqueColor() }
|
||||||
|
|
||||||
private fun findUniqueColor(): Color {
|
private fun findUniqueColor(): Color {
|
||||||
var random = randomColor()
|
var random = randomColor()
|
||||||
|
Loading…
Reference in New Issue
Block a user