Gjallarhorn: Block Color Key and Render Pool Enhancements

This commit is contained in:
Kenneth Endfinger
2022-01-09 04:03:07 -05:00
parent 94d644916b
commit 2d429ae04d
6 changed files with 48 additions and 33 deletions

View File

@ -2,13 +2,14 @@ package cloud.kubelet.foundation.gjallarhorn.render
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey import cloud.kubelet.foundation.gjallarhorn.util.BlockColorKey
import cloud.kubelet.foundation.gjallarhorn.util.defaultBlockColorMap
import java.awt.Color import java.awt.Color
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
class BlockDiversityRenderer(val expanse: BlockExpanse, quadPixelSize: Int = defaultQuadPixelSize) : class BlockDiversityRenderer(val expanse: BlockExpanse, quadPixelSize: Int = defaultQuadPixelSize) :
BlockGridRenderer(quadPixelSize) { BlockGridRenderer(quadPixelSize) {
private val randomColorKey = RandomColorKey() private val blockColorKey = BlockColorKey(defaultBlockColorMap)
override fun render(map: BlockMap): BufferedImage = buildPixelQuadImage(expanse) { graphics, x, z -> override fun render(map: BlockMap): BufferedImage = buildPixelQuadImage(expanse) { graphics, x, z ->
val maybeYBlocks = map.blocks[x]?.get(z) val maybeYBlocks = map.blocks[x]?.get(z)
@ -22,7 +23,7 @@ class BlockDiversityRenderer(val expanse: BlockExpanse, quadPixelSize: Int = def
return@buildPixelQuadImage return@buildPixelQuadImage
} }
val color = randomColorKey.map(maxBlockState.type) val color = blockColorKey.map(maxBlockState.type)
setPixelQuad(graphics, x, z, color.rgb) setPixelQuad(graphics, x, z, color.rgb)
} }
} }

View File

@ -6,10 +6,10 @@ import java.time.Instant
data class BlockChangelogSlice(val from: Instant, val to: Instant, val relative: Duration) { data class BlockChangelogSlice(val from: Instant, val to: Instant, val relative: Duration) {
constructor(from: Instant, to: Instant) : this(from, to, Duration.ofMillis(to.toEpochMilli() - from.toEpochMilli())) constructor(from: Instant, to: Instant) : this(from, to, Duration.ofMillis(to.toEpochMilli() - from.toEpochMilli()))
fun changeResolutionTime(): Instant = to.minus(relative) val changeResolutionTime: Instant = to.minus(relative)
fun isTimeWithin(time: Instant) = time in from..to fun isTimeWithin(time: Instant) = time in from..to
fun isRelativeWithin(time: Instant) = time in changeResolutionTime()..to fun isRelativeWithin(time: Instant) = time in changeResolutionTime..to
fun split(): List<BlockChangelogSlice> { fun split(): List<BlockChangelogSlice> {
val half = relative.dividedBy(2) val half = relative.dividedBy(2)

View File

@ -16,12 +16,12 @@ class BlockMapRenderPool<T>(
private val playbackJobFutures = ConcurrentHashMap<BlockChangelogSlice, Future<*>>() private val playbackJobFutures = ConcurrentHashMap<BlockChangelogSlice, Future<*>>()
private val renderJobFutures = ConcurrentHashMap<BlockChangelogSlice, Future<*>>() private val renderJobFutures = ConcurrentHashMap<BlockChangelogSlice, Future<*>>()
fun submitPlaybackJob(slice: BlockChangelogSlice) { fun submitPlaybackJob(id: String, slice: BlockChangelogSlice) {
val future = threadPoolExecutor.submit { val future = threadPoolExecutor.submit {
try { try {
runPlaybackSlice(slice) runPlaybackSlice(id, slice)
} catch (e: Exception) { } catch (e: Exception) {
logger.error("Failed to run playback job for slice $slice", e) logger.error("Failed to run playback job for slice $id", e)
} }
} }
playbackJobFutures[slice] = future playbackJobFutures[slice] = future
@ -41,7 +41,7 @@ class BlockMapRenderPool<T>(
fun render(slices: List<BlockChangelogSlice>) { fun render(slices: List<BlockChangelogSlice>) {
for (slice in slices) { for (slice in slices) {
submitPlaybackJob(slice) submitPlaybackJob((slices.indexOf(slice) + 1).toString(), slice)
} }
for (future in playbackJobFutures.values) { for (future in playbackJobFutures.values) {
@ -55,16 +55,22 @@ class BlockMapRenderPool<T>(
} }
} }
private fun runPlaybackSlice(slice: BlockChangelogSlice) { private fun runPlaybackSlice(id: String, slice: BlockChangelogSlice) {
val start = System.currentTimeMillis()
val sliced = changelog.slice(slice) val sliced = changelog.slice(slice)
val tracker = BlockLogTracker(blockTrackMode) val tracker = BlockLogTracker(blockTrackMode)
tracker.replay(sliced) tracker.replay(sliced)
delegate.postProcessTracker(tracker)
if (tracker.isNotEmpty()) { if (tracker.isNotEmpty()) {
trackers[slice] = tracker trackers[slice] = tracker
} }
val end = System.currentTimeMillis()
val timeInMilliseconds = end - start
logger.info("Playback Completed for Slice $id in ${timeInMilliseconds}ms")
} }
interface RenderPoolDelegate<T> { interface RenderPoolDelegate<T> {
fun postProcessTracker(tracker: BlockLogTracker)
fun buildRenderJobs(pool: BlockMapRenderPool<T>, trackers: MutableMap<BlockChangelogSlice, BlockLogTracker>) fun buildRenderJobs(pool: BlockMapRenderPool<T>, trackers: MutableMap<BlockChangelogSlice, BlockLogTracker>)
} }

View File

@ -2,6 +2,7 @@ package cloud.kubelet.foundation.gjallarhorn.state
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.stream.Stream
class BlockMapTimelapse<T>(val trim: Pair<BlockCoordinate, BlockCoordinate>? = null) : class BlockMapTimelapse<T>(val trim: Pair<BlockCoordinate, BlockCoordinate>? = null) :
BlockMapRenderPool.RenderPoolDelegate<T> { BlockMapRenderPool.RenderPoolDelegate<T> {
@ -28,35 +29,23 @@ class BlockMapTimelapse<T>(val trim: Pair<BlockCoordinate, BlockCoordinate>? = n
minimumTimeInterval: Duration, minimumTimeInterval: Duration,
slices: List<BlockChangelogSlice> slices: List<BlockChangelogSlice>
): List<BlockChangelogSlice> { ): List<BlockChangelogSlice> {
return slices.flatMap { slice -> return slices.parallelStream().flatMap { slice ->
val count = changelog.countRelativeChangesInSlice(slice) val count = changelog.countRelativeChangesInSlice(slice)
if (count < targetChangeThreshold || if (count < targetChangeThreshold ||
slice.relative < minimumTimeInterval slice.relative < minimumTimeInterval
) { ) {
return@flatMap listOf(slice) return@flatMap Stream.of(slice)
} }
val split = slice.split() val split = slice.split()
return@flatMap splitChangelogSlicesWithThreshold(changelog, targetChangeThreshold, minimumTimeInterval, split) return@flatMap splitChangelogSlicesWithThreshold(changelog, targetChangeThreshold, minimumTimeInterval, split).parallelStream()
} }.toList()
} }
override fun buildRenderJobs( override fun buildRenderJobs(
pool: BlockMapRenderPool<T>, pool: BlockMapRenderPool<T>,
trackers: MutableMap<BlockChangelogSlice, BlockLogTracker> trackers: MutableMap<BlockChangelogSlice, BlockLogTracker>
) { ) {
if (trim != null) {
trackers.values.forEach { tracker ->
tracker.trimOutsideXAndZRange(trim.first, trim.second)
}
}
for ((slice, tracker) in trackers.entries.toList()) {
if (tracker.isEmpty()) {
trackers.remove(slice)
}
}
val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() } val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() }
val globalBlockOffset = BlockCoordinate.maxOf(allBlockOffsets) val globalBlockOffset = BlockCoordinate.maxOf(allBlockOffsets)
val allBlockMaxes = trackers.map { it.value.calculateMaxBlock() } val allBlockMaxes = trackers.map { it.value.calculateMaxBlock() }
@ -65,14 +54,16 @@ class BlockMapTimelapse<T>(val trim: Pair<BlockCoordinate, BlockCoordinate>? = n
val renderer = pool.rendererFactory(globalBlockExpanse) val renderer = pool.rendererFactory(globalBlockExpanse)
for ((slice, tracker) in trackers) { for ((slice, tracker) in trackers) {
if (tracker.isEmpty()) {
continue
}
pool.submitRenderJob(slice) { pool.submitRenderJob(slice) {
val map = tracker.buildBlockMap(globalBlockExpanse.offset) val map = tracker.buildBlockMap(globalBlockExpanse.offset)
renderer.render(map) renderer.render(map)
} }
} }
} }
override fun postProcessTracker(tracker: BlockLogTracker) {
if (trim != null) {
tracker.trimOutsideXAndZRange(trim.first, trim.second)
}
}
} }

View File

@ -3,10 +3,10 @@ package cloud.kubelet.foundation.gjallarhorn.util
import java.awt.Color import java.awt.Color
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class RandomColorKey { class BlockColorKey(assigned: Map<String, Color>) {
private val colors = ConcurrentHashMap<String, Color>() private val colors = ConcurrentHashMap(assigned)
fun map(key: String) = colors.computeIfAbsent(key) { findUniqueColor() } fun map(key: String): Color = colors.computeIfAbsent(key) { findUniqueColor() }
private fun findUniqueColor(): Color { private fun findUniqueColor(): Color {
var random = randomColor() var random = randomColor()

View File

@ -0,0 +1,17 @@
package cloud.kubelet.foundation.gjallarhorn.util
import java.awt.Color
val defaultBlockColorMap = mapOf<String, Color>(
"minecraft:air" to Color.black,
"minecraft:dirt" to Color.decode("#9b7653"),
"minecraft:farmland" to Color.decode("#5d3f2a"),
"minecraft:stone" to Color.decode("#787366"),
"minecraft:cobblestone" to Color.decode("#c4bca7"),
"minecraft:wheat" to Color.decode("#9e884c"),
"minecraft:carrots" to Color.decode("#f89d40"),
"minecraft:stone_brick_stairs" to Color.decode("#b8a18c"),
"minecraft:dirt_path" to Color.decode("#8f743d"),
"minecraft:deepslate_tiles" to Color.decode("#49494b"),
"minecraft:spruce_planks" to Color.decode("#60492d")
)