blob: aceccb7dc62b34a780e84743a4e3b7fde385fa61 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.runtime.snapshots
import androidx.compose.runtime.Stable
import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentMap
import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentHashMapOf
import androidx.compose.runtime.synchronized
import kotlin.jvm.JvmName
/**
* An implementation of [MutableMap] that can be observed and snapshot. This is the result type
* created by [androidx.compose.runtime.mutableStateMapOf].
*
* This class closely implements the same semantics as [HashMap].
*
* @see androidx.compose.runtime.mutableStateMapOf
*/
@Stable
class SnapshotStateMap<K, V> : MutableMap<K, V>, StateObject {
override var firstStateRecord: StateRecord =
StateMapStateRecord<K, V>(persistentHashMapOf())
private set
override fun prependStateRecord(value: StateRecord) {
@Suppress("UNCHECKED_CAST")
firstStateRecord = value as StateMapStateRecord<K, V>
}
/**
* Returns an immutable map containing all key-value pairs from the original map.
*
* The content of the map returned will not change even if the content of the map is changed in
* the same snapshot. It also will be the same instance until the content is changed. It is not,
* however, guaranteed to be the same instance for the same content as adding and removing the
* same item from the this map might produce a different instance with the same content.
*
* This operation is O(1) and does not involve a physically copying the map. It instead
* returns the underlying immutable map used internally to store the content of the map.
*
* It is recommended to use [toMap] when using returning the value of this map from
* [androidx.compose.runtime.snapshotFlow].
*/
fun toMap(): Map<K, V> = readable.map
override val size get() = readable.map.size
override fun containsKey(key: K) = readable.map.containsKey(key)
override fun containsValue(value: V) = readable.map.containsValue(value)
override fun get(key: K) = readable.map[key]
override fun isEmpty() = readable.map.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> = SnapshotMapEntrySet(this)
override val keys: MutableSet<K> = SnapshotMapKeySet(this)
override val values: MutableCollection<V> = SnapshotMapValueSet(this)
override fun clear() = update { persistentHashMapOf() }
override fun put(key: K, value: V): V? = mutate { it.put(key, value) }
override fun putAll(from: Map<out K, V>) = mutate { it.putAll(from) }
override fun remove(key: K): V? = mutate { it.remove(key) }
internal val modification get() = readable.modification
internal fun removeValue(value: V) =
entries.firstOrNull { it.value == value }?.let { remove(it.key); true } == true
@Suppress("UNCHECKED_CAST")
internal val readable: StateMapStateRecord<K, V>
get() = (firstStateRecord as StateMapStateRecord<K, V>).readable(this)
internal inline fun removeIf(predicate: (MutableMap.MutableEntry<K, V>) -> Boolean): Boolean {
var removed = false
mutate {
for (entry in this.entries) {
if (predicate(entry)) {
it.remove(entry.key)
removed = true
}
}
}
return removed
}
internal inline fun any(predicate: (Map.Entry<K, V>) -> Boolean): Boolean {
for (entry in readable.map.entries) {
if (predicate(entry)) return true
}
return false
}
internal inline fun all(predicate: (Map.Entry<K, V>) -> Boolean): Boolean {
for (entry in readable.map.entries) {
if (!predicate(entry)) return false
}
return true
}
/**
* An internal function used by the debugger to display the value of the current value of the
* mutable state object without triggering read observers.
*/
@Suppress("unused")
internal val debuggerDisplayValue: Map<K, V>
@JvmName("getDebuggerDisplayValue")
get() = withCurrent { map }
private inline fun <R> withCurrent(block: StateMapStateRecord<K, V>.() -> R): R =
@Suppress("UNCHECKED_CAST")
(firstStateRecord as StateMapStateRecord<K, V>).withCurrent(block)
private inline fun <R> writable(block: StateMapStateRecord<K, V>.() -> R): R =
@Suppress("UNCHECKED_CAST")
(firstStateRecord as StateMapStateRecord<K, V>).writable(this, block)
private inline fun <R> mutate(block: (MutableMap<K, V>) -> R): R {
var result: R
while (true) {
var oldMap: PersistentMap<K, V>? = null
var currentModification = 0
synchronized(sync) {
val current = withCurrent { this }
oldMap = current.map
currentModification = current.modification
}
val builder = oldMap!!.builder()
result = block(builder)
val newMap = builder.build()
if (newMap == oldMap || synchronized(sync) {
writable {
if (modification == currentModification) {
map = newMap
modification++
true
} else false
}
}
) break
}
return result
}
private inline fun update(block: (PersistentMap<K, V>) -> PersistentMap<K, V>) = withCurrent {
val newMap = block(map)
if (newMap !== map) synchronized(sync) {
writable {
map = newMap
modification++
}
}
}
/**
* Implementation class of [SnapshotStateMap]. Do not use.
*/
internal class StateMapStateRecord<K, V> internal constructor(
internal var map: PersistentMap<K, V>
) : StateRecord() {
internal var modification = 0
override fun assign(value: StateRecord) {
@Suppress("UNCHECKED_CAST")
val other = (value as StateMapStateRecord<K, V>)
synchronized(sync) {
map = other.map
modification = other.modification
}
}
override fun create(): StateRecord = StateMapStateRecord(map)
}
}
private abstract class SnapshotMapSet<K, V, E>(
val map: SnapshotStateMap<K, V>
) : MutableSet<E> {
override val size: Int get() = map.size
override fun clear() = map.clear()
override fun isEmpty() = map.isEmpty()
}
private class SnapshotMapEntrySet<K, V>(
map: SnapshotStateMap<K, V>
) : SnapshotMapSet<K, V, MutableMap.MutableEntry<K, V>>(map) {
override fun add(element: MutableMap.MutableEntry<K, V>) = unsupported()
override fun addAll(elements: Collection<MutableMap.MutableEntry<K, V>>) = unsupported()
override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> =
StateMapMutableEntriesIterator(map, map.readable.map.entries.iterator())
override fun remove(element: MutableMap.MutableEntry<K, V>) =
map.remove(element.key) != null
override fun removeAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
var removed = false
for (element in elements) {
removed = map.remove(element.key) != null || removed
}
return removed
}
override fun retainAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
val entries = elements.associate { it.key to it.value }
return map.removeIf { !entries.containsKey(it.key) || entries[it.key] != it.value }
}
override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean {
return map[element.key] == element.value
}
override fun containsAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
return elements.all { contains(it) }
}
}
private class SnapshotMapKeySet<K, V>(map: SnapshotStateMap<K, V>) : SnapshotMapSet<K, V, K>(map) {
override fun add(element: K) = unsupported()
override fun addAll(elements: Collection<K>) = unsupported()
override fun iterator() = StateMapMutableKeysIterator(map, map.readable.map.entries.iterator())
override fun remove(element: K): Boolean = map.remove(element) != null
override fun removeAll(elements: Collection<K>): Boolean {
var removed = false
elements.forEach {
removed = map.remove(it) != null || removed
}
return removed
}
override fun retainAll(elements: Collection<K>): Boolean {
val set = elements.toSet()
return map.removeIf { it.key !in set }
}
override fun contains(element: K) = map.contains(element)
override fun containsAll(elements: Collection<K>): Boolean = elements.all { map.contains(it) }
}
private class SnapshotMapValueSet<K, V>(
map: SnapshotStateMap<K, V>
) : SnapshotMapSet<K, V, V>(map) {
override fun add(element: V) = unsupported()
override fun addAll(elements: Collection<V>) = unsupported()
override fun iterator() =
StateMapMutableValuesIterator(map, map.readable.map.entries.iterator())
override fun remove(element: V): Boolean = map.removeValue(element)
override fun removeAll(elements: Collection<V>): Boolean {
val set = elements.toSet()
return map.removeIf { it.value in set }
}
override fun retainAll(elements: Collection<V>): Boolean {
val set = elements.toSet()
return map.removeIf { it.value !in set }
}
override fun contains(element: V) = map.containsValue(element)
override fun containsAll(elements: Collection<V>): Boolean {
return elements.all { map.containsValue(it) }
}
}
/**
* This lock is used to ensure that the value of modification and the map in the state record,
* when used together, are atomically read and written.
*
* A global sync object is used to avoid having to allocate a sync object and initialize a monitor
* for each instance the map. This avoids additional allocations but introduces some contention
* between maps. As there is already contention on the global snapshot lock to write so the
* additional contention introduced by this lock is nominal.
*
* In code the requires this lock and calls `writable` (or other operation that acquires the
* snapshot global lock), this lock *MUST* be acquired first to avoid deadlocks.
*/
private val sync = Any()
private abstract class StateMapMutableIterator<K, V>(
val map: SnapshotStateMap<K, V>,
val iterator: Iterator<Map.Entry<K, V>>
) {
protected var modification = map.modification
protected var current: Map.Entry<K, V>? = null
protected var next: Map.Entry<K, V>? = null
init { advance() }
fun remove() = modify {
val value = current
if (value != null) {
map.remove(value.key)
current = null
} else {
throw IllegalStateException()
}
}
fun hasNext() = next != null
protected fun advance() {
current = next
next = if (iterator.hasNext()) iterator.next() else null
}
protected inline fun <T> modify(block: () -> T): T {
if (map.modification != modification) {
throw ConcurrentModificationException()
}
return block().also { modification = map.modification }
}
}
private class StateMapMutableEntriesIterator<K, V>(
map: SnapshotStateMap<K, V>,
iterator: Iterator<Map.Entry<K, V>>
) : StateMapMutableIterator<K, V>(map, iterator), MutableIterator<MutableMap.MutableEntry<K, V>> {
override fun next(): MutableMap.MutableEntry<K, V> {
advance()
if (current != null) {
return object : MutableMap.MutableEntry<K, V> {
override val key = current!!.key
override var value = current!!.value
override fun setValue(newValue: V): V = modify {
val result = value
map[key] = newValue
value = newValue
return result
}
}
} else {
throw IllegalStateException()
}
}
}
private class StateMapMutableKeysIterator<K, V>(
map: SnapshotStateMap<K, V>,
iterator: Iterator<Map.Entry<K, V>>
) : StateMapMutableIterator<K, V>(map, iterator), MutableIterator<K> {
override fun next(): K {
val result = next ?: throw IllegalStateException()
advance()
return result.key
}
}
private class StateMapMutableValuesIterator<K, V>(
map: SnapshotStateMap<K, V>,
iterator: Iterator<Map.Entry<K, V>>
) : StateMapMutableIterator<K, V>(map, iterator), MutableIterator<V> {
override fun next(): V {
val result = next ?: throw IllegalStateException()
advance()
return result.value
}
}
internal fun unsupported(): Nothing {
throw UnsupportedOperationException()
}