even more working - generate zoomed tiles (but also it doesn't work - it stops making new tiles at some point).
This commit is contained in:
parent
5a59b3bd6d
commit
1af5912477
3 changed files with 245 additions and 70 deletions
|
|
@ -78,6 +78,6 @@ val kafkatorioEventsServerKafkaForceReset by tasks.registering(Exec::class) {
|
||||||
commandLine = parseSpaceSeparatedArgs(""" docker exec -d kafka $cmd """)
|
commandLine = parseSpaceSeparatedArgs(""" docker exec -d kafka $cmd """)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.run.configure {
|
//tasks.run.configure {
|
||||||
dependsOn(kafkatorioEventsServerKafkaForceReset)
|
// dependsOn(kafkatorioEventsServerKafkaForceReset)
|
||||||
}
|
//}
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,26 @@ import com.sksamuel.scrimage.nio.PngWriter
|
||||||
import dev.adamko.kafkatorio.events.schema.ColourHex
|
import dev.adamko.kafkatorio.events.schema.ColourHex
|
||||||
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
|
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
|
||||||
import dev.adamko.kafkatorio.events.schema.MapTilePosition
|
import dev.adamko.kafkatorio.events.schema.MapTilePosition
|
||||||
|
import dev.adamko.kafkatorio.events.schema.converters.toMapChunkPosition
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.conflate
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.runningFold
|
||||||
|
import kotlinx.coroutines.job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
|
|
@ -34,13 +39,15 @@ private val cScope = CoroutineScope(
|
||||||
Dispatchers.Default + SupervisorJob() + CoroutineName("f-server-map-updates")
|
Dispatchers.Default + SupervisorJob() + CoroutineName("f-server-map-updates")
|
||||||
)
|
)
|
||||||
|
|
||||||
/** A bridge between Kafka and Kotlin */
|
private val serverMapChunkHandler = ServerMapChunkHandler()
|
||||||
private val chunkedTiles: MutableSharedFlow<ServerMapChunkTiles<ColourHex>> =
|
|
||||||
MutableSharedFlow(
|
///** A bridge between Kafka and Kotlin */
|
||||||
0,
|
//private val chunkedTiles: MutableSharedFlow<ServerMapChunkTiles<ColourHex>> =
|
||||||
1,
|
// MutableSharedFlow(
|
||||||
BufferOverflow.SUSPEND
|
// 0,
|
||||||
)
|
// 1,
|
||||||
|
// BufferOverflow.SUSPEND
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
fun saveMapTiles(
|
fun saveMapTiles(
|
||||||
|
|
@ -53,29 +60,50 @@ fun saveMapTiles(
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
runBlocking(Dispatchers.Default) {
|
runBlocking(Dispatchers.Default) {
|
||||||
println("emitting chunkedTiles ${value.chunkId} tiles: ${value.map.size}")
|
println("emitting chunkedTiles ${value.chunkId} tiles: ${value.map.size}")
|
||||||
chunkedTiles.emit(value)
|
// chunkedTiles.emit(value)
|
||||||
|
serverMapChunkHandler.emit(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cScope.launch {
|
// cScope.launch {
|
||||||
supervisorScope {
|
// supervisorScope {
|
||||||
chunkedTiles
|
// chunkedTiles
|
||||||
.conflate()
|
// .conflate()
|
||||||
.distinctUntilChanged()
|
// .distinctUntilChanged()
|
||||||
.debounce(1.seconds)
|
// .debounce(1.seconds)
|
||||||
.map {
|
// .map {
|
||||||
println("createMapTileImage ${it.chunkId}")
|
// println("createMapTileImage ${it.chunkId}")
|
||||||
createMapTileImage(it)
|
// createMapTileImage(it)
|
||||||
}
|
// }
|
||||||
.collect()
|
// .collect()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// cScope.launch {
|
// cScope.launch {
|
||||||
// println("launching serverMapDataFlow merging")
|
// println("launching serverMapDataFlow merging")
|
||||||
//
|
//
|
||||||
// chunkedTiles
|
// chunkedTiles
|
||||||
|
// .conflate()
|
||||||
|
// .flatMapConcat { chunk ->
|
||||||
|
// ZoomLevel.values
|
||||||
|
// .asFlow()
|
||||||
|
// .flatMapConcat { zoom ->
|
||||||
|
// chunk.map.entries
|
||||||
|
// .groupBy({ (tile, _) -> tile.toMapChunkPosition(zoom.chunkSize) }
|
||||||
|
// ) {
|
||||||
|
// it.key to it.value
|
||||||
|
// }
|
||||||
|
// .map { (chunkPos, tiles) ->
|
||||||
|
// val chunkId = chunk.chunkId.copy(
|
||||||
|
// zoomLevel = zoom,
|
||||||
|
// chunkPosition = chunkPos
|
||||||
|
// )
|
||||||
|
// ServerMapChunkTiles(chunkId, tiles.toMap())
|
||||||
|
// }
|
||||||
|
// .asFlow()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
// .runningFold(
|
// .runningFold(
|
||||||
// mapOf<ServerMapChunkId, MutableSharedFlow<ServerMapChunkTiles<ColourHex>>>()
|
// mapOf<ServerMapChunkId, MutableSharedFlow<ServerMapChunkTiles<ColourHex>>>()
|
||||||
// ) { accumulator, value ->
|
// ) { accumulator, value ->
|
||||||
|
|
@ -95,11 +123,14 @@ fun saveMapTiles(
|
||||||
// supervisorScope {
|
// supervisorScope {
|
||||||
// println("launching MutableSharedFlow for ${value.chunkId}")
|
// println("launching MutableSharedFlow for ${value.chunkId}")
|
||||||
// input
|
// input
|
||||||
|
// .conflate()
|
||||||
|
// .distinctUntilChanged()
|
||||||
// .debounce(30.seconds)
|
// .debounce(30.seconds)
|
||||||
// .mapLatest {
|
// .mapLatest {
|
||||||
// println("createMapTileImage ${it.chunkId}")
|
// println("createMapTileImage ${it.chunkId}")
|
||||||
// createMapTileImage(it)
|
// createMapTileImage(it)
|
||||||
// }.launchIn(this)
|
// }
|
||||||
|
// .launchIn(this)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// input
|
// input
|
||||||
|
|
@ -120,49 +151,43 @@ fun saveMapTiles(
|
||||||
private value class TilePngFilename(
|
private value class TilePngFilename(
|
||||||
val value: String,
|
val value: String,
|
||||||
) {
|
) {
|
||||||
|
constructor(cmd: Cmd.CreateImage) : this(buildString {
|
||||||
constructor(
|
append("src/main/resources/kafkatorio-web-map")
|
||||||
chunkId: ServerMapChunkId
|
append("/s${cmd.surfaceIndex.index}")
|
||||||
) : this(buildString {
|
append("/z${cmd.zoomLevel.level}")
|
||||||
append("src/main/resources/kafkatorio-web-map/s")
|
append("/x${cmd.chunkPosition.x}")
|
||||||
append(chunkId.surfaceIndex.index)
|
append("/y${cmd.chunkPosition.y}")
|
||||||
append("/z")
|
|
||||||
append(chunkId.zoomLevel.level)
|
|
||||||
append("/x")
|
|
||||||
append(chunkId.chunkPosition.x)
|
|
||||||
append("/y")
|
|
||||||
append(chunkId.chunkPosition.y)
|
|
||||||
append(".png")
|
append(".png")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMapTileImage(
|
//private fun createMapTileImage(
|
||||||
chunkTiles: ServerMapChunkTiles<ColourHex>
|
// chunkTiles: ServerMapChunkTiles<ColourHex>,
|
||||||
) {
|
//
|
||||||
|
// zoomLevel: ZoomLevel
|
||||||
println("emitting ChunkData ${chunkTiles.chunkId.chunkPosition}")
|
//) {
|
||||||
|
//
|
||||||
println("starting to save chunkData ${chunkTiles.chunkId.chunkPosition}...")
|
// println("creating chunkData ${chunkTiles.chunkId.chunkPosition}...")
|
||||||
|
//
|
||||||
val filename = TilePngFilename(chunkTiles.chunkId)
|
// val filename = TilePngFilename(chunkTiles.chunkId)
|
||||||
|
//
|
||||||
val chunkImageFile = File(filename.value)
|
// val chunkImageFile = File(filename.value)
|
||||||
if (chunkImageFile.parentFile.mkdirs()) {
|
// if (chunkImageFile.parentFile.mkdirs()) {
|
||||||
println("created new map tile parentFile directory ${chunkImageFile.absolutePath}")
|
// println("created new map tile parentFile directory ${chunkImageFile.absolutePath}")
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
val image =
|
// val image =
|
||||||
createMapTileImage(
|
// createMapTileImage(
|
||||||
chunkTiles.chunkId.chunkPosition,
|
// chunkTiles.chunkId.chunkPosition,
|
||||||
chunkTiles.map,
|
// chunkTiles.map,
|
||||||
chunkTiles.chunkId.zoomLevel
|
// chunkTiles.chunkId.zoomLevel
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
println("saving map tile $chunkImageFile")
|
// println("saving map tile $chunkImageFile")
|
||||||
val savedTile = image.output(PngWriter.NoCompression, chunkImageFile)
|
// val savedTile = image.output(PngWriter.NoCompression, chunkImageFile)
|
||||||
println("savedTile: $savedTile")
|
// println("savedTile: $savedTile")
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
|
||||||
private val TRANSPARENT_AWT = ColourHex.TRANSPARENT.toRgbColor().awt()
|
private val TRANSPARENT_AWT = ColourHex.TRANSPARENT.toRgbColor().awt()
|
||||||
|
|
||||||
|
|
@ -197,7 +222,7 @@ private fun createMapTileImage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return chunkImage.scaleTo(WEB_MAP_TILE_SIZE_PX, WEB_MAP_TILE_SIZE_PX, ScaleMethod.Progressive)
|
return chunkImage.scaleTo(WEB_MAP_TILE_SIZE_PX, WEB_MAP_TILE_SIZE_PX, ScaleMethod.FastScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -209,3 +234,153 @@ private fun ColourHex.toRgbColor(): RGBColor {
|
||||||
alpha.toInt(),
|
alpha.toInt(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ServerMapChunkHandler : CoroutineScope {
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext = Dispatchers.Default +
|
||||||
|
SupervisorJob(cScope.coroutineContext.job) +
|
||||||
|
CoroutineName("ServerMapChunkHandler")
|
||||||
|
|
||||||
|
|
||||||
|
private val allChunksFlow = MutableSharedFlow<ServerMapChunkTiles<ColourHex>>(
|
||||||
|
replay = 0,
|
||||||
|
extraBufferCapacity = 10,
|
||||||
|
onBufferOverflow = BufferOverflow.SUSPEND,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val saveImagesFlow = MutableSharedFlow<Cmd.SaveImage>(
|
||||||
|
replay = 0,
|
||||||
|
extraBufferCapacity = 10,
|
||||||
|
onBufferOverflow = BufferOverflow.SUSPEND,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
launch {
|
||||||
|
supervisorScope {
|
||||||
|
saveImagesFlow
|
||||||
|
// .conflate()
|
||||||
|
.onEach { (filename, img) ->
|
||||||
|
println("saving map tile $filename")
|
||||||
|
|
||||||
|
val chunkImageFile = File(filename.value)
|
||||||
|
if (chunkImageFile.parentFile.mkdirs()) {
|
||||||
|
println("created new map tile parentFile directory ${chunkImageFile.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val savedTile = img.output(PngWriter.NoCompression, chunkImageFile)
|
||||||
|
println("savedTile: $savedTile")
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
supervisorScope {
|
||||||
|
allChunksFlow
|
||||||
|
// .conflate()
|
||||||
|
.runningFold(mapOf<ServerMapChunkId, MutableSharedFlow<Cmd.ChunkSubdivide>>()) { acc, src ->
|
||||||
|
|
||||||
|
val flow = acc.getOrElse(src.chunkId) {
|
||||||
|
println("creating new subdivide-flow for chunkId ${src.chunkId}")
|
||||||
|
subdivisionFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
flow.emit(Cmd.ChunkSubdivide(src.chunkId, src.map))
|
||||||
|
}
|
||||||
|
|
||||||
|
acc + (src.chunkId to flow)
|
||||||
|
}
|
||||||
|
.onEach {
|
||||||
|
println("allChunksFlow has ${it.size} subdivide-flows: ${it.keys.joinToString()}")
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emit(mapChunkTiles: ServerMapChunkTiles<ColourHex>) {
|
||||||
|
launch {
|
||||||
|
allChunksFlow.emit(mapChunkTiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subdivisionFlow(): MutableSharedFlow<Cmd.ChunkSubdivide> {
|
||||||
|
|
||||||
|
val flow = MutableSharedFlow<Cmd.ChunkSubdivide>(
|
||||||
|
replay = 0,
|
||||||
|
extraBufferCapacity = 10,
|
||||||
|
onBufferOverflow = BufferOverflow.SUSPEND,
|
||||||
|
)
|
||||||
|
|
||||||
|
val saveImgCommands = flow
|
||||||
|
// .conflate()
|
||||||
|
.flatMapConcat { subdivideCmd ->
|
||||||
|
ZoomLevel.values
|
||||||
|
.asFlow()
|
||||||
|
.flatMapConcat { zoom ->
|
||||||
|
subdivideCmd.tiles
|
||||||
|
.entries
|
||||||
|
.groupBy({ (tile, _) -> tile.toMapChunkPosition(zoom.chunkSize) }
|
||||||
|
) {
|
||||||
|
it.key to it.value
|
||||||
|
}
|
||||||
|
.map { (chunkPos, tiles) ->
|
||||||
|
Cmd.CreateImage(
|
||||||
|
surfaceIndex = subdivideCmd.chunkId.surfaceIndex,
|
||||||
|
zoomLevel = zoom,
|
||||||
|
chunkPosition = chunkPos,
|
||||||
|
tiles = tiles.toMap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asFlow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map { createCmd ->
|
||||||
|
val filename = TilePngFilename(createCmd)
|
||||||
|
|
||||||
|
val img = createMapTileImage(
|
||||||
|
createCmd.chunkPosition,
|
||||||
|
createCmd.tiles,
|
||||||
|
createCmd.zoomLevel,
|
||||||
|
)
|
||||||
|
|
||||||
|
Cmd.SaveImage(filename, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
supervisorScope {
|
||||||
|
println("emitting all saveImgCommands to saveImagesFlow")
|
||||||
|
saveImagesFlow.emitAll(saveImgCommands)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed interface Cmd {
|
||||||
|
|
||||||
|
data class ChunkSubdivide(
|
||||||
|
val chunkId: ServerMapChunkId,
|
||||||
|
val tiles: Map<MapTilePosition, ColourHex>,
|
||||||
|
) : Cmd
|
||||||
|
|
||||||
|
data class CreateImage(
|
||||||
|
val surfaceIndex: SurfaceIndex,
|
||||||
|
val zoomLevel: ZoomLevel,
|
||||||
|
val chunkPosition: MapChunkPosition,
|
||||||
|
val tiles: Map<MapTilePosition, ColourHex>,
|
||||||
|
) : Cmd
|
||||||
|
|
||||||
|
data class SaveImage(
|
||||||
|
val filename: TilePngFilename,
|
||||||
|
val image: ImmutableImage,
|
||||||
|
) : Cmd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ fun createFactorioMap() = Maps {
|
||||||
tms = true
|
tms = true
|
||||||
// minZoom = -2
|
// minZoom = -2
|
||||||
// maxZoom = 1
|
// maxZoom = 1
|
||||||
maxNativeZoom = 5
|
maxNativeZoom = 4
|
||||||
minNativeZoom = 1
|
minNativeZoom = 0
|
||||||
noWrap = true
|
noWrap = true
|
||||||
updateWhenIdle = true
|
updateWhenIdle = true
|
||||||
updateWhenZooming = true
|
updateWhenZooming = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue