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
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.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.overlay.Tooltip
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
import io.kvision.redux.ReduxStore
import io.kvision.redux.createReduxStore
import io.kvision.utils.obj
import redux.RAction
@ -37,12 +27,6 @@ data class FactorioGameState(
val playerIconsLayer: LayerGroup
get() = map.playerIconsLayer
// init {
// kvMaps.leafletMap {
// playerIconsLayer.addTo(this)
// }
// }
}
@ -55,169 +39,3 @@ fun factorioGameReducer(gameState: FactorioGameState, update: FactorioUpdate): F
when (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,
)
}
}
}