Merge changes I842cb2e0,I476e3c43 into androidx-main
* changes:
Cache field access on internal sets and maps in runtime
Use IdentityArraySet to store snapshot invalidations
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index bc7d310..ddbb91e 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -20,6 +20,7 @@
import androidx.compose.runtime.collection.IdentityArrayMap
import androidx.compose.runtime.collection.IdentityArraySet
import androidx.compose.runtime.collection.IdentityScopeMap
+import androidx.compose.runtime.collection.fastForEach
import androidx.compose.runtime.snapshots.fastAll
import androidx.compose.runtime.snapshots.fastAny
import androidx.compose.runtime.snapshots.fastForEach
@@ -693,7 +694,7 @@
}
}
- for (value in values) {
+ values.fastForEach { value ->
if (value is RecomposeScopeImpl) {
value.invalidateForResult(null)
} else {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index b9d3905..8569cbf 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -199,7 +199,7 @@
private var runnerJob: Job? = null
private var closeCause: Throwable? = null
private val knownCompositions = mutableListOf<ControlledComposition>()
- private var snapshotInvalidations = mutableSetOf<Any>()
+ private var snapshotInvalidations = IdentityArraySet<Any>()
private val compositionInvalidations = mutableListOf<ControlledComposition>()
private val compositionsAwaitingApply = mutableListOf<ControlledComposition>()
private val compositionValuesAwaitingInsert = mutableListOf<MovableContentStateReference>()
@@ -280,7 +280,7 @@
private fun deriveStateLocked(): CancellableContinuation<Unit>? {
if (_state.value <= State.ShuttingDown) {
knownCompositions.clear()
- snapshotInvalidations = mutableSetOf()
+ snapshotInvalidations = IdentityArraySet()
compositionInvalidations.clear()
compositionsAwaitingApply.clear()
compositionValuesAwaitingInsert.clear()
@@ -296,7 +296,7 @@
State.Inactive
}
runnerJob == null -> {
- snapshotInvalidations = mutableSetOf()
+ snapshotInvalidations = IdentityArraySet()
compositionInvalidations.clear()
if (broadcastFrameClock.hasAwaiters) State.InactivePendingWork else State.Inactive
}
@@ -420,7 +420,7 @@
if (_state.value <= State.ShuttingDown) return@run
}
}
- snapshotInvalidations = mutableSetOf()
+ snapshotInvalidations = IdentityArraySet()
if (deriveStateLocked() != null) {
error("called outside of runRecomposeAndApplyChanges")
}
@@ -435,7 +435,7 @@
knownCompositions.fastForEach { composition ->
composition.recordModificationsOf(changes)
}
- snapshotInvalidations = mutableSetOf()
+ snapshotInvalidations = IdentityArraySet()
}
compositionInvalidations.fastForEach(onEachInvalidComposition)
compositionInvalidations.clear()
@@ -657,7 +657,7 @@
compositionsAwaitingApply.clear()
compositionInvalidations.clear()
- snapshotInvalidations = mutableSetOf()
+ snapshotInvalidations = IdentityArraySet()
compositionValuesAwaitingInsert.clear()
compositionValuesRemoved.clear()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayIntMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayIntMap.kt
index 272450c..5644c62 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayIntMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayIntMap.kt
@@ -17,18 +17,14 @@
package androidx.compose.runtime.collection
import androidx.compose.runtime.identityHashCode
-import kotlin.contracts.ExperimentalContracts
-@OptIn(ExperimentalContracts::class)
internal class IdentityArrayIntMap {
- @PublishedApi
internal var size = 0
-
- @PublishedApi
+ private set
internal var keys: Array<Any?> = arrayOfNulls(4)
-
- @PublishedApi
+ private set
internal var values: IntArray = IntArray(4)
+ private set
operator fun get(key: Any): Int {
val index = find(key)
@@ -38,6 +34,8 @@
* Add [value] to the map and return `-1` if it was added or previous value if it already existed.
*/
fun add(key: Any, value: Int): Int {
+ val values = values
+
val index: Int
if (size > 0) {
index = find(key)
@@ -52,6 +50,8 @@
val insertIndex = -(index + 1)
+ val keys = keys
+ val size = size
if (size == keys.size) {
val newKeys = arrayOfNulls<Any>(keys.size * 2)
val newValues = IntArray(keys.size * 2)
@@ -75,8 +75,8 @@
destination = newValues,
endIndex = insertIndex
)
- keys = newKeys
- values = newValues
+ this.keys = newKeys
+ this.values = newValues
} else {
keys.copyInto(
destination = keys,
@@ -91,9 +91,9 @@
endIndex = size
)
}
- keys[insertIndex] = key
- values[insertIndex] = value
- size++
+ this.keys[insertIndex] = key
+ this.values[insertIndex] = value
+ this.size++
return -1
}
@@ -103,6 +103,10 @@
*/
fun remove(key: Any): Boolean {
val index = find(key)
+
+ val keys = keys
+ val values = values
+ val size = size
if (index >= 0) {
if (index < size - 1) {
keys.copyInto(
@@ -118,8 +122,9 @@
endIndex = size
)
}
- size--
- keys[size] = null
+ val newSize = size - 1
+ keys[newSize] = null
+ this.size = newSize
return true
}
return false
@@ -129,6 +134,10 @@
* Removes all values that match [predicate].
*/
inline fun removeValueIf(predicate: (Any, Int) -> Boolean) {
+ val keys = keys
+ val values = values
+ val size = size
+
var destinationIndex = 0
for (i in 0 until size) {
@Suppress("UNCHECKED_CAST")
@@ -145,10 +154,14 @@
for (i in destinationIndex until size) {
keys[i] = null
}
- size = destinationIndex
+ this.size = destinationIndex
}
inline fun any(predicate: (Any, Int) -> Boolean): Boolean {
+ val keys = keys
+ val values = values
+ val size = size
+
for (i in 0 until size) {
if (predicate(keys[i] as Any, values[i])) return true
}
@@ -156,6 +169,10 @@
}
inline fun forEach(block: (Any, Int) -> Unit) {
+ val keys = keys
+ val values = values
+ val size = size
+
for (i in 0 until size) {
block(keys[i] as Any, values[i])
}
@@ -170,6 +187,7 @@
var high = size - 1
val valueIdentity = identityHashCode(key)
+ val keys = keys
while (low <= high) {
val mid = (low + high).ushr(1)
val midVal = keys[mid]
@@ -192,6 +210,9 @@
* be returned, which is always after the last item with the same [identityHashCode].
*/
private fun findExactIndex(midIndex: Int, value: Any?, valueHash: Int): Int {
+ val keys = keys
+ val size = size
+
// hunt down first
for (i in midIndex - 1 downTo 0) {
val v = keys[i]
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
index 13d31df..cb6d619 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
@@ -20,8 +20,11 @@
internal class IdentityArrayMap<Key : Any, Value : Any?>(capacity: Int = 16) {
internal var keys = arrayOfNulls<Any?>(capacity)
+ private set
internal var values = arrayOfNulls<Any?>(capacity)
+ private set
internal var size = 0
+ private set
fun isEmpty() = size == 0
fun isNotEmpty() = size > 0
@@ -35,6 +38,10 @@
}
operator fun set(key: Key, value: Value) {
+ val keys = keys
+ val values = values
+ val size = size
+
val index = find(key)
if (index >= 0) {
values[index] = value
@@ -57,7 +64,7 @@
)
}
destKeys[insertIndex] = key
- keys = destKeys
+ this.keys = destKeys
val destValues = if (resize) {
arrayOfNulls(size * 2)
} else values
@@ -74,8 +81,8 @@
)
}
destValues[insertIndex] = value
- values = destValues
- size++
+ this.values = destValues
+ this.size++
}
}
@@ -158,6 +165,7 @@
var low = 0
var high = size - 1
+ val keys = keys
while (low <= high) {
val mid = (low + high).ushr(1)
val midKey = keys[mid]
@@ -180,6 +188,9 @@
* be returned, which is always after the last key with the same [identityHashCode].
*/
private fun findExactIndex(midIndex: Int, key: Any?, keyHash: Int): Int {
+ val keys = keys
+ val size = size
+
// hunt down first
for (i in midIndex - 1 downTo 0) {
val k = keys[i]
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt
index 1f2404b..29721ca 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArraySet.kt
@@ -27,9 +27,11 @@
@OptIn(ExperimentalContracts::class)
internal class IdentityArraySet<T : Any> : Set<T> {
override var size = 0
+ private set
@PublishedApi
internal var values: Array<Any?> = arrayOfNulls(16)
+ private set
/**
* Returns true if the set contains [element]
@@ -51,6 +53,9 @@
*/
fun add(value: T): Boolean {
val index: Int
+ val size = size
+ val values = values
+
if (size > 0) {
index = find(value)
@@ -75,7 +80,7 @@
destination = newSorted,
endIndex = insertIndex
)
- values = newSorted
+ this.values = newSorted
} else {
values.copyInto(
destination = values,
@@ -84,8 +89,8 @@
endIndex = size
)
}
- values[insertIndex] = value
- size++
+ this.values[insertIndex] = value
+ this.size++
return true
}
@@ -94,7 +99,6 @@
*/
fun clear() {
values.fill(null)
-
size = 0
}
@@ -103,8 +107,125 @@
*/
inline fun fastForEach(block: (T) -> Unit) {
contract { callsInPlace(block) }
+ val values = values
for (i in 0 until size) {
- block(this[i])
+ @Suppress("UNCHECKED_CAST")
+ block(values[i] as T)
+ }
+ }
+
+ fun addAll(collection: Collection<T>) {
+ if (collection.isEmpty()) return
+
+ if (collection !is IdentityArraySet<T>) {
+ // Unknown collection, just add repeatedly
+ for (value in collection) {
+ add(value)
+ }
+ } else {
+ // Identity set, merge sorted arrays
+ val thisValues = values
+ val otherValues = collection.values
+ val thisSize = size
+ val otherSize = collection.size
+ val combinedSize = thisSize + otherSize
+
+ val needsResize = values.size < combinedSize
+ val elementsInOrder = thisSize == 0 ||
+ identityHashCode(thisValues[thisSize - 1]) < identityHashCode(otherValues[0])
+
+ if (!needsResize && elementsInOrder) {
+ // fast path, just copy target values
+ otherValues.copyInto(
+ destination = thisValues,
+ destinationOffset = thisSize,
+ startIndex = 0,
+ endIndex = otherSize
+ )
+ size += otherSize
+ } else {
+ // slow path, merge this and other values
+ val newArray = if (needsResize) {
+ arrayOfNulls(if (thisSize > otherSize) thisSize * 2 else otherSize * 2)
+ } else {
+ thisValues
+ }
+ var thisIndex = thisSize - 1
+ var otherIndex = otherSize - 1
+ var nextInsertIndex = combinedSize - 1
+ while (thisIndex >= 0 || otherIndex >= 0) {
+ val nextValue = when {
+ thisIndex < 0 -> otherValues[otherIndex--]
+ otherIndex < 0 -> thisValues[thisIndex--]
+ else -> {
+ val thisValue = thisValues[thisIndex]
+ val otherValue = otherValues[otherIndex]
+
+ val thisHash = identityHashCode(thisValue)
+ val otherHash = identityHashCode(otherValue)
+ when {
+ thisHash > otherHash -> {
+ thisIndex--
+ thisValue
+ }
+ thisHash < otherHash -> {
+ otherIndex--
+ otherValue
+ }
+ thisValue === otherValue -> {
+ // hash and the value are the same, advance both pointers
+ thisIndex--
+ otherIndex--
+ thisValue
+ }
+ else -> {
+ // collision, lookup if the same item is in the array
+ var i = thisIndex - 1
+ var foundDuplicate = false
+ while (i >= 0) {
+ val value = thisValues[i--]
+ if (identityHashCode(value) != otherHash) break
+ if (otherValue === value) {
+ foundDuplicate = true
+ break
+ }
+ }
+
+ if (foundDuplicate) {
+ // advance pointer and continue next iteration of outer
+ // merge loop.
+ otherIndex--
+ continue
+ } else {
+ // didn't find the duplicate, put other item in array.
+ otherIndex--
+ otherValue
+ }
+ }
+ }
+ }
+ }
+
+ // insert value and continue
+ newArray[nextInsertIndex--] = nextValue
+ }
+
+ if (nextInsertIndex >= 0) {
+ // some values were duplicated, copy the merged part
+ newArray.copyInto(
+ newArray,
+ destinationOffset = 0,
+ startIndex = nextInsertIndex + 1,
+ endIndex = combinedSize
+ )
+ }
+ // newSize = endOffset - startOffset of copy above
+ val newSize = combinedSize - (nextInsertIndex + 1)
+ newArray.fill(null, fromIndex = newSize, toIndex = combinedSize)
+
+ values = newArray
+ size = newSize
+ }
}
}
@@ -123,6 +244,9 @@
*/
fun remove(value: T): Boolean {
val index = find(value)
+ val values = values
+ val size = size
+
if (index >= 0) {
if (index < size - 1) {
values.copyInto(
@@ -132,8 +256,8 @@
endIndex = size
)
}
- size--
- values[size] = null
+ values[size - 1] = null
+ this.size--
return true
}
return false
@@ -143,6 +267,9 @@
* Removes all values that match [predicate].
*/
inline fun removeValueIf(predicate: (T) -> Boolean) {
+ val values = values
+ val size = size
+
var destinationIndex = 0
for (i in 0 until size) {
@Suppress("UNCHECKED_CAST")
@@ -157,7 +284,7 @@
for (i in destinationIndex until size) {
values[i] = null
}
- size = destinationIndex
+ this.size = destinationIndex
}
/**
@@ -168,10 +295,11 @@
var low = 0
var high = size - 1
val valueIdentity = identityHashCode(value)
+ val values = values
while (low <= high) {
val mid = (low + high).ushr(1)
- val midVal = get(mid)
+ val midVal = values[mid]
val midIdentity = identityHashCode(midVal)
when {
midIdentity < valueIdentity -> low = mid + 1
@@ -190,7 +318,14 @@
* If no match is found, the negative index - 1 of the position in which it would be will
* be returned, which is always after the last item with the same [identityHashCode].
*/
- private fun findExactIndex(midIndex: Int, value: Any?, valueHash: Int): Int {
+ private fun findExactIndex(
+ midIndex: Int,
+ value: Any?,
+ valueHash: Int
+ ): Int {
+ val values = values
+ val size = size
+
// hunt down first
for (i in midIndex - 1 downTo 0) {
val v = values[i]
@@ -240,4 +375,16 @@
override fun hasNext(): Boolean = index < size
override fun next(): T = [email protected][index++] as T
}
+
+ override fun toString(): String {
+ return joinToString(prefix = "[", postfix = "]") { it.toString() }
+ }
}
+
+internal inline fun <T : Any> Set<T>.fastForEach(block: (T) -> Unit) {
+ if (this is IdentityArraySet<T>) {
+ fastForEach(block)
+ } else {
+ forEach(block)
+ }
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityScopeMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityScopeMap.kt
index cf102a2..f94b85f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityScopeMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityScopeMap.kt
@@ -54,14 +54,6 @@
internal var size = 0
/**
- * Returns the value at the given [index] order in the map.
- */
- @Suppress("NOTHING_TO_INLINE")
- private inline fun valueAt(index: Int): Any {
- return values[valueOrder[index]]!!
- }
-
- /**
* Returns the [IdentityArraySet] for the value at the given [index] order in the map.
*/
private fun scopeSetAt(index: Int): IdentityArraySet<T> {
@@ -97,6 +89,11 @@
* and insertes it into the map and returns it.
*/
private fun getOrCreateIdentitySet(value: Any): IdentityArraySet<T> {
+ val size = size
+ val valueOrder = valueOrder
+ val values = values
+ val scopeSets = scopeSets
+
val index: Int
if (size > 0) {
index = find(value)
@@ -127,18 +124,18 @@
)
}
valueOrder[insertIndex] = valueIndex
- size++
+ this.size++
return scopeSet
}
// We have to increase the size of all arrays
val newSize = valueOrder.size * 2
val valueIndex = size
- scopeSets = scopeSets.copyOf(newSize)
+ val newScopeSets = scopeSets.copyOf(newSize)
val scopeSet = IdentityArraySet<T>()
- scopeSets[valueIndex] = scopeSet
- values = values.copyOf(newSize)
- values[valueIndex] = value
+ newScopeSets[valueIndex] = scopeSet
+ val newValues = values.copyOf(newSize)
+ newValues[valueIndex] = value
val newKeyOrder = IntArray(newSize)
for (i in size + 1 until newSize) {
@@ -160,8 +157,10 @@
endIndex = insertIndex
)
}
- valueOrder = newKeyOrder
- size++
+ this.scopeSets = newScopeSets
+ this.values = newValues
+ this.valueOrder = newKeyOrder
+ this.size++
return scopeSet
}
@@ -169,7 +168,11 @@
* Removes all values and scopes from the map
*/
fun clear() {
- for (i in 0 until scopeSets.size) {
+ val scopeSets = scopeSets
+ val valueOrder = valueOrder
+ val values = values
+
+ for (i in scopeSets.indices) {
scopeSets[i]?.clear()
valueOrder[i] = i
values[i] = null
@@ -188,6 +191,11 @@
*/
fun remove(value: Any, scope: T): Boolean {
val index = find(value)
+
+ val valueOrder = valueOrder
+ val scopeSets = scopeSets
+ val values = values
+ val size = size
if (index >= 0) {
val valueOrderIndex = valueOrder[index]
val set = scopeSets[valueOrderIndex] ?: return false
@@ -203,9 +211,10 @@
endIndex = endIndex
)
}
- valueOrder[size - 1] = valueOrderIndex
+ val newSize = size - 1
+ valueOrder[newSize] = valueOrderIndex
values[valueOrderIndex] = null
- size--
+ this.size = newSize
}
return removed
}
@@ -233,6 +242,9 @@
}
private inline fun removingScopes(removalOperation: (IdentityArraySet<T>) -> Unit) {
+ val valueOrder = valueOrder
+ val scopeSets = scopeSets
+ val values = values
var destinationIndex = 0
for (i in 0 until size) {
val valueIndex = valueOrder[i]
@@ -265,9 +277,11 @@
var low = 0
var high = size - 1
+ val values = values
+ val valueOrder = valueOrder
while (low <= high) {
val mid = (low + high).ushr(1)
- val midValue = valueAt(mid)
+ val midValue = values[valueOrder[mid]]
val midValHash = identityHashCode(midValue)
when {
midValHash < valueIdentity -> low = mid + 1
@@ -287,9 +301,12 @@
* be returned, which is always after the last item with the same [identityHashCode].
*/
private fun findExactIndex(midIndex: Int, value: Any?, valueHash: Int): Int {
+ val values = values
+ val valueOrder = valueOrder
+
// hunt down first
for (i in midIndex - 1 downTo 0) {
- val v = valueAt(i)
+ val v = values[valueOrder[i]]
if (v === value) {
return i
}
@@ -299,7 +316,7 @@
}
for (i in midIndex + 1 until size) {
- val v = valueAt(i)
+ val v = values[valueOrder[i]]
if (v === value) {
return i
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 84f7d21..d66cff4 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -24,6 +24,7 @@
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.SnapshotThreadLocal
+import androidx.compose.runtime.collection.IdentityArraySet
import androidx.compose.runtime.synchronized
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
@@ -211,7 +212,7 @@
/**
* The set of state objects that have been modified in this snapshot.
*/
- internal abstract val modified: MutableSet<StateObject>?
+ internal abstract val modified: IdentityArraySet<StateObject>?
/**
* Notify the snapshot that all objects created in this snapshot to this point should be
@@ -905,21 +906,21 @@
mergedRecords ?: mutableListOf<Pair<StateObject, StateRecord>>().also {
mergedRecords = it
}
- ).add(state to current.create())
+ ).add(state to current.create())
// If we revert to current then the state is no longer modified.
(
statesToRemove ?: mutableListOf<StateObject>().also {
statesToRemove = it
}
- ).add(state)
+ ).add(state)
}
else -> {
(
mergedRecords ?: mutableListOf<Pair<StateObject, StateRecord>>().also {
mergedRecords = it
}
- ).add(
+ ).add(
if (merged != previous) state to merged
else state to previous.create()
)
@@ -943,9 +944,9 @@
}
}
- statesToRemove?.let {
+ statesToRemove?.fastForEach {
// Remove from modified any state objects that have reverted to the parent value.
- modified.removeAll(it)
+ modified.remove(it)
}
return SnapshotApplyResult.Success
@@ -1003,10 +1004,10 @@
}
override fun recordModified(state: StateObject) {
- (modified ?: HashSet<StateObject>().also { modified = it }).add(state)
+ (modified ?: IdentityArraySet<StateObject>().also { modified = it }).add(state)
}
- override var modified: MutableSet<StateObject>? = null
+ override var modified: IdentityArraySet<StateObject>? = null
/**
* A set of the id's previously associated with this snapshot. When this snapshot closes
@@ -1206,7 +1207,7 @@
override fun hasPendingChanges(): Boolean = false
override val writeObserver: ((Any) -> Unit)? get() = null
- override var modified: HashSet<StateObject>?
+ override var modified: IdentityArraySet<StateObject>?
get() = null
@Suppress("UNUSED_PARAMETER")
set(value) = unsupported()
@@ -1277,7 +1278,7 @@
}
}
- override val modified: HashSet<StateObject>? get() = null
+ override val modified: IdentityArraySet<StateObject>? get() = null
override val writeObserver: ((Any) -> Unit)? get() = null
override fun recordModified(state: StateObject) = reportReadonlySnapshotWrite()
@@ -1401,10 +1402,10 @@
// Add all modified objects in this set to the parent
(
- parent.modified ?: HashSet<StateObject>().also {
+ parent.modified ?: IdentityArraySet<StateObject>().also {
parent.modified = it
}
- ).addAll(modified)
+ ).addAll(modified)
}
// Ensure the parent is newer than the current snapshot
@@ -1479,7 +1480,7 @@
override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
- override var modified: MutableSet<StateObject>?
+ override var modified: IdentityArraySet<StateObject>?
get() = currentSnapshot.modified
@Suppress("UNUSED_PARAMETER")
set(value) = unsupported()
@@ -1583,7 +1584,7 @@
override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
- override var modified: MutableSet<StateObject>?
+ override var modified: IdentityArraySet<StateObject>?
get() = currentSnapshot.modified
@Suppress("UNUSED_PARAMETER")
set(value) = unsupported()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index d8a6081..ad78a63 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -24,6 +24,7 @@
import androidx.compose.runtime.collection.IdentityArrayMap
import androidx.compose.runtime.collection.IdentityArraySet
import androidx.compose.runtime.collection.IdentityScopeMap
+import androidx.compose.runtime.collection.fastForEach
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.composeRuntimeError
import androidx.compose.runtime.observeDerivedStateRecalculations
@@ -509,7 +510,7 @@
*/
fun recordInvalidation(changes: Set<Any>): Boolean {
var hasValues = false
- for (value in changes) {
+ changes.fastForEach { value ->
if (value in dependencyToDerivedStates) {
// Find derived state that is invalidated by this change
dependencyToDerivedStates.forEachScopeOf(value) { derivedState ->
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt
index cad7e12..617e518 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/collection/IdentityArraySetTest.kt
@@ -182,6 +182,54 @@
assertTrue(setOfT.containsAll(listOf(stuff[0], stuff[1], stuff[2])))
}
+ @Test
+ fun addAll_Collection() {
+ set.addAll(list)
+
+ assertEquals(list.size, set.size)
+ for (value in list) {
+ assertTrue(value in set)
+ }
+ }
+
+ @Test
+ fun addAll_IdentityArraySet() {
+ val anotherSet = IdentityArraySet<Stuff>()
+ anotherSet.addAll(list)
+
+ set.addAll(anotherSet)
+
+ for (value in list) {
+ assertTrue(value in set)
+ }
+
+ set.addAll(anotherSet)
+
+ assertEquals(anotherSet.size, set.size)
+ for (value in list) {
+ assertTrue(value in set)
+ }
+
+ val stuff = Array(100) { Stuff(it) }
+ for (i in 0 until 100 step 2) {
+ anotherSet.add(stuff[i])
+ }
+ set.addAll(anotherSet)
+
+ for (i in stuff.indices) {
+ val value = stuff[i]
+ if (i % 2 == 0) {
+ assertTrue(value in set, "Expected to have element $i in $set")
+ } else {
+ assertFalse(value in set, "Didn't expect to have element $i in $set")
+ }
+ }
+
+ for (value in list) {
+ assertTrue(value in set)
+ }
+ }
+
private fun testRemoveValueAtIndex(index: Int) {
val value = set[index]
val initialSize = set.size
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
index 0a96bcf..40ce39e 100644
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
+++ b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
@@ -572,10 +572,19 @@
val snapshotInvalidations = recomposer.javaClass
.getDeclaredField("snapshotInvalidations")
.apply { isAccessible = true }
- .get(recomposer) as MutableCollection<*>
+ .get(recomposer)
compositionInvalidations.clear()
- snapshotInvalidations.clear()
applyObservers.clear()
+
+ if (snapshotInvalidations is MutableCollection<*>) {
+ snapshotInvalidations.clear()
+ } else {
+ // backed by IdentityArraySet
+ snapshotInvalidations.javaClass
+ .getDeclaredMethod("clear")
+ .apply { isAccessible = true }
+ .invoke(snapshotInvalidations)
+ }
}
val dispatcher =