All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.androidx.compose.runtime.snapshots.SnapshotStateMap.kt Maven / Gradle / Ivy

/*
 * 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 : MutableMap, StateObject {
    override var firstStateRecord: StateRecord =
        StateMapStateRecord(persistentHashMapOf())
        private set

    override fun prependStateRecord(value: StateRecord) {
        @Suppress("UNCHECKED_CAST")
        firstStateRecord = value as StateMapStateRecord
    }

    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> = SnapshotMapEntrySet(this)
    override val keys: MutableSet = SnapshotMapKeySet(this)
    override val values: MutableCollection = 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) = 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
        get() = (firstStateRecord as StateMapStateRecord).readable(this)

    internal inline fun removeIf(predicate: (MutableMap.MutableEntry) -> 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) -> Boolean): Boolean {
        for (entry in readable.map.entries) {
            if (predicate(entry)) return true
        }
        return false
    }

    internal inline fun all(predicate: (Map.Entry) -> 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
        @JvmName("getDebuggerDisplayValue")
        get() = withCurrent { map }

    private inline fun  withCurrent(block: StateMapStateRecord.() -> R): R =
        @Suppress("UNCHECKED_CAST")
        (firstStateRecord as StateMapStateRecord).withCurrent(block)

    private inline fun  writable(block: StateMapStateRecord.() -> R): R =
        @Suppress("UNCHECKED_CAST")
        (firstStateRecord as StateMapStateRecord).writable(this, block)

    private inline fun  mutate(block: (MutableMap) -> R): R {
        var result: R
        while (true) {
            var oldMap: PersistentMap? = 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) -> PersistentMap) = withCurrent {
        val newMap = block(map)
        if (newMap !== map) synchronized(sync) {
            writable {
                map = newMap
                modification++
            }
        }
    }

    /**
     * Implementation class of [SnapshotStateMap]. Do not use.
     */
    internal class StateMapStateRecord internal constructor(
        internal var map: PersistentMap
    ) : StateRecord() {
        internal var modification = 0
        override fun assign(value: StateRecord) {
            @Suppress("UNCHECKED_CAST")
            val other = (value as StateMapStateRecord)
            synchronized(sync) {
                map = other.map
                modification = other.modification
            }
        }

        override fun create(): StateRecord = StateMapStateRecord(map)
    }
}

private abstract class SnapshotMapSet(
    val map: SnapshotStateMap
) : MutableSet {
    override val size: Int get() = map.size
    override fun clear() = map.clear()
    override fun isEmpty() = map.isEmpty()
}

private class SnapshotMapEntrySet(
    map: SnapshotStateMap
) : SnapshotMapSet>(map) {
    override fun add(element: MutableMap.MutableEntry) = unsupported()
    override fun addAll(elements: Collection>) = unsupported()
    override fun iterator(): MutableIterator> =
        StateMapMutableEntriesIterator(map, map.readable.map.entries.iterator())
    override fun remove(element: MutableMap.MutableEntry) =
        map.remove(element.key) != null
    override fun removeAll(elements: Collection>): Boolean {
        var removed = false
        for (element in elements) {
            removed = map.remove(element.key) != null || removed
        }
        return removed
    }
    override fun retainAll(elements: Collection>): 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): Boolean {
        return map[element.key] == element.value
    }
    override fun containsAll(elements: Collection>): Boolean {
        return elements.all { contains(it) }
    }
}

private class SnapshotMapKeySet(map: SnapshotStateMap) : SnapshotMapSet(map) {
    override fun add(element: K) = unsupported()
    override fun addAll(elements: Collection) = 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): Boolean {
        var removed = false
        elements.forEach {
            removed = map.remove(it) != null || removed
        }
        return removed
    }
    override fun retainAll(elements: Collection): 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): Boolean = elements.all { map.contains(it) }
}

private class SnapshotMapValueSet(
    map: SnapshotStateMap
) : SnapshotMapSet(map) {
    override fun add(element: V) = unsupported()
    override fun addAll(elements: Collection) = 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): Boolean {
        val set = elements.toSet()
        return map.removeIf { it.value in set }
    }
    override fun retainAll(elements: Collection): 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): 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(
    val map: SnapshotStateMap,
    val iterator: Iterator>
) {
    protected var modification = map.modification
    protected var current: Map.Entry? = null
    protected var next: Map.Entry? = 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  modify(block: () -> T): T {
        if (map.modification != modification) {
            throw ConcurrentModificationException()
        }
        return block().also { modification = map.modification }
    }
}

private class StateMapMutableEntriesIterator(
    map: SnapshotStateMap,
    iterator: Iterator>
) : StateMapMutableIterator(map, iterator), MutableIterator> {
    override fun next(): MutableMap.MutableEntry {
        advance()
        if (current != null) {
            return object : MutableMap.MutableEntry {
                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(
    map: SnapshotStateMap,
    iterator: Iterator>
) : StateMapMutableIterator(map, iterator), MutableIterator {
    override fun next(): K {
        val result = next ?: throw IllegalStateException()
        advance()
        return result.key
    }
}

private class StateMapMutableValuesIterator(
    map: SnapshotStateMap,
    iterator: Iterator>
) : StateMapMutableIterator(map, iterator), MutableIterator {
    override fun next(): V {
        val result = next ?: throw IllegalStateException()
        advance()
        return result.value
    }
}

internal fun unsupported(): Nothing {
    throw UnsupportedOperationException()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy