commonMain.androidx.compose.runtime.snapshots.SnapshotStateList.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of runtime Show documentation
Show all versions of runtime Show documentation
Tree composition support for code generated by the Compose compiler plugin and corresponding public API
The newest version!
/*
* 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.SynchronizedObject
import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
import androidx.compose.runtime.requirePrecondition
import androidx.compose.runtime.synchronized
import kotlin.jvm.JvmName
/**
* An implementation of [MutableList] that can be observed and snapshot. This is the result type
* created by [androidx.compose.runtime.mutableStateListOf].
*
* This class closely implements the same semantics as [ArrayList].
*
* @see androidx.compose.runtime.mutableStateListOf
*/
@Stable
class SnapshotStateList : StateObject, MutableList, RandomAccess {
override var firstStateRecord: StateRecord =
persistentListOf().let { list ->
StateListStateRecord(list).also {
if (Snapshot.isInSnapshot) {
it.next = StateListStateRecord(list).also { next ->
next.snapshotId = Snapshot.PreexistingSnapshotId
}
}
}
}
private set
override fun prependStateRecord(value: StateRecord) {
value.next = firstStateRecord
@Suppress("UNCHECKED_CAST")
firstStateRecord = value as StateListStateRecord
}
/**
* Return a list containing all the elements of this list.
*
* The list returned is immutable and returned will not change even if the content of the list
* 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 list as adding
* and removing the same item from the this list might produce a different instance with the
* same content.
*
* This operation is O(1) and does not involve a physically copying the list. It instead
* returns the underlying immutable list used internally to store the content of the list.
*
* It is recommended to use [toList] when using returning the value of this list from
* [androidx.compose.runtime.snapshotFlow].
*/
fun toList(): List = readable.list
internal val structure: Int get() = withCurrent { structuralChange }
@Suppress("UNCHECKED_CAST")
internal val readable: StateListStateRecord get() =
(firstStateRecord as StateListStateRecord).readable(this)
/**
* This is an internal implementation class of [SnapshotStateList]. Do not use.
*/
internal class StateListStateRecord internal constructor(
internal var list: PersistentList
) : StateRecord() {
internal var modification = 0
internal var structuralChange = 0
override fun assign(value: StateRecord) {
synchronized(sync) {
@Suppress("UNCHECKED_CAST")
list = (value as StateListStateRecord).list
modification = value.modification
structuralChange = value.structuralChange
}
}
override fun create(): StateRecord = StateListStateRecord(list)
}
override val size: Int get() = readable.list.size
override fun contains(element: T) = readable.list.contains(element)
override fun containsAll(elements: Collection) = readable.list.containsAll(elements)
override fun get(index: Int) = readable.list[index]
override fun indexOf(element: T): Int = readable.list.indexOf(element)
override fun isEmpty() = readable.list.isEmpty()
override fun iterator(): MutableIterator = listIterator()
override fun lastIndexOf(element: T) = readable.list.lastIndexOf(element)
override fun listIterator(): MutableListIterator = StateListIterator(this, 0)
override fun listIterator(index: Int): MutableListIterator = StateListIterator(this, index)
override fun subList(fromIndex: Int, toIndex: Int): MutableList {
requirePrecondition(fromIndex in 0..toIndex && toIndex <= size) {
"fromIndex or toIndex are out of bounds"
}
return SubList(this, fromIndex, toIndex)
}
@Suppress("UNCHECKED_CAST")
override fun toString(): String = (firstStateRecord as StateListStateRecord).withCurrent {
"SnapshotStateList(value=${it.list})@${hashCode()}"
}
override fun add(element: T) = conditionalUpdate { it.add(element) }
override fun add(index: Int, element: T) = update { it.add(index, element) }
override fun addAll(index: Int, elements: Collection) = mutateBoolean {
it.addAll(index, elements)
}
override fun addAll(elements: Collection) = conditionalUpdate { it.addAll(elements) }
override fun clear() {
writable {
synchronized(sync) {
list = persistentListOf()
modification++
structuralChange++
}
}
}
override fun remove(element: T) = conditionalUpdate { it.remove(element) }
override fun removeAll(elements: Collection) = conditionalUpdate { it.removeAll(elements) }
override fun removeAt(index: Int): T = get(index).also { update { it.removeAt(index) } }
override fun retainAll(elements: Collection) = mutateBoolean { it.retainAll(elements) }
override fun set(index: Int, element: T): T = get(index).also {
update(structural = false) { it.set(index, element) }
}
fun removeRange(fromIndex: Int, toIndex: Int) {
mutate {
it.subList(fromIndex, toIndex).clear()
}
}
internal fun retainAllInRange(elements: Collection, start: Int, end: Int): Int {
val startSize = size
mutate {
it.subList(start, end).retainAll(elements)
}
return startSize - size
}
/**
* An internal function used by the debugger to display the value of the current list without
* triggering read observers.
*/
@Suppress("unused")
internal val debuggerDisplayValue: List
@JvmName("getDebuggerDisplayValue")
get() = withCurrent { list }
private inline fun writable(block: StateListStateRecord.() -> R): R =
@Suppress("UNCHECKED_CAST")
(firstStateRecord as StateListStateRecord).writable(this, block)
private inline fun withCurrent(block: StateListStateRecord.() -> R): R =
@Suppress("UNCHECKED_CAST")
(firstStateRecord as StateListStateRecord).withCurrent(block)
private fun mutateBoolean(block: (MutableList) -> Boolean): Boolean = mutate(block)
private inline fun mutate(block: (MutableList) -> R): R {
var result: R
while (true) {
var oldList: PersistentList? = null
var currentModification = 0
synchronized(sync) {
val current = withCurrent { this }
currentModification = current.modification
oldList = current.list
}
val builder = oldList!!.builder()
result = block(builder)
val newList = builder.build()
if (newList == oldList || writable {
synchronized(sync) {
if (modification == currentModification) {
list = newList
modification++
structuralChange++
true
} else false
}
}
) break
}
return result
}
private inline fun update(
structural: Boolean = true,
block: (PersistentList) -> PersistentList
) {
conditionalUpdate(structural, block)
}
private inline fun conditionalUpdate(
structural: Boolean = true,
block: (PersistentList) -> PersistentList
) =
run {
val result: Boolean
while (true) {
var oldList: PersistentList? = null
var currentModification = 0
synchronized(sync) {
val current = withCurrent { this }
currentModification = current.modification
oldList = current.list
}
val newList = block(oldList!!)
if (newList == oldList) {
result = false
break
}
if (writable {
synchronized(sync) {
if (modification == currentModification) {
list = newList
if (structural) structuralChange++
modification++
true
} else false
}
}
) {
result = true
break
}
}
result
}
}
/**
* This lock is used to ensure that the value of modification and the list 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 list. This avoid additional allocations but introduces some contention
* between lists. 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 = SynchronizedObject()
private fun modificationError(): Nothing =
error("Cannot modify a state list through an iterator")
private fun validateRange(index: Int, size: Int) {
if (index !in 0 until size) {
throw IndexOutOfBoundsException("index ($index) is out of bound of [0, $size)")
}
}
private fun invalidIteratorSet(): Nothing =
error(
"Cannot call set before the first call to next() or previous() " +
"or immediately after a call to add() or remove()"
)
private class StateListIterator(
val list: SnapshotStateList,
offset: Int
) : MutableListIterator {
private var index = offset - 1
private var lastRequested = -1
private var structure = list.structure
override fun hasPrevious() = index >= 0
override fun nextIndex() = index + 1
override fun previous(): T {
validateModification()
validateRange(index, list.size)
lastRequested = index
return list[index].also { index-- }
}
override fun previousIndex(): Int = index
override fun add(element: T) {
validateModification()
list.add(index + 1, element)
lastRequested = -1
index++
structure = list.structure
}
override fun hasNext() = index < list.size - 1
override fun next(): T {
validateModification()
val newIndex = index + 1
lastRequested = newIndex
validateRange(newIndex, list.size)
return list[newIndex].also { index = newIndex }
}
override fun remove() {
validateModification()
list.removeAt(index)
index--
lastRequested = -1
structure = list.structure
}
override fun set(element: T) {
validateModification()
if (lastRequested < 0) invalidIteratorSet()
list.set(lastRequested, element)
structure = list.structure
}
private fun validateModification() {
if (list.structure != structure) {
throw ConcurrentModificationException()
}
}
}
private class SubList(
val parentList: SnapshotStateList,
fromIndex: Int,
toIndex: Int
) : MutableList {
private val offset = fromIndex
private var structure = parentList.structure
override var size = toIndex - fromIndex
private set
override fun contains(element: T): Boolean = indexOf(element) >= 0
override fun containsAll(elements: Collection): Boolean = elements.all { contains(it) }
override fun get(index: Int): T {
validateModification()
validateRange(index, size)
return parentList[offset + index]
}
override fun indexOf(element: T): Int {
validateModification()
(offset until offset + size).forEach {
if (element == parentList[it]) return it - offset
}
return -1
}
override fun isEmpty(): Boolean = size == 0
override fun iterator(): MutableIterator = listIterator()
override fun lastIndexOf(element: T): Int {
validateModification()
var index = offset + size - 1
while (index >= offset) {
if (element == parentList[index]) return index - offset
index--
}
return -1
}
override fun add(element: T): Boolean {
validateModification()
parentList.add(offset + size, element)
size++
structure = parentList.structure
return true
}
override fun add(index: Int, element: T) {
validateModification()
parentList.add(offset + index, element)
size++
structure = parentList.structure
}
override fun addAll(index: Int, elements: Collection): Boolean {
validateModification()
val result = parentList.addAll(index + offset, elements)
if (result) {
size += elements.size
structure = parentList.structure
}
return result
}
override fun addAll(elements: Collection): Boolean = addAll(size, elements)
override fun clear() {
if (size > 0) {
validateModification()
parentList.removeRange(offset, offset + size)
size = 0
structure = parentList.structure
}
}
override fun listIterator(): MutableListIterator = listIterator(0)
override fun listIterator(index: Int): MutableListIterator {
validateModification()
var current = index - 1
return object : MutableListIterator {
override fun hasPrevious() = current >= 0
override fun nextIndex(): Int = current + 1
override fun previous(): T {
val oldCurrent = current
validateRange(oldCurrent, size)
current = oldCurrent - 1
return this@SubList[oldCurrent]
}
override fun previousIndex(): Int = current
override fun add(element: T) = modificationError()
override fun hasNext(): Boolean = current < size - 1
override fun next(): T {
val newCurrent = current + 1
validateRange(newCurrent, size)
current = newCurrent
return this@SubList[newCurrent]
}
override fun remove() = modificationError()
override fun set(element: T) = modificationError()
}
}
override fun remove(element: T): Boolean {
val index = indexOf(element)
return if (index >= 0) {
removeAt(index)
true
} else false
}
override fun removeAll(elements: Collection): Boolean {
var removed = false
for (element in elements) {
removed = remove(element) || removed
}
return removed
}
override fun removeAt(index: Int): T {
validateModification()
return parentList.removeAt(offset + index).also {
size--
structure = parentList.structure
}
}
override fun retainAll(elements: Collection): Boolean {
validateModification()
val removed = parentList.retainAllInRange(elements, offset, offset + size)
if (removed > 0) {
structure = parentList.structure
size -= removed
}
return removed > 0
}
override fun set(index: Int, element: T): T {
validateRange(index, size)
validateModification()
val result = parentList.set(index + offset, element)
structure = parentList.structure
return result
}
override fun subList(fromIndex: Int, toIndex: Int): MutableList {
requirePrecondition(fromIndex in 0..toIndex && toIndex <= size) {
"fromIndex or toIndex are out of bounds"
}
validateModification()
return SubList(parentList, fromIndex + offset, toIndex + offset)
}
private fun validateModification() {
if (parentList.structure != structure) {
throw ConcurrentModificationException()
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy