mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-03 21:41:32 +00:00
Gjallarhorn: Block Color Key and Render Pool Enhancements
This commit is contained in:
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
@ -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")
|
||||||
|
)
|
Reference in New Issue
Block a user