1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.runtime.snapshots
18
19 import androidx.compose.runtime.Stable
20 import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
21 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
22 import androidx.compose.runtime.platform.makeSynchronizedObject
23 import androidx.compose.runtime.platform.synchronized
24 import androidx.compose.runtime.requirePrecondition
25 import kotlin.jvm.JvmName
26
27 /**
28 * An implementation of [MutableList] that can be observed and snapshot. This is the result type
29 * created by [androidx.compose.runtime.mutableStateListOf].
30 *
31 * This class closely implements the same semantics as [ArrayList].
32 *
33 * @see androidx.compose.runtime.mutableStateListOf
34 */
35 @Stable
36 class SnapshotStateList<T> internal constructor(persistentList: PersistentList<T>) :
37 StateObject, MutableList<T>, RandomAccess {
38 constructor() : this(persistentListOf())
39
40 override var firstStateRecord: StateRecord = stateRecordWith(persistentList)
41 private set
42
prependStateRecordnull43 override fun prependStateRecord(value: StateRecord) {
44 value.next = firstStateRecord
45 @Suppress("UNCHECKED_CAST")
46 firstStateRecord = value as StateListStateRecord<T>
47 }
48
49 /**
50 * Return a list containing all the elements of this list.
51 *
52 * The list returned is immutable and returned will not change even if the content of the list
53 * is changed in the same snapshot. It also will be the same instance until the content is
54 * changed. It is not, however, guaranteed to be the same instance for the same list as adding
55 * and removing the same item from the this list might produce a different instance with the
56 * same content.
57 *
58 * This operation is O(1) and does not involve a physically copying the list. It instead returns
59 * the underlying immutable list used internally to store the content of the list.
60 *
61 * It is recommended to use [toList] when using returning the value of this list from
62 * [androidx.compose.runtime.snapshotFlow].
63 */
toListnull64 fun toList(): List<T> = readable.list
65
66 internal val structure: Int
67 get() = withCurrent { structuralChange }
68
69 @Suppress("UNCHECKED_CAST")
70 internal val readable: StateListStateRecord<T>
71 get() = (firstStateRecord as StateListStateRecord<T>).readable(this)
72
73 /** This is an internal implementation class of [SnapshotStateList]. Do not use. */
74 internal class StateListStateRecord<T>
75 internal constructor(snapshotId: SnapshotId, internal var list: PersistentList<T>) :
76 StateRecord(snapshotId) {
77 internal var modification = 0
78 internal var structuralChange = 0
79
assignnull80 override fun assign(value: StateRecord) {
81 synchronized(sync) {
82 @Suppress("UNCHECKED_CAST")
83 list = (value as StateListStateRecord<T>).list
84 modification = value.modification
85 structuralChange = value.structuralChange
86 }
87 }
88
createnull89 override fun create(): StateRecord = create(currentSnapshot().snapshotId)
90
91 override fun create(snapshotId: SnapshotId): StateRecord =
92 StateListStateRecord(snapshotId, list)
93 }
94
95 override val size: Int
96 get() = readable.list.size
97
98 override fun contains(element: T) = readable.list.contains(element)
99
100 override fun containsAll(elements: Collection<T>) = readable.list.containsAll(elements)
101
102 override fun get(index: Int) = readable.list[index]
103
104 override fun indexOf(element: T): Int = readable.list.indexOf(element)
105
106 override fun isEmpty() = readable.list.isEmpty()
107
108 override fun iterator(): MutableIterator<T> = listIterator()
109
110 override fun lastIndexOf(element: T) = readable.list.lastIndexOf(element)
111
112 override fun listIterator(): MutableListIterator<T> = StateListIterator(this, 0)
113
114 override fun listIterator(index: Int): MutableListIterator<T> = StateListIterator(this, index)
115
116 override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
117 requirePrecondition(fromIndex in 0..toIndex && toIndex <= size) {
118 "fromIndex or toIndex are out of bounds"
119 }
120 return SubList(this, fromIndex, toIndex)
121 }
122
123 @Suppress("UNCHECKED_CAST")
toStringnull124 override fun toString(): String =
125 (firstStateRecord as StateListStateRecord<T>).withCurrent {
126 "SnapshotStateList(value=${it.list})@${hashCode()}"
127 }
128
addnull129 override fun add(element: T) = conditionalUpdate { it.add(element) }
130
addnull131 override fun add(index: Int, element: T) = update { it.add(index, element) }
132
<lambda>null133 override fun addAll(index: Int, elements: Collection<T>) = mutateBoolean {
134 it.addAll(index, elements)
135 }
136
<lambda>null137 override fun addAll(elements: Collection<T>) = conditionalUpdate { it.addAll(elements) }
138
clearnull139 override fun clear() {
140 writable {
141 synchronized(sync) {
142 list = persistentListOf()
143 modification++
144 structuralChange++
145 }
146 }
147 }
148
<lambda>null149 override fun remove(element: T) = conditionalUpdate { it.remove(element) }
150
<lambda>null151 override fun removeAll(elements: Collection<T>) = conditionalUpdate { it.removeAll(elements) }
152
<lambda>null153 override fun removeAt(index: Int): T = get(index).also { update { it.removeAt(index) } }
154
<lambda>null155 override fun retainAll(elements: Collection<T>) = mutateBoolean { it.retainAll(elements) }
156
setnull157 override fun set(index: Int, element: T): T =
158 get(index).also { update(structural = false) { it.set(index, element) } }
159
removeRangenull160 fun removeRange(fromIndex: Int, toIndex: Int) {
161 mutate { it.subList(fromIndex, toIndex).clear() }
162 }
163
retainAllInRangenull164 internal fun retainAllInRange(elements: Collection<T>, start: Int, end: Int): Int {
165 val startSize = size
166 mutate<Unit> { it.subList(start, end).retainAll(elements) }
167 return startSize - size
168 }
169
170 /**
171 * An internal function used by the debugger to display the value of the current list without
172 * triggering read observers.
173 */
174 @Suppress("unused")
175 internal val debuggerDisplayValue: List<T>
<lambda>null176 @JvmName("getDebuggerDisplayValue") get() = withCurrent { list }
177
writablenull178 private inline fun <R> writable(block: StateListStateRecord<T>.() -> R): R =
179 @Suppress("UNCHECKED_CAST")
180 (firstStateRecord as StateListStateRecord<T>).writable(this, block)
181
182 private inline fun <R> withCurrent(block: StateListStateRecord<T>.() -> R): R =
183 @Suppress("UNCHECKED_CAST") (firstStateRecord as StateListStateRecord<T>).withCurrent(block)
184
185 private fun mutateBoolean(block: (MutableList<T>) -> Boolean): Boolean = mutate(block)
186
187 private inline fun <R> mutate(block: (MutableList<T>) -> R): R {
188 var result: R
189 while (true) {
190 var oldList: PersistentList<T>? = null
191 var currentModification = 0
192 synchronized(sync) {
193 val current = withCurrent { this }
194 currentModification = current.modification
195 oldList = current.list
196 }
197 val builder = oldList!!.builder()
198 result = block(builder)
199 val newList = builder.build()
200 if (
201 newList == oldList ||
202 writable { attemptUpdate(currentModification, newList, structural = true) }
203 )
204 break
205 }
206 return result
207 }
208
updatenull209 private inline fun update(
210 structural: Boolean = true,
211 block: (PersistentList<T>) -> PersistentList<T>
212 ) {
213 conditionalUpdate(structural, block)
214 }
215
conditionalUpdatenull216 private inline fun conditionalUpdate(
217 structural: Boolean = true,
218 block: (PersistentList<T>) -> PersistentList<T>
219 ) = run {
220 val result: Boolean
221 while (true) {
222 var oldList: PersistentList<T>? = null
223 var currentModification = 0
224 synchronized(sync) {
225 val current = withCurrent { this }
226 currentModification = current.modification
227 oldList = current.list
228 }
229 val newList = block(oldList!!)
230 if (newList == oldList) {
231 result = false
232 break
233 }
234 if (writable { attemptUpdate(currentModification, newList, structural) }) {
235 result = true
236 break
237 }
238 }
239 result
240 }
241
242 // NOTE: do not inline this method to avoid class verification failures, see b/369909868
StateListStateRecordnull243 private fun StateListStateRecord<T>.attemptUpdate(
244 currentModification: Int,
245 newList: PersistentList<T>,
246 structural: Boolean
247 ): Boolean =
248 synchronized(sync) {
249 if (modification == currentModification) {
250 list = newList
251 if (structural) structuralChange++
252 modification++
253 true
254 } else false
255 }
256
stateRecordWithnull257 private fun stateRecordWith(list: PersistentList<T>): StateRecord {
258 val snapshot = currentSnapshot()
259 return StateListStateRecord(snapshot.snapshotId, list).also {
260 if (snapshot !is GlobalSnapshot) {
261 it.next = StateListStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), list)
262 }
263 }
264 }
265 }
266
267 /**
268 * Creates a new snapshot state list with the specified [size], where each element is calculated by
269 * calling the specified [init] function.
270 *
271 * The function [init] is called for each list element sequentially starting from the first one. It
272 * should return the value for a list element given its index.
273 */
SnapshotStateListnull274 fun <T> SnapshotStateList(size: Int, init: (index: Int) -> T): SnapshotStateList<T> {
275 if (size == 0) {
276 return SnapshotStateList()
277 }
278
279 val builder = persistentListOf<T>().builder()
280 for (i in 0 until size) {
281 builder.add(init(i))
282 }
283 return SnapshotStateList(builder.build())
284 }
285
286 /**
287 * This lock is used to ensure that the value of modification and the list in the state record, when
288 * used together, are atomically read and written.
289 *
290 * A global sync object is used to avoid having to allocate a sync object and initialize a monitor
291 * for each instance the list. This avoid additional allocations but introduces some contention
292 * between lists. As there is already contention on the global snapshot lock to write so the
293 * additional contention introduced by this lock is nominal.
294 *
295 * In code the requires this lock and calls `writable` (or other operation that acquires the
296 * snapshot global lock), this lock *MUST* be acquired last to avoid deadlocks. In other words, the
297 * lock must be taken in the `writable` lambda, if `writable` is used.
298 */
299 private val sync = makeSynchronizedObject()
300
modificationErrornull301 private fun modificationError(): Nothing = error("Cannot modify a state list through an iterator")
302
303 private fun validateRange(index: Int, size: Int) {
304 if (index !in 0 until size) {
305 throw IndexOutOfBoundsException("index ($index) is out of bound of [0, $size)")
306 }
307 }
308
invalidIteratorSetnull309 private fun invalidIteratorSet(): Nothing =
310 error(
311 "Cannot call set before the first call to next() or previous() " +
312 "or immediately after a call to add() or remove()"
313 )
314
315 private class StateListIterator<T>(val list: SnapshotStateList<T>, offset: Int) :
316 MutableListIterator<T> {
317 private var index = offset - 1
318 private var lastRequested = -1
319 private var structure = list.structure
320
321 override fun hasPrevious() = index >= 0
322
323 override fun nextIndex() = index + 1
324
325 override fun previous(): T {
326 validateModification()
327 validateRange(index, list.size)
328 lastRequested = index
329 return list[index].also { index-- }
330 }
331
332 override fun previousIndex(): Int = index
333
334 override fun add(element: T) {
335 validateModification()
336 list.add(index + 1, element)
337 lastRequested = -1
338 index++
339 structure = list.structure
340 }
341
342 override fun hasNext() = index < list.size - 1
343
344 override fun next(): T {
345 validateModification()
346 val newIndex = index + 1
347 lastRequested = newIndex
348 validateRange(newIndex, list.size)
349 return list[newIndex].also { index = newIndex }
350 }
351
352 override fun remove() {
353 validateModification()
354 list.removeAt(index)
355 index--
356 lastRequested = -1
357 structure = list.structure
358 }
359
360 override fun set(element: T) {
361 validateModification()
362 if (lastRequested < 0) invalidIteratorSet()
363 list.set(lastRequested, element)
364 structure = list.structure
365 }
366
367 private fun validateModification() {
368 if (list.structure != structure) {
369 throw ConcurrentModificationException()
370 }
371 }
372 }
373
374 private class SubList<T>(val parentList: SnapshotStateList<T>, fromIndex: Int, toIndex: Int) :
375 MutableList<T> {
376 private val offset = fromIndex
377 private var structure = parentList.structure
378 override var size = toIndex - fromIndex
379 private set
380
containsnull381 override fun contains(element: T): Boolean = indexOf(element) >= 0
382
383 override fun containsAll(elements: Collection<T>): Boolean = elements.all { contains(it) }
384
getnull385 override fun get(index: Int): T {
386 validateModification()
387 validateRange(index, size)
388 return parentList[offset + index]
389 }
390
indexOfnull391 override fun indexOf(element: T): Int {
392 validateModification()
393 (offset until offset + size).forEach { if (element == parentList[it]) return it - offset }
394 return -1
395 }
396
isEmptynull397 override fun isEmpty(): Boolean = size == 0
398
399 override fun iterator(): MutableIterator<T> = listIterator()
400
401 override fun lastIndexOf(element: T): Int {
402 validateModification()
403 var index = offset + size - 1
404 while (index >= offset) {
405 if (element == parentList[index]) return index - offset
406 index--
407 }
408 return -1
409 }
410
addnull411 override fun add(element: T): Boolean {
412 validateModification()
413 parentList.add(offset + size, element)
414 size++
415 structure = parentList.structure
416 return true
417 }
418
addnull419 override fun add(index: Int, element: T) {
420 validateModification()
421 parentList.add(offset + index, element)
422 size++
423 structure = parentList.structure
424 }
425
addAllnull426 override fun addAll(index: Int, elements: Collection<T>): Boolean {
427 validateModification()
428 val result = parentList.addAll(index + offset, elements)
429 if (result) {
430 size += elements.size
431 structure = parentList.structure
432 }
433 return result
434 }
435
addAllnull436 override fun addAll(elements: Collection<T>): Boolean = addAll(size, elements)
437
438 override fun clear() {
439 if (size > 0) {
440 validateModification()
441 parentList.removeRange(offset, offset + size)
442 size = 0
443 structure = parentList.structure
444 }
445 }
446
listIteratornull447 override fun listIterator(): MutableListIterator<T> = listIterator(0)
448
449 override fun listIterator(index: Int): MutableListIterator<T> {
450 validateModification()
451 var current = index - 1
452 return object : MutableListIterator<T> {
453 override fun hasPrevious() = current >= 0
454
455 override fun nextIndex(): Int = current + 1
456
457 override fun previous(): T {
458 val oldCurrent = current
459 validateRange(oldCurrent, size)
460 current = oldCurrent - 1
461 return this@SubList[oldCurrent]
462 }
463
464 override fun previousIndex(): Int = current
465
466 override fun add(element: T) = modificationError()
467
468 override fun hasNext(): Boolean = current < size - 1
469
470 override fun next(): T {
471 val newCurrent = current + 1
472 validateRange(newCurrent, size)
473 current = newCurrent
474 return this@SubList[newCurrent]
475 }
476
477 override fun remove() = modificationError()
478
479 override fun set(element: T) = modificationError()
480 }
481 }
482
removenull483 override fun remove(element: T): Boolean {
484 val index = indexOf(element)
485 return if (index >= 0) {
486 removeAt(index)
487 true
488 } else false
489 }
490
removeAllnull491 override fun removeAll(elements: Collection<T>): Boolean {
492 var removed = false
493 for (element in elements) {
494 removed = remove(element) || removed
495 }
496 return removed
497 }
498
removeAtnull499 override fun removeAt(index: Int): T {
500 validateModification()
501 return parentList.removeAt(offset + index).also {
502 size--
503 structure = parentList.structure
504 }
505 }
506
retainAllnull507 override fun retainAll(elements: Collection<T>): Boolean {
508 validateModification()
509 val removed = parentList.retainAllInRange(elements, offset, offset + size)
510 if (removed > 0) {
511 structure = parentList.structure
512 size -= removed
513 }
514 return removed > 0
515 }
516
setnull517 override fun set(index: Int, element: T): T {
518 validateRange(index, size)
519 validateModification()
520 val result = parentList.set(index + offset, element)
521 structure = parentList.structure
522 return result
523 }
524
subListnull525 override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
526 requirePrecondition(fromIndex in 0..toIndex && toIndex <= size) {
527 "fromIndex or toIndex are out of bounds"
528 }
529 validateModification()
530 return SubList(parentList, fromIndex + offset, toIndex + offset)
531 }
532
validateModificationnull533 private fun validateModification() {
534 if (parentList.structure != structure) {
535 throw ConcurrentModificationException()
536 }
537 }
538 }
539