mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Gjallarhorn: Parallel Rendering and Quad Image Improvements
This commit is contained in:
		@ -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) {
 | 
				
			||||||
 | 
					        trackerPool.submit {
 | 
				
			||||||
          val index = intervals.indexOf(time) + 1
 | 
					          val index = intervals.indexOf(time) + 1
 | 
				
			||||||
          val tracker = buildTrackerState(time, "Timelapse-${index}")
 | 
					          val tracker = buildTrackerState(time, "Timelapse-${index}")
 | 
				
			||||||
          if (tracker.isEmpty()) {
 | 
					          if (tracker.isEmpty()) {
 | 
				
			||||||
          continue
 | 
					            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()
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user