Add `find` to `CompositionData`
Fixes: 239709530
Test: ./gradlew :compose:r:r:tDUT
Relnote: """A `find` method was added to `CompositionData` to
allow developer tools using this API to quickly find a
sub-group of composition using its identity."""
Change-Id: I5794ff3ee6a036d3e425b21d092dba3caaeed4bb
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index a7e4a54..f77f754 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -963,6 +963,7 @@
package androidx.compose.runtime.tooling {
public interface CompositionData {
+ method public default androidx.compose.runtime.tooling.CompositionGroup? find(Object identityToFind);
method public Iterable<androidx.compose.runtime.tooling.CompositionGroup> getCompositionGroups();
method public boolean isEmpty();
property public abstract Iterable<androidx.compose.runtime.tooling.CompositionGroup> compositionGroups;
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 416bb72..36d633f 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -1039,6 +1039,7 @@
package androidx.compose.runtime.tooling {
public interface CompositionData {
+ method public default androidx.compose.runtime.tooling.CompositionGroup? find(Object identityToFind);
method public Iterable<androidx.compose.runtime.tooling.CompositionGroup> getCompositionGroups();
method public boolean isEmpty();
property public abstract Iterable<androidx.compose.runtime.tooling.CompositionGroup> compositionGroups;
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 67b7b6f..f5864c1 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -1002,6 +1002,7 @@
package androidx.compose.runtime.tooling {
public interface CompositionData {
+ method public default androidx.compose.runtime.tooling.CompositionGroup? find(Object identityToFind);
method public Iterable<androidx.compose.runtime.tooling.CompositionGroup> getCompositionGroups();
method public boolean isEmpty();
property public abstract Iterable<androidx.compose.runtime.tooling.CompositionGroup> compositionGroups;
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index b60b182..8764766 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -596,6 +596,9 @@
override fun iterator(): Iterator<CompositionGroup> =
GroupIterator(this, 0, groupsSize)
+
+ override fun find(identityToFind: Any): CompositionGroup? =
+ SlotTableGroup(this, 0).find(identityToFind)
}
/**
@@ -2963,6 +2966,64 @@
if (index > parentAnchorPivot) index else size + index - parentAnchorPivot
}
+private class SlotTableGroup(
+ val table: SlotTable,
+ val group: Int,
+ val version: Int = table.version
+) : CompositionGroup, Iterable<CompositionGroup> {
+ override val isEmpty: Boolean get() = table.groups.groupSize(group) == 0
+
+ override val key: Any
+ get() = if (table.groups.hasObjectKey(group))
+ table.slots[table.groups.objectKeyIndex(group)]!!
+ else table.groups.key(group)
+
+ override val sourceInfo: String?
+ get() = if (table.groups.hasAux(group))
+ table.slots[table.groups.auxIndex(group)] as? String
+ else null
+
+ override val node: Any?
+ get() = if (table.groups.isNode(group))
+ table.slots[table.groups.nodeIndex(group)] else
+ null
+
+ override val data: Iterable<Any?> get() = DataIterator(table, group)
+
+ override val identity: Any
+ get() {
+ validateRead()
+ return table.read { it.anchor(group) }
+ }
+
+ override val compositionGroups: Iterable<CompositionGroup> get() = this
+
+ override fun iterator(): Iterator<CompositionGroup> {
+ validateRead()
+ return GroupIterator(
+ table,
+ group + 1,
+ group + table.groups.groupSize(group)
+ )
+ }
+
+ private fun validateRead() {
+ if (table.version != version) {
+ throw ConcurrentModificationException()
+ }
+ }
+
+ override fun find(identityToFind: Any): CompositionGroup? =
+ (identityToFind as? Anchor)?.let { anchor ->
+ if (table.ownsAnchor(anchor)) {
+ val anchorGroup = table.anchorIndex(anchor)
+ if (anchorGroup >= group && (anchorGroup - group < table.groups.groupSize(group)))
+ SlotTableGroup(table, anchorGroup, version)
+ else null
+ } else null
+ }
+}
+
private class GroupIterator(
val table: SlotTable,
start: Int,
@@ -2982,43 +3043,7 @@
val group = index
index += table.groups.groupSize(group)
- return object : CompositionGroup, Iterable<CompositionGroup> {
- override val isEmpty: Boolean get() = table.groups.groupSize(group) == 0
-
- override val key: Any
- get() = if (table.groups.hasObjectKey(group))
- table.slots[table.groups.objectKeyIndex(group)]!!
- else table.groups.key(group)
-
- override val sourceInfo: String?
- get() = if (table.groups.hasAux(group))
- table.slots[table.groups.auxIndex(group)] as? String
- else null
-
- override val node: Any?
- get() = if (table.groups.isNode(group))
- table.slots[table.groups.nodeIndex(group)] else
- null
-
- override val data: Iterable<Any?> get() = DataIterator(table, group)
-
- override val identity: Any
- get() {
- validateRead()
- return table.read { it.anchor(group) }
- }
-
- override val compositionGroups: Iterable<CompositionGroup> get() = this
-
- override fun iterator(): Iterator<CompositionGroup> {
- validateRead()
- return GroupIterator(
- table,
- group + 1,
- group + table.groups.groupSize(group)
- )
- }
- }
+ return SlotTableGroup(table, group, version)
}
private fun validateRead() {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt
index 856363c..16e9076 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt
@@ -39,6 +39,14 @@
* doesn't contain any child groups.
*/
val isEmpty: Boolean
+
+ /**
+ * Find a sub-group by identity. Returns `null` if the group is not found or the implementation
+ * of this interface does not support finding groups by their identity. In other words, a
+ * `null` result from this method should not be interpreted as the identity is not a group in
+ * the composition data.
+ */
+ fun find(identityToFind: Any): CompositionGroup? = null
}
/**
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/tooling/CompositionDataTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/tooling/CompositionDataTests.kt
index c68333f..bd2a660 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/tooling/CompositionDataTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/tooling/CompositionDataTests.kt
@@ -232,4 +232,22 @@
}
}
}
+
+ @Test
+ fun canFindAGroupInCompositionData() {
+ val slots = SlotTable().also {
+ it.write { writer ->
+ writer.insert {
+ writer.group(0) {
+ repeat(10) { index ->
+ writer.group(100 + index) { }
+ }
+ }
+ }
+ }
+ }
+
+ val identity = slots.compositionGroups.first().compositionGroups.drop(5).first().identity
+ assertEquals(identity, slots.find(identity!!)?.identity)
+ }
}
\ No newline at end of file