Skip to content

Commit

Permalink
refactor: break up style manager (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
sargunv authored Dec 24, 2024
1 parent 6467f6b commit 68f33f7
Show file tree
Hide file tree
Showing 18 changed files with 341 additions and 331 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.compose.ui.test.runAndroidComposeUiTest
import org.maplibre.android.MapLibre

@OptIn(ExperimentalTestApi::class)
class AndroidStyleManagerTest : StyleManagerTest() {
class AndroidStyleNodeTest : StyleNodeTest() {
override fun platformSetup() =
runAndroidComposeUiTest<ComponentActivity> {
activity!!.runOnUiThread { MapLibre.getInstance(activity!!) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public fun MaplibreMap(
val layerNodes =
(styleComposition?.children?.filterIsInstance<LayerNode<*>>() ?: emptyList())
.associateBy { node -> node.layer.id }
val layers = styleComposition?.styleManager?.style?.getLayers() ?: emptyList()
val layers = styleComposition?.style?.getLayers() ?: emptyList()
return layers.asReversed().mapNotNull { layer -> layerNodes[layer.id] }
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,56 @@
package dev.sargunv.maplibrecompose.compose.engine

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ImageBitmap
import dev.sargunv.maplibrecompose.core.expression.Expression
import dev.sargunv.maplibrecompose.core.expression.ExpressionValue
import dev.sargunv.maplibrecompose.core.expression.ExpressionsDsl.cast
import dev.sargunv.maplibrecompose.core.expression.ResolvedValue

internal class ImageManager(private val styleManager: StyleManager) {
internal class ImageManager(private val node: StyleNode) {
private val idMap = IncrementingIdMap<ImageBitmap>("image")

private val counter =
ReferenceCounter<ImageBitmap>(
onZeroToOne = { image ->
val id = idMap.addId(image)
styleManager.logger?.i { "Adding image $id" }
styleManager.style.addImage(id, image)
node.logger?.i { "Adding image $id" }
node.style.addImage(id, image)
},
onOneToZero = { image ->
val id = idMap.removeId(image)
styleManager.logger?.i { "Removing image $id" }
styleManager.style.removeImage(id)
node.logger?.i { "Removing image $id" }
node.style.removeImage(id)
},
)

fun addReference(image: ImageBitmap): String {
private fun addReference(image: ImageBitmap): String {
counter.increment(image)
return idMap.getId(image)
}

fun removeReference(image: ImageBitmap) {
private fun removeReference(image: ImageBitmap) {
counter.decrement(image)
}

@Composable
internal fun <T : ExpressionValue> resolveImages(
expr: Expression<T>
): Expression<ResolvedValue<T>> {
DisposableEffect(expr) {
onDispose { expr.visitLeaves { if (it is ImageBitmap) removeReference(it) } }
}
return remember(expr) {
expr
.mapLeaves {
when (it) {
is ImageBitmap -> addReference(it)
else -> it
}
}
.cast()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package dev.sargunv.maplibrecompose.compose.engine

import dev.sargunv.maplibrecompose.compose.layer.Anchor
import dev.sargunv.maplibrecompose.core.layer.Layer

internal class LayerManager(private val styleNode: StyleNode) {
private val baseLayers = styleNode.style.getLayers().associateBy { it.id }

private val userLayers = mutableListOf<LayerNode<*>>()

// special handling for Replace anchors
private val replacedLayers = mutableMapOf<Anchor.Replace, Layer>()
private val replacementCounters = mutableMapOf<Anchor.Replace, Int>()

internal fun addLayer(node: LayerNode<*>, index: Int) {
require(node.layer.id !in baseLayers) {
"Layer ID '${node.layer.id}' already exists in base style"
}
node.anchor.validate()
styleNode.logger?.i {
"Queuing layer ${node.layer.id} for addition at anchor ${node.anchor}, index $index"
}
userLayers.add(index, node)
}

internal fun removeLayer(node: LayerNode<*>, oldIndex: Int) {
userLayers.removeAt(oldIndex)

// special handling for Replace anchors
// restore the original before removing if this layer was the last replacement
val anchor = node.anchor
if (anchor is Anchor.Replace) {
val count = replacementCounters.getValue(anchor) - 1
if (count > 0) replacementCounters[anchor] = count
else {
replacementCounters.remove(anchor)
styleNode.logger?.i { "Restoring layer ${anchor.layerId}" }
styleNode.style.addLayerBelow(node.layer.id, replacedLayers.remove(anchor)!!)
}
}

styleNode.logger?.i { "Removing layer ${node.layer.id}" }
styleNode.style.removeLayer(node.layer)
node.added = false
}

internal fun moveLayer(node: LayerNode<*>, oldIndex: Int, index: Int) {
styleNode.logger?.i { "Moving layer ${node.layer.id} from $oldIndex to $index" }
removeLayer(node, oldIndex)
addLayer(node, index)
styleNode.logger?.i { "Done moving layer ${node.layer.id}" }
}

internal fun applyChanges() {
val tailLayerIds = mutableMapOf<Anchor, String>()
val missedLayers = mutableMapOf<Anchor, MutableList<LayerNode<*>>>()

userLayers.forEach { node ->
val layer = node.layer
val anchor = node.anchor

if (node.added && anchor in missedLayers) {
// we found an existing head; let's add the missed layers
val layersToAdd = missedLayers.remove(anchor)!!
layersToAdd.forEach { missedLayer ->
styleNode.logger?.i { "Adding layer ${missedLayer.layer.id} below ${layer.id}" }
styleNode.style.addLayerBelow(layer.id, missedLayer.layer)
missedLayer.markAdded()
}
}

if (!node.added) {
// we found a layer to add; let's try to add it, or queue it up until we find a head
tailLayerIds[anchor]?.let { tailLayerId ->
styleNode.logger?.i { "Adding layer ${layer.id} below $tailLayerId" }
styleNode.style.addLayerAbove(tailLayerId, layer)
node.markAdded()
} ?: missedLayers.getOrPut(anchor) { mutableListOf() }.add(node)
}

// update the tail
if (node.added) tailLayerIds[anchor] = layer.id
}

// anything left in missedLayers is a new anchor
missedLayers.forEach { (anchor, nodes) ->
// let's initialize the anchor with one layer
val tail = nodes.removeLast()
styleNode.logger?.i { "Initializing anchor $anchor with layer ${tail.layer.id}" }
when (anchor) {
is Anchor.Top -> styleNode.style.addLayer(tail.layer)
is Anchor.Bottom -> styleNode.style.addLayerAt(0, tail.layer)
is Anchor.Above -> styleNode.style.addLayerAbove(anchor.layerId, tail.layer)
is Anchor.Below -> styleNode.style.addLayerBelow(anchor.layerId, tail.layer)
is Anchor.Replace -> {
val layerToReplace = styleNode.style.getLayer(anchor.layerId)!!
styleNode.style.addLayerAbove(layerToReplace.id, tail.layer)
styleNode.logger?.i { "Replacing layer ${layerToReplace.id} with ${tail.layer.id}" }
styleNode.style.removeLayer(layerToReplace)
replacedLayers[anchor] = layerToReplace
replacementCounters[anchor] = 0
}
}
tail.markAdded()

// and add the rest below it
nodes.forEach { node ->
styleNode.logger?.i { "Adding layer ${node.layer.id} below ${tail.layer.id}" }
styleNode.style.addLayerBelow(tail.layer.id, node.layer)
node.markAdded()
}
}
}

private fun LayerNode<*>.markAdded() {
if (anchor is Anchor.Replace)
replacementCounters[anchor] = replacementCounters.getValue(anchor) + 1
added = true
}

private fun Anchor.validate() {
layerIdOrNull?.let { layerId ->
require(baseLayers.containsKey(layerId)) { "Layer ID '$layerId' not found in base style" }
}
}

private val Anchor.layerIdOrNull: String?
get() =
when (this) {
is Anchor.Above -> layerId
is Anchor.Below -> layerId
is Anchor.Replace -> layerId
else -> null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package dev.sargunv.maplibrecompose.compose.engine

import dev.sargunv.maplibrecompose.core.source.Source

internal class SourceManager(private val node: StyleNode) {
private val baseSources = node.style.getSources().associateBy { it.id }

private val sourcesToAdd = mutableListOf<Source>()

private val counter =
ReferenceCounter<Source>(
onZeroToOne = { source ->
node.logger?.i { "Queuing source ${source.id} for addition" }
sourcesToAdd.add(source)
},
onOneToZero = { source ->
node.logger?.i { "Removing source ${source.id}" }
node.style.removeSource(source)
},
)

internal fun getBaseSource(id: String): Source {
return baseSources[id] ?: error("Source ID '$id' not found in base style")
}

internal fun addReference(source: Source) {
require(source.id !in baseSources) { "Source ID '${source.id}' already exists in base style" }
counter.increment(source)
}

internal fun removeReference(source: Source) {
require(source.id !in baseSources) {
"Source ID '${source.id}' is part of the base style and can't be removed here"
}
counter.decrement(source)
}

internal fun applyChanges() {
sourcesToAdd
.onEach {
node.logger?.i { "Adding source ${it.id}" }
node.style.addSource(it)
}
.clear()
}
}
Loading

0 comments on commit 68f33f7

Please sign in to comment.