webmap: refactor state + update handling code, to separate files

This commit is contained in:
Adam 2022-05-24 18:08:32 +02:00
parent aa4de3fb14
commit 187fca8b8c
2 changed files with 181 additions and 182 deletions

View file

@ -0,0 +1,181 @@
package dev.adamko.kafkatorio.webmap.state
import dev.adamko.kafkatorio.schema.common.Colour
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
import dev.adamko.kafkatorio.schema.common.PlayerIndex
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
import dev.adamko.kafkatorio.schema.common.Tick
import dev.adamko.kafkatorio.schema.common.toHexString
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.webmap.latLng
import io.kvision.maps.Maps
import io.kvision.maps.externals.leaflet.geo.LatLng
import io.kvision.maps.externals.leaflet.layer.overlay.Tooltip
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
import io.kvision.utils.obj
fun handlePlayerUpdate(
gameState: FactorioGameState,
update: FactorioUpdate.Player,
): FactorioGameState {
val previous = gameState.players[update.data.key.index]
?: PlayerState(update.data.key.index, update.tick)
val updated = previous.update(update.tick, update.data)
if (previous.mapMarker != updated.mapMarker) {
if (previous.mapMarker != null) {
println("removing player icon ${previous.index}")
gameState.playerIconsLayer.removeLayer(previous.mapMarker)
}
if (updated.mapMarker != null) {
println("adding player icon ${updated.index}")
gameState.playerIconsLayer.addLayer(updated.mapMarker)
}
}
val updatedPlayersState = gameState.players + (previous.index to updated)
return gameState.copy(players = updatedPlayersState)
}
data class PlayerState(
val index: PlayerIndex,
val lastTick: Tick,
val playerDetails: PlayerProperties = PlayerProperties.Partial(),
val mapMarker: CircleMarker? = null,
) {
fun update(tick: Tick, update: PlayerUpdate): PlayerState {
if (lastTick.value > tick.value) {
println("discarding old PlayerUpdate, ${lastTick.value} > ${tick.value}")
return this
}
val newState = this.copy(
lastTick = tick,
playerDetails = playerDetails.update(update),
mapMarker = updateMapMarker()
)
println("updated PlayerState index:${newState.index}, name:${newState.playerDetails.name}")
return newState
}
private fun updateMapMarker(): CircleMarker? {
return when (playerDetails) {
is PlayerProperties.Partial -> {
println("removing player $index map marker")
null
}
is PlayerProperties.Complete -> {
val mapMarker = mapMarker ?: createMapMarker(playerDetails)
mapMarker.apply {
options.color = playerDetails.hexColour
options.fillColor = playerDetails.hexColour
val lat = playerDetails.latLng.lat.toInt()
val lng = playerDetails.latLng.lng.toInt()
setTooltipContent("${playerDetails.name} [$lat, $lng]")
setLatLng(playerDetails.latLng)
}
}
}
}
companion object {
private fun createMapMarker(playerDetails: PlayerProperties.Complete): CircleMarker =
Maps.L.circleMarker(playerDetails.latLng) {
fillOpacity = 1f
radius = 10
className = "player-marker"
// interactive = false
}.also { circle ->
circle.bindTooltip("", obj<Tooltip.TooltipOptions> {
// interactive = false
})
}
}
}
sealed interface PlayerProperties {
val position: MapEntityPosition?
val surfaceIndex: SurfaceIndex?
val colour: Colour?
val name: String?
val latLng: LatLng?
val isConnected: Boolean
val isShowOnMap: Boolean
data class Partial(
override val position: MapEntityPosition? = null,
override val surfaceIndex: SurfaceIndex? = null,
override val colour: Colour? = null,
override val name: String? = null,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng? = null
}
data class Complete(
override val position: MapEntityPosition,
override val surfaceIndex: SurfaceIndex,
override val colour: Colour,
override val name: String,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng = position.latLng()
val hexColour: String = colour.toHexString(false)
}
fun update(update: PlayerUpdate): PlayerProperties {
val position = update.position ?: this.position
val surfaceIndex = update.surfaceIndex ?: this.surfaceIndex
val colour = update.colour ?: this.colour
val name = update.name ?: this.name
val isConnected = update.isConnected ?: false
val isShowOnMap = update.isShowOnMap ?: false
return when {
position != null
&& surfaceIndex != null
&& colour != null
&& name != null ->
Complete(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
else ->
Partial(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
}
}
}

View file

@ -1,22 +1,12 @@
package dev.adamko.kafkatorio.webmap.state package dev.adamko.kafkatorio.webmap.state
import dev.adamko.kafkatorio.schema.common.Colour
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
import dev.adamko.kafkatorio.schema.common.PlayerIndex import dev.adamko.kafkatorio.schema.common.PlayerIndex
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
import dev.adamko.kafkatorio.schema.common.Tick import dev.adamko.kafkatorio.schema.common.Tick
import dev.adamko.kafkatorio.schema.common.toHexString
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.webmap.FactorioMap import dev.adamko.kafkatorio.webmap.FactorioMap
import dev.adamko.kafkatorio.webmap.latLng
import io.kvision.maps.Maps.Companion.L
import io.kvision.maps.externals.leaflet.geo.LatLng
import io.kvision.maps.externals.leaflet.layer.LayerGroup import io.kvision.maps.externals.leaflet.layer.LayerGroup
import io.kvision.maps.externals.leaflet.layer.overlay.Tooltip
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
import io.kvision.redux.ReduxStore import io.kvision.redux.ReduxStore
import io.kvision.redux.createReduxStore import io.kvision.redux.createReduxStore
import io.kvision.utils.obj
import redux.RAction import redux.RAction
@ -37,12 +27,6 @@ data class FactorioGameState(
val playerIconsLayer: LayerGroup val playerIconsLayer: LayerGroup
get() = map.playerIconsLayer get() = map.playerIconsLayer
// init {
// kvMaps.leafletMap {
// playerIconsLayer.addTo(this)
// }
// }
} }
@ -55,169 +39,3 @@ fun factorioGameReducer(gameState: FactorioGameState, update: FactorioUpdate): F
when (update) { when (update) {
is FactorioUpdate.Player -> handlePlayerUpdate(gameState, update) is FactorioUpdate.Player -> handlePlayerUpdate(gameState, update)
} }
private fun handlePlayerUpdate(
gameState: FactorioGameState,
update: FactorioUpdate.Player,
): FactorioGameState {
val previous = gameState.players[update.data.key.index]
?: PlayerState(update.data.key.index, update.tick)
val updated = previous.update(update.tick, update.data)
if (previous.mapMarker != updated.mapMarker) {
if (previous.mapMarker != null) {
println("removing player icon ${previous.index}")
gameState.playerIconsLayer.removeLayer(previous.mapMarker)
}
if (updated.mapMarker != null) {
println("adding player icon ${updated.index}")
gameState.playerIconsLayer.addLayer(updated.mapMarker)
}
}
val updatedPlayersState = gameState.players + (previous.index to updated)
return gameState.copy(players = updatedPlayersState)
}
data class PlayerState(
val index: PlayerIndex,
val lastTick: Tick,
val playerDetails: PlayerProperties = PlayerProperties.Partial(),
val mapMarker: CircleMarker? = null,
) {
fun update(tick: Tick, update: PlayerUpdate): PlayerState {
if (lastTick.value > tick.value) {
println("discarding old PlayerUpdate, ${lastTick.value} > ${tick.value}")
return this
}
val newState = this.copy(
lastTick = tick,
playerDetails = playerDetails.update(update),
mapMarker = updateMapMarker()
)
println("updated PlayerState index:${newState.index}, name:${newState.playerDetails.name}")
return newState
}
private fun updateMapMarker(): CircleMarker? {
return when (playerDetails) {
is PlayerProperties.Partial -> {
println("removing player $index map marker")
null
}
is PlayerProperties.Complete -> {
val mapMarker = mapMarker ?: createMapMarker(playerDetails)
mapMarker.apply {
options.color = playerDetails.hexColour
options.fillColor = playerDetails.hexColour
val lat = playerDetails.latLng.lat.toInt()
val lng = playerDetails.latLng.lng.toInt()
setTooltipContent("${playerDetails.name} [$lat, $lng]")
setLatLng(playerDetails.latLng)
}
}
}
}
companion object {
private fun createMapMarker(playerDetails: PlayerProperties.Complete): CircleMarker =
L.circleMarker(playerDetails.latLng) {
fillOpacity = 1f
radius = 10
className = "player-marker"
// interactive = false
}.also { circle ->
circle.bindTooltip("", obj<Tooltip.TooltipOptions> {
// interactive = false
})
}
}
}
sealed interface PlayerProperties {
val position: MapEntityPosition?
val surfaceIndex: SurfaceIndex?
val colour: Colour?
val name: String?
val latLng: LatLng?
val isConnected: Boolean
val isShowOnMap: Boolean
data class Partial(
override val position: MapEntityPosition? = null,
override val surfaceIndex: SurfaceIndex? = null,
override val colour: Colour? = null,
override val name: String? = null,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng? = null
}
data class Complete(
override val position: MapEntityPosition,
override val surfaceIndex: SurfaceIndex,
override val colour: Colour,
override val name: String,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng = position.latLng()
val hexColour: String = colour.toHexString(false)
}
fun update(update: PlayerUpdate): PlayerProperties {
val position = update.position ?: this.position
val surfaceIndex = update.surfaceIndex ?: this.surfaceIndex
val colour = update.colour ?: this.colour
val name = update.name ?: this.name
val isConnected = update.isConnected ?: false
val isShowOnMap = update.isShowOnMap ?: false
return when {
position != null
&& surfaceIndex != null
&& colour != null
&& name != null ->
Complete(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
else ->
Partial(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
}
}
}