1 /*
<lambda>null2 * Copyright 2019 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 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
18
19 package androidx.compose.runtime
20
21 import androidx.collection.MutableIntList
22 import androidx.collection.MutableIntObjectMap
23 import androidx.collection.MutableIntSet
24 import androidx.collection.MutableObjectList
25 import androidx.collection.mutableIntListOf
26 import androidx.compose.runtime.collection.fastCopyInto
27 import androidx.compose.runtime.platform.makeSynchronizedObject
28 import androidx.compose.runtime.platform.synchronized
29 import androidx.compose.runtime.snapshots.fastAny
30 import androidx.compose.runtime.snapshots.fastFilterIndexed
31 import androidx.compose.runtime.snapshots.fastForEach
32 import androidx.compose.runtime.snapshots.fastMap
33 import androidx.compose.runtime.tooling.CompositionData
34 import androidx.compose.runtime.tooling.CompositionGroup
35 import kotlin.jvm.JvmInline
36 import kotlin.math.max
37 import kotlin.math.min
38
39 // Nomenclature -
40 // Address - an absolute offset into the array ignoring its gap. See Index below.
41 // Anchor - an encoding of Index that allows it to not need to be updated when groups or slots
42 // are inserted or deleted. An anchor is positive if the Index it is tracking is
43 // before the gap and negative if it is after the gap. If the Anchor is negative, it
44 // records its distance from the end of the array. If slots or groups are inserted or
45 // deleted this distance doesn't change but the index it represents automatically
46 // reflects the deleted or inserted groups or slots. Anchors only need to be updated
47 // if they track indexes whose group or slot Address moves when the gap moves.
48 // The term anchor is used not only for the Anchor class but for the parent anchor
49 // and data anchors in the group fields which are also anchors but not Anchor
50 // instances.
51 // Aux - auxiliary data that can be associated with a node and set independent of groups
52 // slots. This is used, for example, by the composer to record CompositionLocal
53 // maps as the map is not known at the when the group starts, only when the map is
54 // calculated after using an arbitrary number of slots.
55 // Data - the portion of the slot array associated with the group that contains the slots as
56 // well as the ObjectKey, Node, and Aux if present. The slots for a group are after
57 // the optional fixed group data.
58 // Group fields - a set of 5 contiguous integer elements in the groups array aligned at 5
59 // containing the key, node count, group size parent anchor and an anchor to the
60 // group data and flags to indicate if the group is a node, has Aux or has an
61 // ObjectKey. There are a set of extension methods used to access the group fields.
62 // Group - a contiguous range in the groups array. The groups is an inlined array of group
63 // fields. The group fields for a group and all of its children's fields comprise
64 // a group. A groups describes how to interpret the slot array. Groups have an
65 // integer key, and optional object key, node and aux and a 0 or more slots.
66 // Groups form a tree where the child groups are immediately after the group
67 // fields for the group. This data structure favors a linear scan of the children.
68 // Random access is expensive unless it is through a group anchor.
69 // Index - the logical index of a group or slot in its array. The index doesn't change when
70 // the gap moves. See Address and Anchor above. The index and address of a group
71 // or slot are identical if the gap is at the end of the buffer. This is taken
72 // advantage of in the SlotReader.
73 // Key - an Int value used as a key by the composer.
74 // Node - a value of a node group that can be set independently of the slots of the group.
75 // This is, for example, where the LayoutNode is stored by the slot table when
76 // emitting using the UIEmitter.
77 // ObjectKey - an object that can be used by the composer as part of a groups key. The key()
78 // composable, for example, produces an ObjectKey. Using the key composable
79 // function, for example, produces an ObjectKey value.
80 // Slot - and element in the slot array. Slots are managed by and addressed through a group.
81 // Slots are allocated in the slots array and are stored in order of their respective
82 // groups.
83
84 // All fields and variables referring to an array index are assumed to be Index values, not Address
85 // values, unless explicitly ending in Address.
86
87 // For simplicity and efficiency of the reader, the gaps are always moved to the end resulting in
88 // Index and Address being identical in a reader.
89
90 // The public API refers only to Index values. Address values are internal.
91
92 internal class SlotTable : CompositionData, Iterable<CompositionGroup> {
93 /**
94 * An array to store group information that is stored as groups of [Group_Fields_Size] elements
95 * of the array. The [groups] array can be thought of as an array of an inline struct.
96 */
97 var groups = IntArray(0)
98 private set
99
100 /** The number of groups contained in [groups]. */
101 var groupsSize = 0
102 private set
103
104 /**
105 * An array that stores the slots for a group. The slot elements for a group start at the offset
106 * returned by [dataAnchor] of [groups] and continue to the next group's slots or to [slotsSize]
107 * for the last group. When in a writer the [dataAnchor] is an anchor instead of an index as
108 * [slots] might contain a gap.
109 */
110 var slots = Array<Any?>(0) { null }
111 private set
112
113 /** The number of slots used in [slots]. */
114 var slotsSize = 0
115 private set
116
117 /**
118 * Tracks the number of active readers. A SlotTable can have multiple readers but only one
119 * writer.
120 */
121 private var readers = 0
122
123 private val lock = makeSynchronizedObject()
124
125 /** Tracks whether there is an active writer. */
126 internal var writer = false
127 private set
128
129 /**
130 * An internal version that is incremented whenever a writer is created. This is used to detect
131 * when an iterator created by [CompositionData] is invalid.
132 */
133 internal var version = 0
134
135 /** A list of currently active anchors. */
136 internal var anchors: ArrayList<Anchor> = arrayListOf()
137
138 /** A map of source information to anchor. */
139 internal var sourceInformationMap: HashMap<Anchor, GroupSourceInformation>? = null
140
141 /**
142 * A map of source marker numbers to their, potentially indirect, parent key. This is recorded
143 * for LiveEdit to allow a function that doesn't itself have a group to be invalidated.
144 */
145 internal var calledByMap: MutableIntObjectMap<MutableIntSet>? = null
146
147 /** Returns true if the slot table is empty */
148 override val isEmpty
149 get() = groupsSize == 0
150
151 /**
152 * Read the slot table in [block]. Any number of readers can be created but a slot table cannot
153 * be read while it is being written to.
154 *
155 * @see SlotReader
156 */
157 inline fun <T> read(block: (reader: SlotReader) -> T): T =
158 openReader().let { reader ->
159 try {
160 block(reader)
161 } finally {
162 reader.close()
163 }
164 }
165
166 /**
167 * Write to the slot table in [block]. Only one writer can be created for a slot table at a time
168 * and all readers must be closed an do readers can be created while the slot table is being
169 * written to.
170 *
171 * @see SlotWriter
172 */
173 inline fun <T> write(block: (writer: SlotWriter) -> T): T =
174 openWriter().let { writer ->
175 var normalClose = false
176 try {
177 block(writer).also { normalClose = true }
178 } finally {
179 writer.close(normalClose)
180 }
181 }
182
183 /**
184 * Open a reader. Any number of readers can be created but a slot table cannot be read while it
185 * is being written to.
186 *
187 * @see SlotReader
188 */
189 fun openReader(): SlotReader {
190 if (writer) error("Cannot read while a writer is pending")
191 readers++
192 return SlotReader(table = this)
193 }
194
195 /**
196 * Open a writer. Only one writer can be created for a slot table at a time and all readers must
197 * be closed an do readers can be created while the slot table is being written to.
198 *
199 * @see SlotWriter
200 */
201 fun openWriter(): SlotWriter {
202 runtimeCheck(!writer) { "Cannot start a writer when another writer is pending" }
203 runtimeCheck(readers <= 0) { "Cannot start a writer when a reader is pending" }
204 writer = true
205 version++
206 return SlotWriter(table = this)
207 }
208
209 /**
210 * Return an anchor to the given index. [anchorIndex] can be used to determine the current index
211 * of the group currently at [index] after a [SlotWriter] as inserted or removed groups or the
212 * group itself was moved. [Anchor.valid] will be `false` if the group at [index] was removed.
213 *
214 * If an anchor is moved using [SlotWriter.moveFrom] or [SlotWriter.moveTo] the anchor will move
215 * to be owned by the receiving table. [ownsAnchor] can be used to determine if the group at
216 * [index] is still in this table.
217 */
218 fun anchor(index: Int): Anchor {
219 runtimeCheck(!writer) { "use active SlotWriter to create an anchor location instead" }
220 requirePrecondition(index in 0 until groupsSize) { "Parameter index is out of range" }
221 return anchors.getOrAdd(index, groupsSize) { Anchor(index) }
222 }
223
224 /** Return an anchor to the given index if there is one already, null otherwise. */
225 private fun tryAnchor(index: Int): Anchor? {
226 runtimeCheck(!writer) { "use active SlotWriter to crate an anchor for location instead" }
227 return if (index in 0 until groupsSize) anchors.find(index, groupsSize) else null
228 }
229
230 /**
231 * Return the group index for [anchor]. This [SlotTable] is assumed to own [anchor] but that is
232 * not validated. If [anchor] is not owned by this [SlotTable] the result is undefined. If a
233 * [SlotWriter] is open the [SlotWriter.anchorIndex] must be called instead as [anchor] might be
234 * affected by the modifications being performed by the [SlotWriter].
235 */
236 fun anchorIndex(anchor: Anchor): Int {
237 runtimeCheck(!writer) { "Use active SlotWriter to determine anchor location instead" }
238 requirePrecondition(anchor.valid) { "Anchor refers to a group that was removed" }
239 return anchor.location
240 }
241
242 /**
243 * Returns true if [anchor] is owned by this [SlotTable] or false if it is owned by a different
244 * [SlotTable] or no longer part of this table (e.g. it was moved or the group it was an anchor
245 * for was removed).
246 */
247 fun ownsAnchor(anchor: Anchor): Boolean {
248 return anchor.valid &&
249 anchors.search(anchor.location, groupsSize).let { it >= 0 && anchors[it] == anchor }
250 }
251
252 /** Returns true if the [anchor] is for the group at [groupIndex] or one of it child groups. */
253 fun groupContainsAnchor(groupIndex: Int, anchor: Anchor): Boolean {
254 runtimeCheck(!writer) { "Writer is active" }
255 runtimeCheck(groupIndex in 0 until groupsSize) { "Invalid group index" }
256 return ownsAnchor(anchor) &&
257 anchor.location in groupIndex until (groupIndex + groups.groupSize(groupIndex))
258 }
259
260 /** Close [reader]. */
261 internal fun close(
262 reader: SlotReader,
263 sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?
264 ) {
265 runtimeCheck(reader.table === this && readers > 0) { "Unexpected reader close()" }
266 readers--
267 if (sourceInformationMap != null) {
268 synchronized(lock) {
269 val thisMap = this.sourceInformationMap
270 if (thisMap != null) {
271 thisMap.putAll(sourceInformationMap)
272 } else {
273 this.sourceInformationMap = sourceInformationMap
274 }
275 }
276 }
277 }
278
279 /**
280 * Close [writer] and adopt the slot arrays returned. The [SlotTable] is invalid until
281 * [SlotWriter.close] is called as the [SlotWriter] is modifying [groups] and [slots] directly
282 * and will only make copies of the arrays if the slot table grows.
283 */
284 internal fun close(
285 writer: SlotWriter,
286 groups: IntArray,
287 groupsSize: Int,
288 slots: Array<Any?>,
289 slotsSize: Int,
290 anchors: ArrayList<Anchor>,
291 sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?,
292 calledByMap: MutableIntObjectMap<MutableIntSet>?
293 ) {
294 requirePrecondition(writer.table === this && this.writer) { "Unexpected writer close()" }
295 this.writer = false
296 setTo(groups, groupsSize, slots, slotsSize, anchors, sourceInformationMap, calledByMap)
297 }
298
299 /**
300 * Used internally by [SlotWriter.moveFrom] to swap arrays with a slot table target [SlotTable]
301 * is empty.
302 */
303 internal fun setTo(
304 groups: IntArray,
305 groupsSize: Int,
306 slots: Array<Any?>,
307 slotsSize: Int,
308 anchors: ArrayList<Anchor>,
309 sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?,
310 calledByMap: MutableIntObjectMap<MutableIntSet>?
311 ) {
312 // Adopt the slots from the writer
313 this.groups = groups
314 this.groupsSize = groupsSize
315 this.slots = slots
316 this.slotsSize = slotsSize
317 this.anchors = anchors
318 this.sourceInformationMap = sourceInformationMap
319 this.calledByMap = calledByMap
320 }
321
322 /**
323 * Modifies the current slot table such that every group with the target key will be
324 * invalidated, and when recomposed, the content of those groups will be disposed and
325 * re-inserted.
326 *
327 * This is currently only used for developer tooling such as Live Edit to invalidate groups
328 * which we know will no longer have the same structure so we want to remove them before
329 * recomposing.
330 *
331 * Returns a list of groups if they were successfully invalidated. If this returns null then a
332 * full composition must be forced.
333 */
334 internal fun invalidateGroupsWithKey(target: Int): List<RecomposeScopeImpl>? {
335 val anchors = mutableListOf<Anchor>()
336 val scopes = mutableListOf<RecomposeScopeImpl>()
337 var allScopesFound = true
338 val set =
339 MutableIntSet().also {
340 it.add(target)
341 it.add(LIVE_EDIT_INVALID_KEY)
342 }
343 calledByMap?.get(target)?.let { set.addAll(it) }
344
345 // Invalidate groups with the target key
346 read { reader ->
347 fun scanGroup() {
348 val key = reader.groupKey
349 if (key in set) {
350 if (key != LIVE_EDIT_INVALID_KEY) anchors.add(reader.anchor())
351 if (allScopesFound) {
352 val nearestScope = findEffectiveRecomposeScope(reader.currentGroup)
353 if (nearestScope != null) {
354 scopes.add(nearestScope)
355 if (nearestScope.anchor?.location == reader.currentGroup) {
356 // For the group that contains the restart group then, in some
357 // cases, such as when the parameter names of a function change,
358 // the restart lambda can be invalid if it is called. To avoid this
359 // the scope parent scope needs to be invalidated too.
360 val parentScope = findEffectiveRecomposeScope(reader.parent)
361 parentScope?.let { scopes.add(it) }
362 }
363 } else {
364 allScopesFound = false
365 scopes.clear()
366 }
367 }
368 reader.skipGroup()
369 return
370 }
371 reader.startGroup()
372 while (!reader.isGroupEnd) {
373 scanGroup()
374 }
375 reader.endGroup()
376 }
377 scanGroup()
378 }
379
380 // Bash groups even if we could not invalidate it. The call is responsible for ensuring
381 // the group is recomposed when this happens.
382 write { writer ->
383 writer.startGroup()
384 anchors.fastForEach { anchor ->
385 if (anchor.toIndexFor(writer) >= writer.currentGroup) {
386 writer.seek(anchor)
387 writer.bashCurrentGroup()
388 }
389 }
390 writer.skipToGroupEnd()
391 writer.endGroup()
392 }
393
394 return if (allScopesFound) scopes else null
395 }
396
397 /** Turns true if the first group (considered the root group) contains a mark. */
398 fun containsMark(): Boolean {
399 return groupsSize > 0 && groups.containsMark(0)
400 }
401
402 fun sourceInformationOf(group: Int) =
403 sourceInformationMap?.let { map -> tryAnchor(group)?.let { anchor -> map[anchor] } }
404
405 /**
406 * Find the nearest recompose scope for [group] that, when invalidated, will cause [group] group
407 * to be recomposed. This will force non-restartable recompose scopes in between this [group]
408 * and the restartable group to recompose.
409 */
410 private fun findEffectiveRecomposeScope(group: Int): RecomposeScopeImpl? {
411 var current = group
412 while (current > 0) {
413 for (data in DataIterator(this, current)) {
414 if (data is RecomposeScopeImpl) {
415 if (data.used && current != group) return data else data.forcedRecompose = true
416 }
417 }
418 current = groups.parentAnchor(current)
419 }
420 return null
421 }
422
423 /**
424 * A debugging aid to validate the internal structure of the slot table. Throws an exception if
425 * the slot table is not in the expected shape.
426 */
427 fun verifyWellFormed() {
428 // If the check passes Address and Index are identical so there is no need for
429 // indexToAddress conversions.
430 var current = 0
431 fun validateGroup(parent: Int, parentEnd: Int): Int {
432 val group = current++
433 val parentIndex = groups.parentAnchor(group)
434 checkPrecondition(parentIndex == parent) {
435 "Invalid parent index detected at $group, expected parent index to be $parent " +
436 "found $parentIndex"
437 }
438 val end = group + groups.groupSize(group)
439 checkPrecondition(end <= groupsSize) {
440 "A group extends past the end of the table at $group"
441 }
442 checkPrecondition(end <= parentEnd) {
443 "A group extends past its parent group at $group"
444 }
445
446 val dataStart = groups.dataAnchor(group)
447 val dataEnd = if (group >= groupsSize - 1) slotsSize else groups.dataAnchor(group + 1)
448 checkPrecondition(dataEnd <= slots.size) {
449 "Slots for $group extend past the end of the slot table"
450 }
451 checkPrecondition(dataStart <= dataEnd) { "Invalid data anchor at $group" }
452 val slotStart = groups.slotAnchor(group)
453 checkPrecondition(slotStart <= dataEnd) { "Slots start out of range at $group" }
454 val minSlotsNeeded =
455 (if (groups.isNode(group)) 1 else 0) +
456 (if (groups.hasObjectKey(group)) 1 else 0) +
457 (if (groups.hasAux(group)) 1 else 0)
458 checkPrecondition(dataEnd - dataStart >= minSlotsNeeded) {
459 "Not enough slots added for group $group"
460 }
461 val isNode = groups.isNode(group)
462 checkPrecondition(!isNode || slots[groups.nodeIndex(group)] != null) {
463 "No node recorded for a node group at $group"
464 }
465 var nodeCount = 0
466 while (current < end) {
467 nodeCount += validateGroup(group, end)
468 }
469 val expectedNodeCount = groups.nodeCount(group)
470 val expectedSlotCount = groups.groupSize(group)
471 checkPrecondition(expectedNodeCount == nodeCount) {
472 "Incorrect node count detected at $group, " +
473 "expected $expectedNodeCount, received $nodeCount"
474 }
475 val actualSlotCount = current - group
476 checkPrecondition(expectedSlotCount == actualSlotCount) {
477 "Incorrect slot count detected at $group, expected $expectedSlotCount, received " +
478 "$actualSlotCount"
479 }
480 if (groups.containsAnyMark(group)) {
481 checkPrecondition(group <= 0 || groups.containsMark(parent)) {
482 "Expected group $parent to record it contains a mark because $group does"
483 }
484 }
485
486 return if (isNode) 1 else nodeCount
487 }
488
489 if (groupsSize > 0) {
490 while (current < groupsSize) {
491 validateGroup(-1, current + groups.groupSize(current))
492 }
493 checkPrecondition(current == groupsSize) {
494 "Incomplete group at root $current expected to be $groupsSize"
495 }
496 }
497
498 // Verify that slot gap contains all nulls
499 for (index in slotsSize until slots.size) {
500 checkPrecondition(slots[index] == null) {
501 "Non null value in the slot gap at index $index"
502 }
503 }
504
505 // Verify anchors are well-formed
506 var lastLocation = -1
507 anchors.fastForEach { anchor ->
508 val location = anchor.toIndexFor(this)
509 requirePrecondition(location in 0..groupsSize) {
510 "Invalid anchor, location out of bound"
511 }
512 requirePrecondition(lastLocation < location) { "Anchor is out of order" }
513 lastLocation = location
514 }
515
516 // Verify source information is well-formed
517 fun verifySourceGroup(group: GroupSourceInformation) {
518 group.groups?.fastForEach { item ->
519 when (item) {
520 is Anchor -> {
521 requirePrecondition(item.valid) { "Source map contains invalid anchor" }
522 requirePrecondition(ownsAnchor(item)) {
523 "Source map anchor is not owned by the slot table"
524 }
525 }
526 is GroupSourceInformation -> verifySourceGroup(item)
527 }
528 }
529 }
530
531 sourceInformationMap?.let { sourceInformationMap ->
532 for ((anchor, sourceGroup) in sourceInformationMap) {
533 requirePrecondition(anchor.valid) { "Source map contains invalid anchor" }
534 requirePrecondition(ownsAnchor(anchor)) {
535 "Source map anchor is not owned by the slot table"
536 }
537 verifySourceGroup(sourceGroup)
538 }
539 }
540 }
541
542 fun collectCalledByInformation() {
543 calledByMap = MutableIntObjectMap()
544 }
545
546 fun collectSourceInformation() {
547 sourceInformationMap = HashMap()
548 }
549
550 /**
551 * A debugging aid that renders the slot table as a string. [toString] is avoided as producing
552 * this information is potentially a very expensive operation for large slot tables and calling
553 * this function in the debugger should never be implicit which it often is for [toString]
554 */
555 @Suppress("unused", "MemberVisibilityCanBePrivate")
556 fun toDebugString(): String {
557 return if (writer) super.toString()
558 else
559 buildString {
560 append(super.toString())
561 append('\n')
562 val groupsSize = groupsSize
563 if (groupsSize > 0) {
564 var current = 0
565 while (current < groupsSize) {
566 current += emitGroup(current, 0)
567 }
568 } else append("<EMPTY>")
569 }
570 }
571
572 /** A helper function used by [toDebugString] to render a particular group. */
573 private fun StringBuilder.emitGroup(index: Int, level: Int): Int {
574 repeat(level) { append(' ') }
575 append("Group(")
576 append(index)
577 append(")")
578 sourceInformationOf(index)?.sourceInformation?.let {
579 if (it.startsWith("C(") || it.startsWith("CC(")) {
580 val start = it.indexOf("(") + 1
581 val endParen = it.indexOf(')')
582 append(" ")
583 append(it.substring(start, endParen))
584 append("()")
585 }
586 }
587 append(" key=")
588 append(groups.key(index))
589 fun dataIndex(index: Int) = if (index >= groupsSize) slotsSize else groups.dataAnchor(index)
590
591 val groupSize = groups.groupSize(index)
592 append(", nodes=")
593 append(groups.nodeCount(index))
594 append(", size=")
595 append(groupSize)
596 if (groups.hasMark(index)) {
597 append(", mark")
598 }
599 if (groups.containsMark(index)) {
600 append(", contains mark")
601 }
602 val dataStart = dataIndex(index)
603 val dataEnd = dataIndex(index + 1)
604 if (dataStart in 0..dataEnd && dataEnd <= slotsSize) {
605 if (groups.hasObjectKey(index)) {
606 append(
607 " objectKey=${slots[
608 groups.objectKeyIndex(index)].toString().summarize(10)
609 }"
610 )
611 }
612 if (groups.isNode(index)) {
613 append(" node=${slots[groups.nodeIndex(index)].toString().summarize(10)}")
614 }
615 if (groups.hasAux(index)) {
616 append(" aux=${slots[groups.auxIndex(index)].toString().summarize(10)}")
617 }
618 val slotStart = groups.slotAnchor(index)
619 if (slotStart < dataEnd) {
620 append(", slots=[")
621 append(slotStart)
622 append(": ")
623 for (dataIndex in slotStart until dataEnd) {
624 if (dataIndex != slotStart) append(", ")
625 append(slots[dataIndex].toString().summarize(10))
626 }
627 append("]")
628 }
629 } else {
630 append(", *invalid data offsets $dataStart-$dataEnd*")
631 }
632 append('\n')
633 var current = index + 1
634 val end = index + groupSize
635 while (current < end) {
636 current += emitGroup(current, level + 1)
637 }
638 return groupSize
639 }
640
641 /** A debugging aid to list all the keys [key] values in the [groups] array. */
642 @Suppress("unused") private fun keys() = groups.keys(groupsSize * Group_Fields_Size)
643
644 /** A debugging aid to list all the [nodeCount] values in the [groups] array. */
645 @Suppress("unused") private fun nodes() = groups.nodeCounts(groupsSize * Group_Fields_Size)
646
647 /** A debugging aid to list all the [parentAnchor] values in the [groups] array. */
648 @Suppress("unused")
649 private fun parentIndexes() = groups.parentAnchors(groupsSize * Group_Fields_Size)
650
651 /** A debugging aid to list all the indexes into the [slots] array from the [groups] array. */
652 @Suppress("unused")
653 private fun dataIndexes() = groups.dataAnchors(groupsSize * Group_Fields_Size)
654
655 /** A debugging aid to list the [groupsSize] of all the groups in [groups]. */
656 @Suppress("unused") private fun groupSizes() = groups.groupSizes(groupsSize * Group_Fields_Size)
657
658 @Suppress("unused")
659 internal fun slotsOf(group: Int): List<Any?> {
660 val start = groups.dataAnchor(group)
661 val end = if (group + 1 < groupsSize) groups.dataAnchor(group + 1) else slots.size
662 return slots.toList().subList(start, end)
663 }
664
665 internal fun slot(group: Int, slotIndex: Int): Any? {
666 val start = groups.slotAnchor(group)
667 val end = if (group + 1 < groupsSize) groups.dataAnchor(group + 1) else slots.size
668 val len = end - start
669 return if (slotIndex in 0 until len) return slots[start + slotIndex] else Composer.Empty
670 }
671
672 override val compositionGroups: Iterable<CompositionGroup>
673 get() = this
674
675 override fun iterator(): Iterator<CompositionGroup> = GroupIterator(this, 0, groupsSize)
676
677 override fun find(identityToFind: Any): CompositionGroup? =
678 SlotTableGroup(this, 0).find(identityToFind)
679 }
680
681 /**
682 * An [Anchor] tracks a groups as its index changes due to other groups being inserted and removed
683 * before it. If the group the [Anchor] is tracking is removed, directly or indirectly, [valid] will
684 * return false. The current index of the group can be determined by passing either the [SlotTable]
685 * or [SlotWriter] to [toIndexFor]. If a [SlotWriter] is active, it must be used instead of the
686 * [SlotTable] as the anchor index could have shifted due to operations performed on the writer.
687 */
688 internal class Anchor(loc: Int) {
689 internal var location: Int = loc
690 val valid
691 get() = location != Int.MIN_VALUE
692
toIndexFornull693 fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this)
694
695 fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this)
696
697 override fun toString(): String {
698 return "${super.toString()}{ location = $location }"
699 }
700 }
701
702 internal class GroupSourceInformation(
703 val key: Int,
704 var sourceInformation: String?,
705 val dataStartOffset: Int
706 ) {
707 var groups: ArrayList<Any /* Anchor | GroupSourceInformation */>? = null
708 var closed = false
709 var dataEndOffset: Int = 0
710
startGrouplessCallnull711 fun startGrouplessCall(key: Int, sourceInformation: String, dataOffset: Int) {
712 openInformation().add(GroupSourceInformation(key, sourceInformation, dataOffset))
713 }
714
endGrouplessCallnull715 fun endGrouplessCall(dataOffset: Int) {
716 openInformation().close(dataOffset)
717 }
718
reportGroupnull719 fun reportGroup(writer: SlotWriter, group: Int) {
720 openInformation().add(writer.anchor(group))
721 }
722
reportGroupnull723 fun reportGroup(table: SlotTable, group: Int) {
724 openInformation().add(table.anchor(group))
725 }
726
addGroupAfternull727 fun addGroupAfter(writer: SlotWriter, predecessor: Int, group: Int) {
728 val groups = groups ?: ArrayList<Any>().also { groups = it }
729 val index =
730 if (predecessor >= 0) {
731 val anchor = writer.tryAnchor(predecessor)
732 if (anchor != null) {
733 groups.fastIndexOf {
734 it == anchor || (it is GroupSourceInformation && it.hasAnchor(anchor))
735 }
736 } else 0
737 } else 0
738 groups.add(index, writer.anchor(group))
739 }
740
closenull741 fun close(dataOffset: Int) {
742 closed = true
743 dataEndOffset = dataOffset
744 }
745
746 // Return the current open nested source information or this.
openInformationnull747 private fun openInformation(): GroupSourceInformation =
748 (groups?.let { groups ->
749 groups.fastLastOrNull { it is GroupSourceInformation && !it.closed }
750 } as? GroupSourceInformation)
751 ?.openInformation() ?: this
752
addnull753 private fun add(group: Any /* Anchor | GroupSourceInformation */) {
754 val groups = groups ?: ArrayList()
755 this.groups = groups
756 groups.add(group)
757 }
758
hasAnchornull759 private fun hasAnchor(anchor: Anchor): Boolean =
760 groups?.fastAny {
761 it == anchor || (it is GroupSourceInformation && it.hasAnchor(anchor))
762 } == true
763
removeAnchornull764 fun removeAnchor(anchor: Anchor): Boolean {
765 val groups = groups
766 if (groups != null) {
767 var index = groups.size - 1
768 while (index >= 0) {
769 when (val item = groups[index]) {
770 is Anchor -> if (item == anchor) groups.removeAt(index)
771 is GroupSourceInformation ->
772 if (!item.removeAnchor(anchor)) {
773 groups.removeAt(index)
774 }
775 }
776 index--
777 }
778 if (groups.isEmpty()) {
779 this.groups = null
780 return false
781 }
782 return true
783 }
784 return true
785 }
786 }
787
fastLastOrNullnull788 private inline fun <T> ArrayList<T>.fastLastOrNull(predicate: (T) -> Boolean): T? {
789 var index = size - 1
790 while (index >= 0) {
791 val value = get(index)
792 if (predicate(value)) return value
793 index--
794 }
795 return null
796 }
797
fastIndexOfnull798 private inline fun <T> ArrayList<T>.fastIndexOf(predicate: (T) -> Boolean): Int {
799 var index = 0
800 val size = size
801 while (index < size) {
802 val value = get(index)
803 if (predicate(value)) return index
804 index++
805 }
806 return -1
807 }
808
809 /** A reader of a slot table. See [SlotTable] */
810 internal class SlotReader(
811 /** The table for whom this is a reader. */
812 internal val table: SlotTable
813 ) {
814
815 /** A copy of the [SlotTable.groups] array to avoid having indirect through [table]. */
816 private val groups: IntArray = table.groups
817
818 /** A copy of [SlotTable.groupsSize] to avoid having to indirect through [table]. */
819 private val groupsSize: Int = table.groupsSize
820
821 /** A copy of [SlotTable.slots] to avoid having to indirect through [table]. */
822 private val slots: Array<Any?> = table.slots
823
824 /** A Copy of [SlotTable.slotsSize] to avoid having to indirect through [table]. */
825 private val slotsSize: Int = table.slotsSize
826
827 /**
828 * A local copy of the [sourceInformationMap] being created to be merged into [table] when the
829 * reader closes.
830 */
831 private var sourceInformationMap: HashMap<Anchor, GroupSourceInformation>? = null
832
833 /** True if the reader has been closed */
834 var closed: Boolean = false
835 private set
836
837 /** The current group that will be started with [startGroup] or skipped with [skipGroup]. */
838 var currentGroup = 0
839
840 /** The end of the [parent] group. */
841 var currentEnd = groupsSize
842 private set
843
844 /** The parent of the [currentGroup] group which is the last group started with [startGroup]. */
845 var parent = -1
846 private set
847
848 /** The current location of the current slot to restore [endGroup] is called. */
849 private val currentSlotStack = IntStack()
850
851 /** The number of times [beginEmpty] has been called. */
852 private var emptyCount = 0
853
854 /**
855 * The current slot of [parent]. This slot will be the next slot returned by [next] unless it is
856 * equal ot [currentSlotEnd].
857 */
858 private var currentSlot = 0
859
860 /** The current end slot of [parent]. */
861 private var currentSlotEnd = 0
862
863 /** Return the total number of groups in the slot table. */
864 val size: Int
865 get() = groupsSize
866
867 /** Return the current slot of the group whose value will be returned by calling [next]. */
868 val slot: Int
869 get() = currentSlot - groups.slotAnchor(parent)
870
871 /** Return the parent index of [index]. */
parentnull872 fun parent(index: Int) = groups.parentAnchor(index)
873
874 /** Determine if the slot is start of a node. */
875 val isNode: Boolean
876 get() = groups.isNode(currentGroup)
877
878 /** Determine if the group at [index] is a node. */
879 fun isNode(index: Int) = groups.isNode(index)
880
881 /**
882 * The number of nodes managed by the current group. For node groups, this is the list of the
883 * children nodes.
884 */
885 val nodeCount: Int
886 get() = groups.nodeCount(currentGroup)
887
888 /** Return the number of nodes for the group at [index]. */
889 fun nodeCount(index: Int) = groups.nodeCount(index)
890
891 /** Return the node at [index] if [index] is a node group else null. */
892 fun node(index: Int): Any? = if (groups.isNode(index)) groups.node(index) else null
893
894 /** Determine if the reader is at the end of a group and an [endGroup] is expected. */
895 val isGroupEnd
896 get() = inEmpty || currentGroup == currentEnd
897
898 /** Determine if a [beginEmpty] has been called. */
899 val inEmpty
900 get() = emptyCount > 0
901
902 /** Get the size of the group at [currentGroup]. */
903 val groupSize
904 get() = groups.groupSize(currentGroup)
905
906 /**
907 * Get the size of the group at [index]. Will throw an exception if [index] is not a group
908 * start.
909 */
910 fun groupSize(index: Int) = groups.groupSize(index)
911
912 /** Get the slot size for [group]. Will throw an exception if [group] is not a group start. */
913 fun slotSize(group: Int): Int {
914 val start = groups.slotAnchor(group)
915 val next = group + 1
916 val end = if (next < groupsSize) groups.dataAnchor(next) else slotsSize
917 return end - start
918 }
919
920 /** Get location the end of the currently started group. */
921 val groupEnd
922 get() = currentEnd
923
924 /** Get location of the end of the group at [index]. */
groupEndnull925 fun groupEnd(index: Int) = index + groups.groupSize(index)
926
927 /** Get the key of the current group. Returns 0 if the [currentGroup] is not a group. */
928 val groupKey
929 get() =
930 if (currentGroup < currentEnd) {
931 groups.key(currentGroup)
932 } else 0
933
934 /** Get the key of the group at [index]. */
groupKeynull935 fun groupKey(index: Int) = groups.key(index)
936
937 /**
938 * The group slot index is the index into the current groups slots that will be updated by read
939 * by [next].
940 */
941 val groupSlotIndex
942 get() = currentSlot - groups.slotAnchor(parent)
943
944 /** Determine if the group at [index] has an object key */
945 fun hasObjectKey(index: Int) = groups.hasObjectKey(index)
946
947 val hasObjectKey: Boolean
948 get() = currentGroup < currentEnd && groups.hasObjectKey(currentGroup)
949
950 /** Get the object key for the current group or null if no key was provide */
951 val groupObjectKey
952 get() = if (currentGroup < currentEnd) groups.objectKey(currentGroup) else null
953
954 /** Get the object key at [index]. */
955 fun groupObjectKey(index: Int) = groups.objectKey(index)
956
957 /** Get the current group aux data. */
958 val groupAux
959 get() = if (currentGroup < currentEnd) groups.aux(currentGroup) else 0
960
961 /** Get the aux data for the group at [index] */
962 fun groupAux(index: Int) = groups.aux(index)
963
964 /** Get the node associated with the group if there is one. */
965 val groupNode
966 get() = if (currentGroup < currentEnd) groups.node(currentGroup) else null
967
968 /** Get the group key at [anchor]. This return 0 if the anchor is not valid. */
969 fun groupKey(anchor: Anchor) = if (anchor.valid) groups.key(table.anchorIndex(anchor)) else 0
970
971 /** Returns true when the group at [index] was marked with [SlotWriter.markGroup]. */
972 fun hasMark(index: Int) = groups.hasMark(index)
973
974 /**
975 * Returns true if the group contains a group, directly or indirectly, that has been marked by a
976 * call to [SlotWriter.markGroup].
977 */
978 fun containsMark(index: Int) = groups.containsMark(index)
979
980 /** Return the number of nodes where emitted into the current group. */
981 val parentNodes: Int
982 get() = if (parent >= 0) groups.nodeCount(parent) else 0
983
984 /** Return the number of slots left to enumerate with [next]. */
985 val remainingSlots
986 get(): Int = currentSlotEnd - currentSlot
987
988 /** Return the index of the parent group of the given group */
989 fun parentOf(index: Int): Int {
990 @Suppress("ConvertTwoComparisonsToRangeCheck")
991 requirePrecondition(index >= 0 && index < groupsSize) { "Invalid group index $index" }
992 return groups.parentAnchor(index)
993 }
994
995 /** Return the number of slots allocated to the [currentGroup] group. */
996 val groupSlotCount: Int
997 get() {
998 val current = currentGroup
999 val start = groups.slotAnchor(current)
1000 val next = current + 1
1001 val end = if (next < groupsSize) groups.dataAnchor(next) else slotsSize
1002 return end - start
1003 }
1004
1005 /** Get the value stored at [index] in the parent group's slot. */
getnull1006 fun get(index: Int) =
1007 (currentSlot + index).let { slotIndex ->
1008 if (slotIndex < currentSlotEnd) slots[slotIndex] else Composer.Empty
1009 }
1010
1011 /** Get the value of the group's slot at [index] for the [currentGroup] group. */
groupGetnull1012 fun groupGet(index: Int): Any? = groupGet(currentGroup, index)
1013
1014 /** Get the slot value of the [group] at [index] */
1015 fun groupGet(group: Int, index: Int): Any? {
1016 val start = groups.slotAnchor(group)
1017 val next = group + 1
1018 val end = if (next < groupsSize) groups.dataAnchor(next) else slotsSize
1019 val address = start + index
1020 return if (address < end) slots[address] else Composer.Empty
1021 }
1022
1023 /**
1024 * Get the value of the slot at [currentGroup] or [Composer.Empty] if at then end of a group.
1025 * During empty mode this value is always [Composer.Empty] which is the value a newly inserted
1026 * slot.
1027 */
nextnull1028 fun next(): Any? {
1029 if (emptyCount > 0 || currentSlot >= currentSlotEnd) {
1030 hadNext = false
1031 return Composer.Empty
1032 }
1033 hadNext = true
1034 return slots[currentSlot++]
1035 }
1036
1037 /** `true` if the last call to `next()` returned a slot value and [currentSlot] advanced. */
1038 var hadNext: Boolean = false
1039 private set
1040
1041 /**
1042 * Begin reporting empty for all calls to next() or get(). beginEmpty() can be nested and must
1043 * be called with a balanced number of endEmpty()
1044 */
beginEmptynull1045 fun beginEmpty() {
1046 emptyCount++
1047 }
1048
1049 /** End reporting [Composer.Empty] for calls to [next] and [get], */
endEmptynull1050 fun endEmpty() {
1051 requirePrecondition(emptyCount > 0) { "Unbalanced begin/end empty" }
1052 emptyCount--
1053 }
1054
1055 /**
1056 * Close the slot reader. After all [SlotReader]s have been closed the [SlotTable] a
1057 * [SlotWriter] can be created.
1058 */
closenull1059 fun close() {
1060 closed = true
1061 table.close(this, sourceInformationMap)
1062 }
1063
1064 /** Start a group. */
startGroupnull1065 fun startGroup() {
1066 if (emptyCount <= 0) {
1067 val parent = parent
1068 val currentGroup = currentGroup
1069 requirePrecondition(groups.parentAnchor(currentGroup) == parent) {
1070 "Invalid slot table detected"
1071 }
1072 sourceInformationMap?.get(anchor(parent))?.reportGroup(table, currentGroup)
1073 val currentSlotStack = currentSlotStack
1074 val currentSlot = currentSlot
1075 val currentEndSlot = currentSlotEnd
1076 if (currentSlot == 0 && currentEndSlot == 0) {
1077 currentSlotStack.push(-1)
1078 } else {
1079 currentSlotStack.push(currentSlot)
1080 }
1081 this.parent = currentGroup
1082 currentEnd = currentGroup + groups.groupSize(currentGroup)
1083 this.currentGroup = currentGroup + 1
1084 this.currentSlot = groups.slotAnchor(currentGroup)
1085 this.currentSlotEnd =
1086 if (currentGroup >= groupsSize - 1) slotsSize
1087 else groups.dataAnchor(currentGroup + 1)
1088 }
1089 }
1090
1091 /** Start a group and validate it is a node group */
startNodenull1092 fun startNode() {
1093 if (emptyCount <= 0) {
1094 requirePrecondition(groups.isNode(currentGroup)) { "Expected a node group" }
1095 startGroup()
1096 }
1097 }
1098
1099 /** Skip a group. Must be called at the start of a group. */
skipGroupnull1100 fun skipGroup(): Int {
1101 runtimeCheck(emptyCount == 0) { "Cannot skip while in an empty region" }
1102 val count = if (groups.isNode(currentGroup)) 1 else groups.nodeCount(currentGroup)
1103 currentGroup += groups.groupSize(currentGroup)
1104 return count
1105 }
1106
1107 /** Skip to the end of the current group. */
skipToGroupEndnull1108 fun skipToGroupEnd() {
1109 runtimeCheck(emptyCount == 0) { "Cannot skip the enclosing group while in an empty region" }
1110 currentGroup = currentEnd
1111 currentSlot = 0
1112 currentSlotEnd = 0
1113 }
1114
1115 /** Reposition the read to the group at [index]. */
repositionnull1116 fun reposition(index: Int) {
1117 runtimeCheck(emptyCount == 0) { "Cannot reposition while in an empty region" }
1118 currentGroup = index
1119 val parent = if (index < groupsSize) groups.parentAnchor(index) else -1
1120 if (parent != this.parent) {
1121 this.parent = parent
1122 if (parent < 0) this.currentEnd = groupsSize
1123 else this.currentEnd = parent + groups.groupSize(parent)
1124 this.currentSlot = 0
1125 this.currentSlotEnd = 0
1126 }
1127 }
1128
1129 /** Restore the parent to a parent of the current group. */
restoreParentnull1130 fun restoreParent(index: Int) {
1131 val newCurrentEnd = index + groups.groupSize(index)
1132 val current = currentGroup
1133 @Suppress("ConvertTwoComparisonsToRangeCheck")
1134 runtimeCheck(current >= index && current <= newCurrentEnd) {
1135 "Index $index is not a parent of $current"
1136 }
1137 this.parent = index
1138 this.currentEnd = newCurrentEnd
1139 this.currentSlot = 0
1140 this.currentSlotEnd = 0
1141 }
1142
1143 /** End the current group. Must be called after the corresponding [startGroup]. */
endGroupnull1144 fun endGroup() {
1145 if (emptyCount == 0) {
1146 runtimeCheck(currentGroup == currentEnd) {
1147 "endGroup() not called at the end of a group"
1148 }
1149 val parent = groups.parentAnchor(parent)
1150 this.parent = parent
1151 currentEnd = if (parent < 0) groupsSize else parent + groups.groupSize(parent)
1152 val currentSlotStack = currentSlotStack
1153 val newCurrentSlot = currentSlotStack.pop()
1154 if (newCurrentSlot < 0) {
1155 currentSlot = 0
1156 currentSlotEnd = 0
1157 } else {
1158 currentSlot = newCurrentSlot
1159 currentSlotEnd =
1160 if (parent >= groupsSize - 1) slotsSize else groups.dataAnchor(parent + 1)
1161 }
1162 }
1163 }
1164
1165 /**
1166 * Extract the keys from this point to the end of the group. The current is left unaffected.
1167 * Must be called inside a group.
1168 */
extractKeysnull1169 fun extractKeys(): MutableList<KeyInfo> {
1170 val result = mutableListOf<KeyInfo>()
1171 if (emptyCount > 0) return result
1172 var index = 0
1173 var childIndex = currentGroup
1174 while (childIndex < currentEnd) {
1175 result.add(
1176 KeyInfo(
1177 groups.key(childIndex),
1178 groups.objectKey(childIndex),
1179 childIndex,
1180 if (groups.isNode(childIndex)) 1 else groups.nodeCount(childIndex),
1181 index++
1182 )
1183 )
1184 childIndex += groups.groupSize(childIndex)
1185 }
1186 return result
1187 }
1188
toStringnull1189 override fun toString(): String =
1190 "SlotReader(current=$currentGroup, key=$groupKey, parent=$parent, end=$currentEnd)"
1191
1192 /** Create an anchor to the current reader location or [index]. */
1193 fun anchor(index: Int = currentGroup) =
1194 table.anchors.getOrAdd(index, groupsSize) { Anchor(index) }
1195
nodenull1196 private fun IntArray.node(index: Int) =
1197 if (isNode(index)) {
1198 slots[nodeIndex(index)]
1199 } else Composer.Empty
1200
IntArraynull1201 private fun IntArray.aux(index: Int) =
1202 if (hasAux(index)) {
1203 slots[auxIndex(index)]
1204 } else Composer.Empty
1205
IntArraynull1206 private fun IntArray.objectKey(index: Int) =
1207 if (hasObjectKey(index)) {
1208 slots[objectKeyIndex(index)]
1209 } else null
1210 }
1211
1212 /** Information about groups and their keys. */
1213 internal class KeyInfo
1214 internal constructor(
1215 /** The group key. */
1216 val key: Int,
1217
1218 /** The object key for the group */
1219 val objectKey: Any?,
1220
1221 /** The location of the group. */
1222 val location: Int,
1223
1224 /** The number of nodes in the group. If the group is a node this is always 1. */
1225 val nodes: Int,
1226
1227 /** The index of the key info in the list returned by extractKeys */
1228 val index: Int
1229 )
1230
1231 /** The writer for a slot table. See [SlotTable] for details. */
1232 internal class SlotWriter(
1233 /** The [SlotTable] for whom this is writer. */
1234 internal val table: SlotTable
1235 ) {
1236 /**
1237 * The gap buffer for groups. This might change as groups are inserted and the array needs to be
1238 * expanded to account groups. The current valid groups occupy 0 until [groupGapStart] followed
1239 * [groupGapStart] + [groupGapLen] until groups.size where [groupGapStart] until
1240 * [groupGapStart] + [groupGapLen] is the gap.
1241 */
1242 private var groups: IntArray = table.groups
1243
1244 /**
1245 * The gap buffer for the slots. This might change as slots are inserted an and the array needs
1246 * to be expanded to account for the new slots. The current valid slots occupy 0 until
1247 * [slotsGapStart] and [slotsGapStart] + [slotsGapLen] until slots.size where [slotsGapStart]
1248 * until [slotsGapStart] + [slotsGapLen] is the gap.
1249 */
1250 private var slots: Array<Any?> = table.slots
1251
1252 /** A copy of the [SlotTable.anchors] to avoid having to index through [table]. */
1253 private var anchors: ArrayList<Anchor> = table.anchors
1254
1255 /** A copy of [SlotTable.sourceInformationMap] to avoid having to index through [table] */
1256 private var sourceInformationMap = table.sourceInformationMap
1257
1258 /** A copy of [SlotTable.calledByMap] to avoid having to index through [table] */
1259 private var calledByMap = table.calledByMap
1260
1261 /** Group index of the start of the gap in the groups array. */
1262 private var groupGapStart: Int = table.groupsSize
1263
1264 /** The number of groups in the gap in the groups array. */
1265 private var groupGapLen: Int = groups.size / Group_Fields_Size - table.groupsSize
1266
1267 /** The location of the [slots] array that contains the data for the [parent] group. */
1268 private var currentSlot = 0
1269
1270 /** The location of the index in [slots] after the slots for the [parent] group. */
1271 private var currentSlotEnd = 0
1272
1273 /** The is the index of gap in the [slots] array. */
1274 private var slotsGapStart: Int = table.slotsSize
1275
1276 /** The number of slots in the gop in the [slots] array. */
1277 private var slotsGapLen: Int = slots.size - table.slotsSize
1278
1279 /** The owner of the gap is the first group that has a end relative index. */
1280 private var slotsGapOwner = table.groupsSize
1281
1282 /** The number of times [beginInsert] has been called. */
1283 private var insertCount = 0
1284
1285 /**
1286 * The number of nodes in the current group. Used to track when nodes are being added and
1287 * removed in the [parent] group. Once [endGroup] is called, if the nodes count has changed, the
1288 * containing groups are updated until a node group is encountered.
1289 */
1290 private var nodeCount = 0
1291
1292 /**
1293 * A stack of the groups that have been explicitly started. A group can be implicitly started by
1294 * using [seek] to seek to indirect child and calling [startGroup] on that child. The groups
1295 * implicitly started groups will be updated when the [endGroup] is called for the indirect
1296 * child group.
1297 */
1298 private val startStack = IntStack()
1299
1300 /**
1301 * A stack of the [currentGroupEnd] corresponding with the group is [startStack]. As groups are
1302 * ended by calling [endGroup], the size of the group might have changed. This stack is a stack
1303 * of enc group anchors where will reflect the group size change when it is restored by calling
1304 * [restoreCurrentGroupEnd].
1305 */
1306 private val endStack = IntStack()
1307
1308 /** This a count of the [nodeCount] of the explicitly started groups. */
1309 private val nodeCountStack = IntStack()
1310
1311 /**
1312 * Deferred slot writes for open groups to avoid thrashing the slot table when slots are added
1313 * to parent group which already has children.
1314 */
1315 private var deferredSlotWrites: MutableIntObjectMap<MutableObjectList<Any?>>? = null
1316
1317 /** The current group that will be started by [startGroup] or skipped by [skipGroup] */
1318 var currentGroup = 0
1319 private set
1320
1321 /** The index end of the current group. */
1322 var currentGroupEnd = table.groupsSize
1323 private set
1324
1325 /** True if at the end of a group and an [endGroup] is expected. */
1326 val isGroupEnd
1327 get() = currentGroup == currentGroupEnd
1328
1329 val slotsSize
1330 get() = slots.size - slotsGapLen
1331
1332 /**
1333 * Return true if the current slot starts a node. A node is a kind of group so this will return
1334 * true for isGroup as well.
1335 */
1336 val isNode
1337 get() = currentGroup < currentGroupEnd && groups.isNode(groupIndexToAddress(currentGroup))
1338
1339 /** Returns true if the writer is collecting source information */
1340 val collectingSourceInformation
1341 get() = sourceInformationMap != null
1342
1343 /** Returns true if the writer is collecting called by information */
1344 val collectingCalledInformation
1345 get() = calledByMap != null
1346
1347 /** Return true if the group at [index] is a node. */
isNodenull1348 fun isNode(index: Int) = groups.isNode(groupIndexToAddress(index))
1349
1350 /** return the number of nodes contained in the group at [index] */
1351 fun nodeCount(index: Int) = groups.nodeCount(groupIndexToAddress(index))
1352
1353 /** Return the key for the group at [index]. */
1354 fun groupKey(index: Int): Int = groups.key(groupIndexToAddress(index))
1355
1356 /** Return the object key for the group at [index], if it has one, or null if not. */
1357 fun groupObjectKey(index: Int): Any? {
1358 val address = groupIndexToAddress(index)
1359 return if (groups.hasObjectKey(address)) slots[groups.objectKeyIndex(address)] else null
1360 }
1361
1362 /** Return the size of the group at [index]. */
groupSizenull1363 fun groupSize(index: Int): Int = groups.groupSize(groupIndexToAddress(index))
1364
1365 /** Return the aux of the group at [index]. */
1366 fun groupAux(index: Int): Any? {
1367 val address = groupIndexToAddress(index)
1368 return if (groups.hasAux(address)) slots[groups.auxIndex(address)] else Composer.Empty
1369 }
1370
1371 @Suppress("ConvertTwoComparisonsToRangeCheck")
indexInParentnull1372 fun indexInParent(index: Int): Boolean =
1373 index > parent && index < currentGroupEnd || (parent == 0 && index == 0)
1374
1375 fun indexInCurrentGroup(index: Int): Boolean = indexInGroup(index, currentGroup)
1376
1377 @Suppress("ConvertTwoComparisonsToRangeCheck")
1378 fun indexInGroup(index: Int, group: Int): Boolean {
1379 // If the group is open then the group size in the groups array has not been updated yet
1380 // so calculate the end from the stored anchor value in the end stack.
1381 val end =
1382 when {
1383 group == parent -> currentGroupEnd
1384 group > startStack.peekOr(0) -> group + groupSize(group)
1385 else -> {
1386 val openIndex = startStack.indexOf(group)
1387 when {
1388 openIndex < 0 -> group + groupSize(group)
1389 else -> (capacity - groupGapLen) - endStack.peek(openIndex)
1390 }
1391 }
1392 }
1393 return index > group && index < end
1394 }
1395
1396 /** Return the node at [index] if [index] is a node group or null. */
nodenull1397 fun node(index: Int): Any? {
1398 val address = groupIndexToAddress(index)
1399 return if (groups.isNode(address)) slots[dataIndexToDataAddress(groups.nodeIndex(address))]
1400 else null
1401 }
1402
1403 /** Return the node at [anchor] if it is a node group or null. */
nodenull1404 fun node(anchor: Anchor) = node(anchor.toIndexFor(this))
1405
1406 /** Return the index of the nearest group that contains [currentGroup]. */
1407 var parent: Int = -1
1408 private set
1409
1410 /** Return the index of the parent for the group at [index]. */
1411 fun parent(index: Int) = groups.parent(index)
1412
1413 /**
1414 * Return the index of the parent for the group referenced by [anchor]. If the anchor is not
1415 * valid it returns -1.
1416 */
1417 fun parent(anchor: Anchor) = if (anchor.valid) groups.parent(anchorIndex(anchor)) else -1
1418
1419 /** True if the writer has been closed */
1420 var closed = false
1421 private set
1422
1423 /** Close the writer */
1424 fun close(normalClose: Boolean) {
1425 closed = true
1426 // Ensure, for readers, there is no gap
1427 if (normalClose && startStack.isEmpty()) {
1428 // Only reset the writer if it closes normally.
1429 moveGroupGapTo(size)
1430 moveSlotGapTo(slots.size - slotsGapLen, groupGapStart)
1431 clearSlotGap()
1432 recalculateMarks()
1433 }
1434 table.close(
1435 writer = this,
1436 groups = groups,
1437 groupsSize = groupGapStart,
1438 slots = slots,
1439 slotsSize = slotsGapStart,
1440 anchors = anchors,
1441 sourceInformationMap = sourceInformationMap,
1442 calledByMap = calledByMap
1443 )
1444 }
1445
1446 /**
1447 * Reset the writer to the beginning of the slot table and in the state as if it had just been
1448 * opened. This differs form closing a writer and opening a new one in that the instance doesn't
1449 * change and the gap in the slots are not reset to the end of the buffer.
1450 */
resetnull1451 fun reset() {
1452 runtimeCheck(insertCount == 0) { "Cannot reset when inserting" }
1453 recalculateMarks()
1454 currentGroup = 0
1455 currentGroupEnd = capacity - groupGapLen
1456 currentSlot = 0
1457 currentSlotEnd = 0
1458 nodeCount = 0
1459 }
1460
1461 /**
1462 * Set the value of the next slot. Returns the previous value of the slot or [Composer.Empty] is
1463 * being inserted.
1464 */
updatenull1465 fun update(value: Any?): Any? {
1466 if (insertCount > 0 && currentSlot != slotsGapStart) {
1467 // Defer write as doing it now would thrash the slot table.
1468 val deferred =
1469 (deferredSlotWrites ?: MutableIntObjectMap())
1470 .also { deferredSlotWrites = it }
1471 .getOrPut(parent) { MutableObjectList() }
1472 deferred.add(value)
1473 return Composer.Empty
1474 }
1475 return rawUpdate(value)
1476 }
1477
rawUpdatenull1478 private fun rawUpdate(value: Any?): Any? {
1479 val result = skip()
1480 set(value)
1481 return result
1482 }
1483
1484 /** Append a slot to the [parent] group. */
appendSlotnull1485 fun appendSlot(anchor: Anchor, value: Any?) {
1486 runtimeCheck(insertCount == 0) { "Can only append a slot if not current inserting" }
1487 var previousCurrentSlot = currentSlot
1488 var previousCurrentSlotEnd = currentSlotEnd
1489 val anchorIndex = anchorIndex(anchor)
1490 val slotIndex = groups.dataIndex(groupIndexToAddress(anchorIndex + 1))
1491 currentSlot = slotIndex
1492 currentSlotEnd = slotIndex
1493 insertSlots(1, anchorIndex)
1494 if (previousCurrentSlot >= slotIndex) {
1495 previousCurrentSlot++
1496 previousCurrentSlotEnd++
1497 }
1498 slots[slotIndex] = value
1499 currentSlot = previousCurrentSlot
1500 currentSlotEnd = previousCurrentSlotEnd
1501 }
1502
trimTailSlotsnull1503 fun trimTailSlots(count: Int) {
1504 runtimeCheck(count > 0)
1505 val parent = parent
1506 val groupSlotStart = groups.slotIndex(groupIndexToAddress(parent))
1507 val groupSlotEnd = groups.dataIndex(groupIndexToAddress(parent + 1))
1508 val removeStart = groupSlotEnd - count
1509 runtimeCheck(removeStart >= groupSlotStart)
1510 removeSlots(removeStart, count, parent)
1511 val currentSlot = currentSlot
1512 if (currentSlot >= groupSlotStart) {
1513 this.currentSlot = currentSlot - count
1514 }
1515 }
1516
1517 /** Updates the data for the current data group. */
updateAuxnull1518 fun updateAux(value: Any?) {
1519 val address = groupIndexToAddress(currentGroup)
1520 runtimeCheck(groups.hasAux(address)) {
1521 "Updating the data of a group that was not created with a data slot"
1522 }
1523 slots[dataIndexToDataAddress(groups.auxIndex(address))] = value
1524 }
1525
1526 /**
1527 * Insert aux data into the parent group.
1528 *
1529 * This must be done only after at most one value has been inserted into the slot table for the
1530 * group.
1531 */
insertAuxnull1532 fun insertAux(value: Any?) {
1533 runtimeCheck(insertCount >= 0) { "Cannot insert auxiliary data when not inserting" }
1534 val parent = parent
1535 val parentGroupAddress = groupIndexToAddress(parent)
1536 runtimeCheck(!groups.hasAux(parentGroupAddress)) { "Group already has auxiliary data" }
1537 insertSlots(1, parent)
1538 val auxIndex = groups.auxIndex(parentGroupAddress)
1539 val auxAddress = dataIndexToDataAddress(auxIndex)
1540 if (currentSlot > auxIndex) {
1541 // One or more values were inserted into the slot table before the aux value, we need
1542 // to move them. Currently we only will run into one or two slots (the recompose
1543 // scope inserted by a restart group and the lambda value in a composableLambda
1544 // instance) so this is the only case currently supported.
1545 val slotsToMove = currentSlot - auxIndex
1546 checkPrecondition(slotsToMove < 3) { "Moving more than two slot not supported" }
1547 if (slotsToMove > 1) {
1548 slots[auxAddress + 2] = slots[auxAddress + 1]
1549 }
1550 slots[auxAddress + 1] = slots[auxAddress]
1551 }
1552 groups.addAux(parentGroupAddress)
1553 slots[auxAddress] = value
1554 currentSlot++
1555 }
1556
updateToTableMapsnull1557 fun updateToTableMaps() {
1558 this.sourceInformationMap = table.sourceInformationMap
1559 this.calledByMap = table.calledByMap
1560 }
1561
recordGroupSourceInformationnull1562 fun recordGroupSourceInformation(sourceInformation: String) {
1563 if (insertCount > 0) {
1564 groupSourceInformationFor(parent, sourceInformation)
1565 }
1566 }
1567
recordGrouplessCallSourceInformationStartnull1568 fun recordGrouplessCallSourceInformationStart(key: Int, value: String) {
1569 if (insertCount > 0) {
1570 calledByMap?.add(key, groupKey(parent))
1571 groupSourceInformationFor(parent, null)
1572 ?.startGrouplessCall(key, value, currentGroupSlotIndex)
1573 }
1574 }
1575
recordGrouplessCallSourceInformationEndnull1576 fun recordGrouplessCallSourceInformationEnd() {
1577 if (insertCount > 0) {
1578 groupSourceInformationFor(parent, null)?.endGrouplessCall(currentGroupSlotIndex)
1579 }
1580 }
1581
groupSourceInformationFornull1582 private fun groupSourceInformationFor(
1583 parent: Int,
1584 sourceInformation: String?
1585 ): GroupSourceInformation? =
1586 sourceInformationMap?.getOrPut(anchor(parent)) {
1587 val result = GroupSourceInformation(0, sourceInformation, 0)
1588
1589 // If we called from a groupless call then the groups added before this call
1590 // are not reflected in this group information so they need to be added now
1591 // if they exist.
1592 if (sourceInformation == null) {
1593 var child = parent + 1
1594 val end = currentGroup
1595 while (child < end) {
1596 result.reportGroup(this, child)
1597 child += groups.groupSize(child)
1598 }
1599 }
1600
1601 result
1602 }
1603
1604 /** Updates the node for the current node group to [value]. */
updateNodenull1605 fun updateNode(value: Any?) = updateNodeOfGroup(currentGroup, value)
1606
1607 /** Update the node of a the group at [anchor] to [value]. */
1608 fun updateNode(anchor: Anchor, value: Any?) = updateNodeOfGroup(anchor.toIndexFor(this), value)
1609
1610 /** Updates the node of the parent group. */
1611 fun updateParentNode(value: Any?) = updateNodeOfGroup(parent, value)
1612
1613 /** Set the value at the groups current data slot */
1614 fun set(value: Any?) {
1615 runtimeCheck(currentSlot <= currentSlotEnd) { "Writing to an invalid slot" }
1616 slots[dataIndexToDataAddress(currentSlot - 1)] = value
1617 }
1618
1619 /** Set the group's slot at [index] to [value]. Returns the previous value. */
setnull1620 inline fun set(index: Int, value: Any?): Any? = set(currentGroup, index, value)
1621
1622 /** Convert a slot group index into a global slot index. */
1623 fun slotIndexOfGroupSlotIndex(group: Int, index: Int): Int {
1624 val address = groupIndexToAddress(group)
1625 val slotsStart = groups.slotIndex(address)
1626 val slotsEnd = groups.dataIndex(groupIndexToAddress(group + 1))
1627 val slotsIndex = slotsStart + index
1628 @Suppress("ConvertTwoComparisonsToRangeCheck")
1629 runtimeCheck(slotsIndex >= slotsStart && slotsIndex < slotsEnd) {
1630 "Write to an invalid slot index $index for group $group"
1631 }
1632 return slotsIndex
1633 }
1634
1635 /** Set the [group] slot at [index] to [value]. Returns the previous value. */
setnull1636 fun set(group: Int, index: Int, value: Any?): Any? {
1637 val slotsIndex = slotIndexOfGroupSlotIndex(group, index)
1638 val slotAddress = dataIndexToDataAddress(slotsIndex)
1639 val result = slots[slotAddress]
1640 slots[slotAddress] = value
1641 return result
1642 }
1643
1644 /** Set the slot by index to Composer.Empty, returning previous value */
clearnull1645 fun clear(slotIndex: Int): Any? {
1646 val address = dataIndexToDataAddress(slotIndex)
1647 val result = slots[address]
1648 slots[address] = Composer.Empty
1649 return result
1650 }
1651
1652 /**
1653 * Skip the current slot without updating. If the slot table is inserting then and
1654 * [Composer.Empty] slot is added and [skip] return [Composer.Empty].
1655 */
skipnull1656 fun skip(): Any? {
1657 if (insertCount > 0) {
1658 insertSlots(1, parent)
1659 }
1660 return slots[dataIndexToDataAddress(currentSlot++)]
1661 }
1662
1663 /**
1664 * Read the [index] slot at the group at [anchor]. Returns [Composer.Empty] if the slot is empty
1665 * (e.g. out of range).
1666 */
slotnull1667 fun slot(anchor: Anchor, index: Int) = slot(anchorIndex(anchor), index)
1668
1669 /**
1670 * Read the [index] slot at the group at index [groupIndex]. Returns [Composer.Empty] if the
1671 * slot is empty (e.g. out of range).
1672 */
1673 fun slot(groupIndex: Int, index: Int): Any? {
1674 val address = groupIndexToAddress(groupIndex)
1675 val slotsStart = groups.slotIndex(address)
1676 val slotsEnd = groups.dataIndex(groupIndexToAddress(groupIndex + 1))
1677 val slotsIndex = slotsStart + index
1678 if (slotsIndex !in slotsStart until slotsEnd) {
1679 return Composer.Empty
1680 }
1681 val slotAddress = dataIndexToDataAddress(slotsIndex)
1682 return slots[slotAddress]
1683 }
1684
1685 /** Call [block] for up to [count] slots values at the end of the group's slots. */
forEachTailSlotnull1686 inline fun forEachTailSlot(groupIndex: Int, count: Int, block: (Int, Any?) -> Unit) {
1687 val slotsStart = slotsStartIndex(groupIndex)
1688 val slotsEnd = slotsEndIndex(groupIndex)
1689 for (slotIndex in max(slotsStart, slotsEnd - count) until slotsEnd) {
1690 block(slotIndex, slots[dataIndexToDataAddress(slotIndex)])
1691 }
1692 }
1693
1694 /**
1695 * Return the start index of the slot for [groupIndex]. Used in [forEachTailSlot] to enumerate
1696 * slots.
1697 */
slotsStartIndexnull1698 internal fun slotsStartIndex(groupIndex: Int): Int =
1699 groups.slotIndex(groupIndexToAddress(groupIndex))
1700
1701 /**
1702 * Return the end index of the slot for [groupIndex]. Used in [forEachTailSlot] to enumerate
1703 * slots.
1704 */
1705 internal fun slotsEndIndex(groupIndex: Int): Int =
1706 groups.dataIndex(groupIndexToAddress(groupIndex + 1))
1707
1708 internal fun slotsEndAllIndex(groupIndex: Int): Int =
1709 groups.dataIndex(groupIndexToAddress(groupIndex + groupSize(groupIndex)))
1710
1711 private val currentGroupSlotIndex: Int
1712 get() = groupSlotIndex(parent)
1713
1714 fun groupSlotIndex(group: Int) =
1715 currentSlot - slotsStartIndex(group) + (deferredSlotWrites?.get(group)?.size ?: 0)
1716
1717 /**
1718 * Advance [currentGroup] by [amount]. The [currentGroup] group cannot be advanced outside the
1719 * currently started [parent].
1720 */
1721 fun advanceBy(amount: Int) {
1722 runtimeCheck(amount >= 0) { "Cannot seek backwards" }
1723 checkPrecondition(insertCount <= 0) { "Cannot call seek() while inserting" }
1724 if (amount == 0) return
1725 val index = currentGroup + amount
1726 @Suppress("ConvertTwoComparisonsToRangeCheck")
1727 runtimeCheck(index >= parent && index <= currentGroupEnd) {
1728 "Cannot seek outside the current group ($parent-$currentGroupEnd)"
1729 }
1730 this.currentGroup = index
1731 val newSlot = groups.dataIndex(groupIndexToAddress(index))
1732 this.currentSlot = newSlot
1733 this.currentSlotEnd = newSlot
1734 }
1735
1736 /**
1737 * Seek the current location to [anchor]. The [anchor] must be an anchor to a possibly indirect
1738 * child of [parent].
1739 */
seeknull1740 fun seek(anchor: Anchor) = advanceBy(anchor.toIndexFor(this) - currentGroup)
1741
1742 /** Skip to the end of the current group. */
1743 fun skipToGroupEnd() {
1744 val newGroup = currentGroupEnd
1745 currentGroup = newGroup
1746 currentSlot = groups.dataIndex(groupIndexToAddress(newGroup))
1747 }
1748
1749 /**
1750 * Begin inserting at the current location. beginInsert() can be nested and must be called with
1751 * a balanced number of endInsert()
1752 */
beginInsertnull1753 fun beginInsert() {
1754 if (insertCount++ == 0) {
1755 saveCurrentGroupEnd()
1756 }
1757 }
1758
1759 /** Ends inserting. */
endInsertnull1760 fun endInsert() {
1761 checkPrecondition(insertCount > 0) { "Unbalanced begin/end insert" }
1762 if (--insertCount == 0) {
1763 runtimeCheck(nodeCountStack.size == startStack.size) {
1764 "startGroup/endGroup mismatch while inserting"
1765 }
1766 restoreCurrentGroupEnd()
1767 }
1768 }
1769
1770 /** Enter the group at current without changing it. Requires not currently inserting. */
startGroupnull1771 fun startGroup() {
1772 runtimeCheck(insertCount == 0) { "Key must be supplied when inserting" }
1773 startGroup(key = 0, objectKey = Composer.Empty, isNode = false, aux = Composer.Empty)
1774 }
1775
1776 /** Start a group with a integer key */
startGroupnull1777 fun startGroup(key: Int) = startGroup(key, Composer.Empty, isNode = false, aux = Composer.Empty)
1778
1779 /** Start a group with a data key */
1780 fun startGroup(key: Int, dataKey: Any?) =
1781 startGroup(key, dataKey, isNode = false, aux = Composer.Empty)
1782
1783 /** Start a node. */
1784 fun startNode(key: Int, objectKey: Any?) =
1785 startGroup(key, objectKey, isNode = true, aux = Composer.Empty)
1786
1787 /** Start a node */
1788 fun startNode(key: Int, objectKey: Any?, node: Any?) =
1789 startGroup(key, objectKey, isNode = true, aux = node)
1790
1791 /** Start a data group. */
1792 fun startData(key: Int, objectKey: Any?, aux: Any?) =
1793 startGroup(key, objectKey, isNode = false, aux = aux)
1794
1795 /** Start a data group. */
1796 fun startData(key: Int, aux: Any?) = startGroup(key, Composer.Empty, isNode = false, aux = aux)
1797
1798 private fun startGroup(key: Int, objectKey: Any?, isNode: Boolean, aux: Any?) {
1799 val previousParent = parent
1800 val inserting = insertCount > 0
1801 nodeCountStack.push(nodeCount)
1802
1803 currentGroupEnd =
1804 if (inserting) {
1805 val current = currentGroup
1806 val newCurrentSlot = groups.dataIndex(groupIndexToAddress(current))
1807 insertGroups(1)
1808 currentSlot = newCurrentSlot
1809 currentSlotEnd = newCurrentSlot
1810 val currentAddress = groupIndexToAddress(current)
1811 val hasObjectKey = objectKey !== Composer.Empty
1812 val hasAux = !isNode && aux !== Composer.Empty
1813 val dataAnchor =
1814 dataIndexToDataAnchor(
1815 index = newCurrentSlot,
1816 gapLen = slotsGapLen,
1817 gapStart = slotsGapStart,
1818 capacity = slots.size
1819 )
1820 .let { anchor ->
1821 if (anchor >= 0 && slotsGapOwner < current) {
1822 // This is a special case where the a parent added slots to its
1823 // group
1824 // setting the slotGapOwner back, but no intervening groups contain
1825 // slots
1826 // so the slotCurrent is at the beginning fo the gap but is not
1827 // owned by this
1828 // group. By definition the beginning of the gap is the index but
1829 // there are
1830 // actually two valid anchor values for this location a positive one
1831 // and a
1832 // negative (distance from theend of the slot array). In this case
1833 // moveSlotGapTo() the negative value for all groups after the
1834 // slotGapOwner
1835 // so when the gap moves it can adjust the anchors correctly needs
1836 // the negative
1837 // anchor.
1838 val slotsSize = slots.size - slotsGapLen
1839 -(slotsSize - anchor + 1)
1840 } else anchor
1841 }
1842 groups.initGroup(
1843 address = currentAddress,
1844 key = key,
1845 isNode = isNode,
1846 hasDataKey = hasObjectKey,
1847 hasData = hasAux,
1848 parentAnchor = parent,
1849 dataAnchor = dataAnchor
1850 )
1851
1852 val dataSlotsNeeded =
1853 (if (isNode) 1 else 0) + (if (hasObjectKey) 1 else 0) + (if (hasAux) 1 else 0)
1854 if (dataSlotsNeeded > 0) {
1855 insertSlots(dataSlotsNeeded, current)
1856 val slots = slots
1857 var currentSlot = currentSlot
1858 if (isNode) slots[currentSlot++] = aux
1859 if (hasObjectKey) slots[currentSlot++] = objectKey
1860 if (hasAux) slots[currentSlot++] = aux
1861 this.currentSlot = currentSlot
1862 }
1863 nodeCount = 0
1864 val newCurrent = current + 1
1865 this.parent = current
1866 this.currentGroup = newCurrent
1867 if (previousParent >= 0) {
1868 sourceInformationOf(previousParent)?.reportGroup(this, current)
1869 }
1870 newCurrent
1871 } else {
1872 startStack.push(previousParent)
1873 saveCurrentGroupEnd()
1874 val currentGroup = currentGroup
1875 val currentGroupAddress = groupIndexToAddress(currentGroup)
1876 if (aux != Composer.Empty) {
1877 if (isNode) updateNode(aux) else updateAux(aux)
1878 }
1879 currentSlot = groups.slotIndex(currentGroupAddress)
1880 currentSlotEnd = groups.dataIndex(groupIndexToAddress(this.currentGroup + 1))
1881 nodeCount = groups.nodeCount(currentGroupAddress)
1882
1883 this.parent = currentGroup
1884 this.currentGroup = currentGroup + 1
1885 currentGroup + groups.groupSize(currentGroupAddress)
1886 }
1887 }
1888
1889 /** End the current group. Must be called after the corresponding startGroup(). */
endGroupnull1890 fun endGroup(): Int {
1891 val inserting = insertCount > 0
1892 val currentGroup = currentGroup
1893 val currentGroupEnd = currentGroupEnd
1894
1895 val groupIndex = parent
1896 val groupAddress = groupIndexToAddress(groupIndex)
1897 val newNodes = nodeCount
1898 val newGroupSize = currentGroup - groupIndex
1899 val isNode = groups.isNode(groupAddress)
1900 if (inserting) {
1901 // Check for deferred slot writes
1902 val deferredSlotWrites = deferredSlotWrites
1903 deferredSlotWrites?.get(groupIndex)?.let {
1904 it.forEach { value -> rawUpdate(value) }
1905 deferredSlotWrites.remove(groupIndex)
1906 }
1907
1908 // Close the group
1909 groups.updateGroupSize(groupAddress, newGroupSize)
1910 groups.updateNodeCount(groupAddress, newNodes)
1911 nodeCount = nodeCountStack.pop() + if (isNode) 1 else newNodes
1912 parent = groups.parent(groupIndex)
1913 val nextAddress = if (parent < 0) size else groupIndexToAddress(parent + 1)
1914 val newCurrentSlot = if (nextAddress < 0) 0 else groups.dataIndex(nextAddress)
1915 currentSlot = newCurrentSlot
1916 currentSlotEnd = newCurrentSlot
1917 } else {
1918 runtimeCheck(currentGroup == currentGroupEnd) { "Expected to be at the end of a group" }
1919 // Update group length
1920 val oldGroupSize = groups.groupSize(groupAddress)
1921 val oldNodes = groups.nodeCount(groupAddress)
1922 groups.updateGroupSize(groupAddress, newGroupSize)
1923 groups.updateNodeCount(groupAddress, newNodes)
1924 val newParent = startStack.pop()
1925 restoreCurrentGroupEnd()
1926 this.parent = newParent
1927 val groupParent = groups.parent(groupIndex)
1928 nodeCount = nodeCountStack.pop()
1929 if (groupParent == newParent) {
1930 // The parent group was started we just need to update the node count.
1931 nodeCount += if (isNode) 0 else newNodes - oldNodes
1932 } else {
1933 // If we are closing a group for which startGroup was called after calling
1934 // seek(). startGroup allows the indirect children to be started. If the group
1935 // has changed size or the number of nodes it contains the groups between the
1936 // group being closed and the group that is currently open need to be adjusted.
1937 // This allows the caller to avoid the overhead of needing to start and end the
1938 // groups explicitly.
1939 val groupSizeDelta = newGroupSize - oldGroupSize
1940 var nodesDelta = if (isNode) 0 else newNodes - oldNodes
1941 if (groupSizeDelta != 0 || nodesDelta != 0) {
1942 var current = groupParent
1943 while (
1944 current != 0 &&
1945 current != newParent &&
1946 (nodesDelta != 0 || groupSizeDelta != 0)
1947 ) {
1948 val currentAddress = groupIndexToAddress(current)
1949 if (groupSizeDelta != 0) {
1950 val newSize = groups.groupSize(currentAddress) + groupSizeDelta
1951 groups.updateGroupSize(currentAddress, newSize)
1952 }
1953 if (nodesDelta != 0) {
1954 groups.updateNodeCount(
1955 currentAddress,
1956 groups.nodeCount(currentAddress) + nodesDelta
1957 )
1958 }
1959 if (groups.isNode(currentAddress)) nodesDelta = 0
1960 current = groups.parent(current)
1961 }
1962 }
1963 nodeCount += nodesDelta
1964 }
1965 }
1966 return newNodes
1967 }
1968
1969 /**
1970 * If the start of a group was skipped using [skip], calling [ensureStarted] puts the writer
1971 * into the same state as if [startGroup] or [startNode] was called on the group starting at
1972 * [index]. If, after starting, the group, [currentGroup] is not at the end of the group or
1973 * [currentGroup] is not at the start of a group for which [index] is not location the parent
1974 * group, an exception is thrown.
1975 *
1976 * Calling [ensureStarted] implies that an [endGroup] should be called once the end of the group
1977 * is reached.
1978 */
ensureStartednull1979 fun ensureStarted(index: Int) {
1980 runtimeCheck(insertCount <= 0) { "Cannot call ensureStarted() while inserting" }
1981 val parent = parent
1982 if (parent != index) {
1983 // The new parent a child of the current group.
1984 @Suppress("ConvertTwoComparisonsToRangeCheck")
1985 runtimeCheck(index >= parent && index < currentGroupEnd) {
1986 "Started group at $index must be a subgroup of the group at $parent"
1987 }
1988
1989 val oldCurrent = currentGroup
1990 val oldCurrentSlot = currentSlot
1991 val oldCurrentSlotEnd = currentSlotEnd
1992 currentGroup = index
1993 startGroup()
1994 currentGroup = oldCurrent
1995 currentSlot = oldCurrentSlot
1996 currentSlotEnd = oldCurrentSlotEnd
1997 }
1998 }
1999
ensureStartednull2000 fun ensureStarted(anchor: Anchor) = ensureStarted(anchor.toIndexFor(this))
2001
2002 /** Skip the current group. Returns the number of nodes skipped by skipping the group. */
2003 fun skipGroup(): Int {
2004 val groupAddress = groupIndexToAddress(currentGroup)
2005 val newGroup = currentGroup + groups.groupSize(groupAddress)
2006 this.currentGroup = newGroup
2007 this.currentSlot = groups.dataIndex(groupIndexToAddress(newGroup))
2008 return if (groups.isNode(groupAddress)) 1 else groups.nodeCount(groupAddress)
2009 }
2010
2011 /** Remove the current group. Returns if any anchors were in the group removed. */
removeGroupnull2012 fun removeGroup(): Boolean {
2013 runtimeCheck(insertCount == 0) { "Cannot remove group while inserting" }
2014 val oldGroup = currentGroup
2015 val oldSlot = currentSlot
2016 val dataStart = groups.dataIndex(groupIndexToAddress(oldGroup))
2017 val count = skipGroup()
2018
2019 // Remove the group from its parent information
2020 sourceInformationOf(parent)?.let { sourceInformation ->
2021 tryAnchor(oldGroup)?.let { anchor -> sourceInformation.removeAnchor(anchor) }
2022 }
2023
2024 // Remove any recalculate markers ahead of this delete as they are in the group
2025 // that is being deleted.
2026 pendingRecalculateMarks?.let {
2027 while (it.isNotEmpty() && it.peek() >= oldGroup) {
2028 it.takeMax()
2029 }
2030 }
2031
2032 val anchorsRemoved = removeGroups(oldGroup, currentGroup - oldGroup)
2033 removeSlots(dataStart, currentSlot - dataStart, oldGroup - 1)
2034 currentGroup = oldGroup
2035 currentSlot = oldSlot
2036 nodeCount -= count
2037 return anchorsRemoved
2038 }
2039
2040 /** Returns an iterator for all the slots of group and all the children of the group. */
groupSlotsnull2041 fun groupSlots(): Iterator<Any?> {
2042 val start = groups.dataIndex(groupIndexToAddress(currentGroup))
2043 val end = groups.dataIndex(groupIndexToAddress(currentGroup + groupSize(currentGroup)))
2044 return object : Iterator<Any?> {
2045 var current = start
2046
2047 override fun hasNext(): Boolean = current < end
2048
2049 override fun next(): Any? =
2050 if (hasNext()) slots[dataIndexToDataAddress(current++)] else null
2051 }
2052 }
2053
forAllDatanull2054 inline fun forAllData(group: Int, block: (index: Int, data: Any?) -> Unit) {
2055 val address = groupIndexToAddress(group)
2056 val start = groups.dataIndex(address)
2057 val end = groups.dataIndex(groupIndexToAddress(currentGroup + groupSize(currentGroup)))
2058 for (slot in start until end) {
2059 block(slot, slots[dataIndexToDataAddress(slot)])
2060 }
2061 }
2062
2063 /**
2064 * Move the group at [offset] groups after [currentGroup] to be in front of [currentGroup].
2065 * After this completes, the moved group will be the current group. [offset] must less than the
2066 * number of groups after the [currentGroup] left in the [parent] group.
2067 */
moveGroupnull2068 fun moveGroup(offset: Int) {
2069 runtimeCheck(insertCount == 0) { "Cannot move a group while inserting" }
2070 runtimeCheck(offset >= 0) { "Parameter offset is out of bounds" }
2071 if (offset == 0) return
2072 val current = currentGroup
2073 val parent = parent
2074 val parentEnd = currentGroupEnd
2075
2076 // Find the group to move
2077 var count = offset
2078 var groupToMove = current
2079 while (count > 0) {
2080 groupToMove += groups.groupSize(address = groupIndexToAddress(groupToMove))
2081 runtimeCheck(groupToMove <= parentEnd) { "Parameter offset is out of bounds" }
2082 count--
2083 }
2084
2085 val moveLen = groups.groupSize(address = groupIndexToAddress(groupToMove))
2086 val destinationSlot = groups.dataIndex(groupIndexToAddress(currentGroup))
2087 val dataStart = groups.dataIndex(groupIndexToAddress(groupToMove))
2088 val dataEnd = groups.dataIndex(address = groupIndexToAddress(index = groupToMove + moveLen))
2089 val moveDataLen = dataEnd - dataStart
2090
2091 // The order of operations is important here. Moving a block in the array requires,
2092 //
2093 // 1) inserting space for the block
2094 // 2) copying the block
2095 // 3) deleting the previous block
2096 //
2097 // Inserting into a gap buffer requires moving the gap to the location of the insert and
2098 // then shrinking the gap. Moving the gap in the slot table requires updating all anchors
2099 // in the group table that refer to locations that changed by the gap move. For this to
2100 // work correctly, the groups must be in a valid state. That requires that the slot table
2101 // must be inserted into first so there are no transitory constraint violations in the
2102 // groups (that is, there are no invalid, duplicate or out of order anchors). Conversely,
2103 // removing slots also involves moving the gap requiring the groups to be valid so the
2104 // slots must be removed after the groups that reference the old locations are removed.
2105 // So the order of operations when moving groups is,
2106 //
2107 // 1) insert space for the slots at the destination (must be first)
2108 // 2) insert space for the groups at the destination
2109 // 3) copy the groups to their new location
2110 // 4) copy the slots to their new location
2111 // 5) fix-up the moved group anchors to refer to the new slot locations
2112 // 6) update any anchors in the group being moved
2113 // 7) remove the old groups
2114 // 8) fix parent anchors
2115 // 9) remove the old slots (must be last)
2116
2117 // 1) insert space for the slots at the destination (must be first)
2118 insertSlots(moveDataLen, max(currentGroup - 1, 0))
2119
2120 // 2) insert space for the groups at the destination
2121 insertGroups(moveLen)
2122
2123 // 3) copy the groups to their new location
2124 val groups = groups
2125 val moveLocationAddress = groupIndexToAddress(groupToMove + moveLen)
2126 val moveLocationOffset = moveLocationAddress * Group_Fields_Size
2127 val currentAddress = groupIndexToAddress(current)
2128 groups.copyInto(
2129 destination = groups,
2130 destinationOffset = currentAddress * Group_Fields_Size,
2131 startIndex = moveLocationOffset,
2132 endIndex = moveLocationOffset + moveLen * Group_Fields_Size
2133 )
2134
2135 // 4) copy the slots to their new location
2136 if (moveDataLen > 0) {
2137 val slots = slots
2138 slots.fastCopyInto(
2139 destination = slots,
2140 destinationOffset = destinationSlot,
2141 startIndex = dataIndexToDataAddress(dataStart + moveDataLen),
2142 endIndex = dataIndexToDataAddress(dataEnd + moveDataLen)
2143 )
2144 }
2145
2146 // 5) fix-up the moved group anchors to refer to the new slot locations
2147 val dataMoveDistance = (dataStart + moveDataLen) - destinationSlot
2148 val slotsGapStart = slotsGapStart
2149 val slotsGapLen = slotsGapLen
2150 val slotsCapacity = slots.size
2151 val slotsGapOwner = slotsGapOwner
2152 for (group in current until current + moveLen) {
2153 val groupAddress = groupIndexToAddress(group)
2154 val oldIndex = groups.dataIndex(groupAddress)
2155 val newIndex = oldIndex - dataMoveDistance
2156 val newAnchor =
2157 dataIndexToDataAnchor(
2158 index = newIndex,
2159 gapStart = if (slotsGapOwner < groupAddress) 0 else slotsGapStart,
2160 gapLen = slotsGapLen,
2161 capacity = slotsCapacity
2162 )
2163 groups.updateDataIndex(groupAddress, newAnchor)
2164 }
2165
2166 // 6) update any anchors in the group being moved
2167 moveAnchors(groupToMove + moveLen, current, moveLen)
2168
2169 // 7) remove the old groups
2170 val anchorsRemoved = removeGroups(groupToMove + moveLen, moveLen)
2171 runtimeCheck(!anchorsRemoved) { "Unexpectedly removed anchors" }
2172
2173 // 8) fix parent anchors
2174 fixParentAnchorsFor(parent, currentGroupEnd, current)
2175
2176 // 9) remove the old slots (must be last)
2177 if (moveDataLen > 0) {
2178 removeSlots(dataStart + moveDataLen, moveDataLen, groupToMove + moveLen - 1)
2179 }
2180 }
2181
2182 companion object {
moveGroupnull2183 private fun moveGroup(
2184 fromWriter: SlotWriter,
2185 fromIndex: Int,
2186 toWriter: SlotWriter,
2187 updateFromCursor: Boolean,
2188 updateToCursor: Boolean,
2189 removeSourceGroup: Boolean = true
2190 ): List<Anchor> {
2191 val groupsToMove = fromWriter.groupSize(fromIndex)
2192 val sourceGroupsEnd = fromIndex + groupsToMove
2193 val sourceSlotsStart = fromWriter.dataIndex(fromIndex)
2194 val sourceSlotsEnd = fromWriter.dataIndex(sourceGroupsEnd)
2195 val slotsToMove = sourceSlotsEnd - sourceSlotsStart
2196 val hasMarks = fromWriter.containsAnyGroupMarks(fromIndex)
2197
2198 // Make room in the slot table
2199 toWriter.insertGroups(groupsToMove)
2200 toWriter.insertSlots(slotsToMove, toWriter.currentGroup)
2201
2202 // If either from gap is before the move, move the gap after the move to simplify
2203 // the logic of this method.
2204 if (fromWriter.groupGapStart < sourceGroupsEnd) {
2205 fromWriter.moveGroupGapTo(sourceGroupsEnd)
2206 }
2207 if (fromWriter.slotsGapStart < sourceSlotsEnd) {
2208 fromWriter.moveSlotGapTo(sourceSlotsEnd, sourceGroupsEnd)
2209 }
2210
2211 // Copy the groups and slots
2212 val groups = toWriter.groups
2213 val currentGroup = toWriter.currentGroup
2214 fromWriter.groups.copyInto(
2215 destination = groups,
2216 destinationOffset = currentGroup * Group_Fields_Size,
2217 startIndex = fromIndex * Group_Fields_Size,
2218 endIndex = sourceGroupsEnd * Group_Fields_Size
2219 )
2220 val slots = toWriter.slots
2221 val currentSlot = toWriter.currentSlot
2222 fromWriter.slots.fastCopyInto(
2223 destination = slots,
2224 destinationOffset = currentSlot,
2225 startIndex = sourceSlotsStart,
2226 endIndex = sourceSlotsEnd
2227 )
2228
2229 // Fix the parent anchors and data anchors. This would read better as two loops but
2230 // conflating the loops has better locality of reference.
2231 val parent = toWriter.parent
2232 groups.updateParentAnchor(currentGroup, parent)
2233 val parentDelta = currentGroup - fromIndex
2234 val moveEnd = currentGroup + groupsToMove
2235 val dataIndexDelta = currentSlot - with(toWriter) { groups.dataIndex(currentGroup) }
2236 var slotsGapOwner = toWriter.slotsGapOwner
2237 val slotsGapLen = toWriter.slotsGapLen
2238 val slotsCapacity = slots.size
2239 for (groupAddress in currentGroup until moveEnd) {
2240 // Update the parent anchor, the first group has already been set.
2241 if (groupAddress != currentGroup) {
2242 val previousParent = groups.parentAnchor(groupAddress)
2243 groups.updateParentAnchor(groupAddress, previousParent + parentDelta)
2244 }
2245
2246 val newDataIndex =
2247 with(toWriter) { groups.dataIndex(groupAddress) + dataIndexDelta }
2248 val newDataAnchor =
2249 with(toWriter) {
2250 dataIndexToDataAnchor(
2251 newDataIndex,
2252 // Ensure that if the slotGapOwner is below groupAddress we get an end
2253 // relative
2254 // anchor
2255 if (slotsGapOwner < groupAddress) 0 else slotsGapStart,
2256 slotsGapLen,
2257 slotsCapacity
2258 )
2259 }
2260
2261 // Update the data index
2262 groups.updateDataAnchor(groupAddress, newDataAnchor)
2263
2264 // Move the slotGapOwner if necessary
2265 if (groupAddress == slotsGapOwner) slotsGapOwner++
2266 }
2267 toWriter.slotsGapOwner = slotsGapOwner
2268
2269 // Extract the anchors in range
2270 val startAnchors = fromWriter.anchors.locationOf(fromIndex, fromWriter.size)
2271 val endAnchors = fromWriter.anchors.locationOf(sourceGroupsEnd, fromWriter.size)
2272 val anchors =
2273 if (startAnchors < endAnchors) {
2274 val sourceAnchors = fromWriter.anchors
2275 val anchors = ArrayList<Anchor>(endAnchors - startAnchors)
2276
2277 // update the anchor locations to their new location
2278 val anchorDelta = currentGroup - fromIndex
2279 for (anchorIndex in startAnchors until endAnchors) {
2280 val sourceAnchor = sourceAnchors[anchorIndex]
2281 sourceAnchor.location += anchorDelta
2282 anchors.add(sourceAnchor)
2283 }
2284
2285 // Insert them into the new table
2286 val insertLocation =
2287 toWriter.anchors.locationOf(toWriter.currentGroup, toWriter.size)
2288 toWriter.anchors.addAll(insertLocation, anchors)
2289
2290 // Remove them from the old table
2291 sourceAnchors.subList(startAnchors, endAnchors).clear()
2292
2293 anchors
2294 } else emptyList()
2295
2296 // Move any source information from the source table to the destination table
2297 if (anchors.isNotEmpty()) {
2298 val sourceSourceInformationMap = fromWriter.sourceInformationMap
2299 val destinationSourceInformation = toWriter.sourceInformationMap
2300 if (sourceSourceInformationMap != null && destinationSourceInformation != null) {
2301 anchors.fastForEach { anchor ->
2302 val information = sourceSourceInformationMap[anchor]
2303 if (information != null) {
2304 sourceSourceInformationMap.remove(anchor)
2305 destinationSourceInformation[anchor] = information
2306 }
2307 }
2308 }
2309 }
2310
2311 // Record the new group in the parent information
2312 val toWriterParent = toWriter.parent
2313 toWriter.sourceInformationOf(parent)?.let {
2314 var predecessor = -1
2315 var child = toWriterParent + 1
2316 val endGroup = toWriter.currentGroup
2317 while (child < endGroup) {
2318 predecessor = child
2319 child += toWriter.groups.groupSize(child)
2320 }
2321 it.addGroupAfter(toWriter, predecessor, endGroup)
2322 }
2323 val parentGroup = fromWriter.parent(fromIndex)
2324 val anchorsRemoved =
2325 if (!removeSourceGroup) {
2326 // e.g.: we can skip groups removal for insertTable of Composer because
2327 // it's going to be disposed anyway after changes applied
2328 false
2329 } else if (updateFromCursor) {
2330 // Remove the group using the sequence the writer expects when removing a group,
2331 // that
2332 // is the root group and the group's parent group must be correctly started and
2333 // ended
2334 // when it is not a root group.
2335 val needsStartGroups = parentGroup >= 0
2336 if (needsStartGroups) {
2337 // If we are not a root group then we are removing from a group so ensure
2338 // the
2339 // root group is started and then seek to the parent group and start it.
2340 fromWriter.startGroup()
2341 fromWriter.advanceBy(parentGroup - fromWriter.currentGroup)
2342 fromWriter.startGroup()
2343 }
2344 fromWriter.advanceBy(fromIndex - fromWriter.currentGroup)
2345 val anchorsRemoved = fromWriter.removeGroup()
2346 if (needsStartGroups) {
2347 fromWriter.skipToGroupEnd()
2348 fromWriter.endGroup()
2349 fromWriter.skipToGroupEnd()
2350 fromWriter.endGroup()
2351 }
2352 anchorsRemoved
2353 } else {
2354 // Remove the group directly instead of using cursor operations.
2355 val anchorsRemoved = fromWriter.removeGroups(fromIndex, groupsToMove)
2356 fromWriter.removeSlots(sourceSlotsStart, slotsToMove, fromIndex - 1)
2357 anchorsRemoved
2358 }
2359
2360 // Ensure we correctly do not remove anchors with the above delete.
2361 runtimeCheck(!anchorsRemoved) { "Unexpectedly removed anchors" }
2362
2363 // Update the node count in the toWriter
2364 toWriter.nodeCount +=
2365 if (groups.isNode(currentGroup)) 1 else groups.nodeCount(currentGroup)
2366
2367 // Move the toWriter's currentGroup passed the insert
2368 if (updateToCursor) {
2369 toWriter.currentGroup = currentGroup + groupsToMove
2370 toWriter.currentSlot = currentSlot + slotsToMove
2371 }
2372
2373 // If the group being inserted has marks then update the toWriter's parent marks
2374 if (hasMarks) {
2375 toWriter.updateContainsMark(parent)
2376 }
2377
2378 return anchors
2379 }
2380 }
2381
2382 /**
2383 * Move (insert then delete) the group at [anchor] group into the current insert location of
2384 * [writer]. All anchors in the group are moved into the slot table of [writer]. [anchor] must
2385 * be a group contained in the current started group.
2386 *
2387 * This requires [writer] be inserting and this writer to not be inserting.
2388 */
moveTonull2389 fun moveTo(anchor: Anchor, offset: Int, writer: SlotWriter): List<Anchor> {
2390 runtimeCheck(writer.insertCount > 0)
2391 runtimeCheck(insertCount == 0)
2392 runtimeCheck(anchor.valid)
2393 val location = anchorIndex(anchor) + offset
2394 val currentGroup = currentGroup
2395 runtimeCheck(location in currentGroup until currentGroupEnd)
2396 val parent = parent(location)
2397 val size = groupSize(location)
2398 val nodes = if (isNode(location)) 1 else nodeCount(location)
2399 val result =
2400 moveGroup(
2401 fromWriter = this,
2402 fromIndex = location,
2403 toWriter = writer,
2404 updateFromCursor = false,
2405 updateToCursor = false
2406 )
2407
2408 updateContainsMark(parent)
2409
2410 // Fix group sizes and node counts from the parent of the moved group to the current group
2411 var current = parent
2412 var updatingNodes = nodes > 0
2413 while (current >= currentGroup) {
2414 val currentAddress = groupIndexToAddress(current)
2415 groups.updateGroupSize(currentAddress, groups.groupSize(currentAddress) - size)
2416 if (updatingNodes) {
2417 if (groups.isNode(currentAddress)) updatingNodes = false
2418 else
2419 groups.updateNodeCount(currentAddress, groups.nodeCount(currentAddress) - nodes)
2420 }
2421 current = parent(current)
2422 }
2423 if (updatingNodes) {
2424 runtimeCheck(nodeCount >= nodes)
2425 nodeCount -= nodes
2426 }
2427
2428 return result
2429 }
2430
2431 /**
2432 * Move (insert and then delete) the group at [index] from [slots]. All anchors in the range
2433 * (including [index]) are moved to the slot table for which this is a reader.
2434 *
2435 * It is required that the writer be inserting.
2436 *
2437 * @return a list of the anchors that were moved
2438 */
moveFromnull2439 fun moveFrom(table: SlotTable, index: Int, removeSourceGroup: Boolean = true): List<Anchor> {
2440 runtimeCheck(insertCount > 0)
2441
2442 if (
2443 index == 0 &&
2444 currentGroup == 0 &&
2445 this.table.groupsSize == 0 &&
2446 table.groups.groupSize(index) == table.groupsSize
2447 ) {
2448 // Special case for moving the entire slot table into an empty table. This case occurs
2449 // during initial composition.
2450 val myGroups = groups
2451 val mySlots = slots
2452 val myAnchors = anchors
2453 val mySourceInformation = sourceInformationMap
2454 val myCallInformation = calledByMap
2455 val groups = table.groups
2456 val groupsSize = table.groupsSize
2457 val slots = table.slots
2458 val slotsSize = table.slotsSize
2459 val sourceInformation = table.sourceInformationMap
2460 val callInformation = table.calledByMap
2461 this.groups = groups
2462 this.slots = slots
2463 this.anchors = table.anchors
2464 this.groupGapStart = groupsSize
2465 this.groupGapLen = groups.size / Group_Fields_Size - groupsSize
2466 this.slotsGapStart = slotsSize
2467 this.slotsGapLen = slots.size - slotsSize
2468 this.slotsGapOwner = groupsSize
2469 this.sourceInformationMap = sourceInformation
2470 this.calledByMap = callInformation
2471
2472 table.setTo(myGroups, 0, mySlots, 0, myAnchors, mySourceInformation, myCallInformation)
2473 return this.anchors
2474 }
2475
2476 return table.write { tableWriter ->
2477 moveGroup(
2478 tableWriter,
2479 index,
2480 this,
2481 updateFromCursor = true,
2482 updateToCursor = true,
2483 removeSourceGroup = removeSourceGroup
2484 )
2485 }
2486 }
2487
2488 /**
2489 * Replace the key of the current group with one that will not match its current value which
2490 * will cause the composer to discard it and rebuild the content.
2491 *
2492 * This is used during live edit when the function that generated the content has been changed
2493 * and the slot table information does not match the expectations of the new code. This is done
2494 * conservatively in that any change in the code is assume to make the state stored in the table
2495 * incompatible.
2496 */
bashCurrentGroupnull2497 fun bashCurrentGroup() {
2498 groups.updateGroupKey(currentGroup, LIVE_EDIT_INVALID_KEY)
2499 }
2500
2501 /**
2502 * Insert the group at [index] in [table] to be the content of [currentGroup] plus [offset]
2503 * without moving [currentGroup].
2504 *
2505 * It is required that the writer is *not* inserting and the [currentGroup] is empty.
2506 *
2507 * @return a list of the anchors that were moved.
2508 */
moveIntoGroupFromnull2509 fun moveIntoGroupFrom(offset: Int, table: SlotTable, index: Int): List<Anchor> {
2510 runtimeCheck(insertCount <= 0 && groupSize(currentGroup + offset) == 1)
2511 val previousCurrentGroup = currentGroup
2512 val previousCurrentSlot = currentSlot
2513 val previousCurrentSlotEnd = currentSlotEnd
2514 advanceBy(offset)
2515 startGroup()
2516 beginInsert()
2517 val anchors =
2518 table.write { tableWriter ->
2519 moveGroup(tableWriter, index, this, updateFromCursor = false, updateToCursor = true)
2520 }
2521 endInsert()
2522 endGroup()
2523 currentGroup = previousCurrentGroup
2524 currentSlot = previousCurrentSlot
2525 currentSlotEnd = previousCurrentSlotEnd
2526 return anchors
2527 }
2528
2529 /** Allocate an anchor to the current group or [index]. */
anchornull2530 fun anchor(index: Int = currentGroup): Anchor =
2531 anchors.getOrAdd(index, size) {
2532 Anchor(if (index <= groupGapStart) index else -(size - index))
2533 }
2534
markGroupnull2535 fun markGroup(group: Int = parent) {
2536 val groupAddress = groupIndexToAddress(group)
2537 if (!groups.hasMark(groupAddress)) {
2538 groups.updateMark(groupAddress, true)
2539 if (!groups.containsMark(groupAddress)) {
2540 // This is a new mark, record the parent needs to update its contains mark.
2541 updateContainsMark(parent(group))
2542 }
2543 }
2544 }
2545
containsGroupMarknull2546 private fun containsGroupMark(group: Int) =
2547 group >= 0 && groups.containsMark(groupIndexToAddress(group))
2548
2549 private fun containsAnyGroupMarks(group: Int) =
2550 group >= 0 && groups.containsAnyMark(groupIndexToAddress(group))
2551
2552 private var pendingRecalculateMarks: PrioritySet? = null
2553
2554 private fun recalculateMarks() {
2555 pendingRecalculateMarks?.let { set ->
2556 while (set.isNotEmpty()) {
2557 updateContainsMarkNow(set.takeMax(), set)
2558 }
2559 }
2560 }
2561
updateContainsMarknull2562 private fun updateContainsMark(group: Int) {
2563 if (group >= 0) {
2564 (pendingRecalculateMarks ?: PrioritySet().also { pendingRecalculateMarks = it }).add(
2565 group
2566 )
2567 }
2568 }
2569
updateContainsMarkNownull2570 private fun updateContainsMarkNow(group: Int, set: PrioritySet) {
2571 val groupAddress = groupIndexToAddress(group)
2572 val containsAnyMarks = childContainsAnyMarks(group)
2573 val markChanges = groups.containsMark(groupAddress) != containsAnyMarks
2574 if (markChanges) {
2575 groups.updateContainsMark(groupAddress, containsAnyMarks)
2576 val parent = parent(group)
2577 if (parent >= 0) set.add(parent)
2578 }
2579 }
2580
childContainsAnyMarksnull2581 private fun childContainsAnyMarks(group: Int): Boolean {
2582 var child = group + 1
2583 val end = group + groupSize(group)
2584 while (child < end) {
2585 if (groups.containsAnyMark(groupIndexToAddress(child))) return true
2586 child += groupSize(child)
2587 }
2588 return false
2589 }
2590
2591 /** Return the current anchor location while changing the slot table. */
<lambda>null2592 fun anchorIndex(anchor: Anchor) = anchor.location.let { if (it < 0) size + it else it }
2593
toStringnull2594 override fun toString(): String {
2595 return "SlotWriter(current = $currentGroup end=$currentGroupEnd size = $size " +
2596 "gap=$groupGapStart-${groupGapStart + groupGapLen})"
2597 }
2598
2599 /** Save [currentGroupEnd] to [endStack]. */
saveCurrentGroupEndnull2600 private fun saveCurrentGroupEnd() {
2601 // Record the end location as relative to the end of the slot table so when we pop it
2602 // back off again all inserts and removes that happened while a child group was open
2603 // are already reflected into its value.
2604 endStack.push(capacity - groupGapLen - currentGroupEnd)
2605 }
2606
2607 /** Restore [currentGroupEnd] from [endStack]. */
restoreCurrentGroupEndnull2608 private fun restoreCurrentGroupEnd(): Int {
2609 val newGroupEnd = (capacity - groupGapLen) - endStack.pop()
2610 currentGroupEnd = newGroupEnd
2611 return newGroupEnd
2612 }
2613
2614 /**
2615 * As groups move their parent anchors need to be updated. This recursively updates the parent
2616 * anchors [parent] starting at [firstChild] and ending at [endGroup]. These are passed as a
2617 * parameter to as the [groups] does not contain the current values for [parent] yet.
2618 */
fixParentAnchorsFornull2619 private fun fixParentAnchorsFor(parent: Int, endGroup: Int, firstChild: Int) {
2620 val parentAnchor = parentIndexToAnchor(parent, groupGapStart)
2621 var child = firstChild
2622 while (child < endGroup) {
2623 groups.updateParentAnchor(groupIndexToAddress(child), parentAnchor)
2624 val childEnd = child + groups.groupSize(groupIndexToAddress(child))
2625 fixParentAnchorsFor(child, childEnd, child + 1)
2626 child = childEnd
2627 }
2628 }
2629
2630 /** Move the gap in [groups] to [index]. */
moveGroupGapTonull2631 private fun moveGroupGapTo(index: Int) {
2632 val gapLen = groupGapLen
2633 val gapStart = groupGapStart
2634 if (gapStart != index) {
2635 if (anchors.isNotEmpty()) updateAnchors(gapStart, index)
2636 if (gapLen > 0) {
2637 val groups = groups
2638 // Here physical is used to mean an index of the actual first int of the group in
2639 // the
2640 // array as opposed ot the logical address which is in groups of Group_Field_Size
2641 // integers. IntArray.copyInto expects physical indexes.
2642 val groupPhysicalAddress = index * Group_Fields_Size
2643 val groupPhysicalGapLen = gapLen * Group_Fields_Size
2644 val groupPhysicalGapStart = gapStart * Group_Fields_Size
2645 if (index < gapStart) {
2646 groups.copyInto(
2647 destination = groups,
2648 destinationOffset = groupPhysicalAddress + groupPhysicalGapLen,
2649 startIndex = groupPhysicalAddress,
2650 endIndex = groupPhysicalGapStart
2651 )
2652 } else {
2653 groups.copyInto(
2654 destination = groups,
2655 destinationOffset = groupPhysicalGapStart,
2656 startIndex = groupPhysicalGapStart + groupPhysicalGapLen,
2657 endIndex = groupPhysicalAddress + groupPhysicalGapLen
2658 )
2659 }
2660 }
2661
2662 // Gap has moved so the anchor for the groups that moved have changed so the parent
2663 // anchors that refer to these groups must be updated.
2664 var groupAddress = if (index < gapStart) index + gapLen else gapStart
2665 val capacity = capacity
2666 runtimeCheck(groupAddress < capacity)
2667 while (groupAddress < capacity) {
2668 val oldAnchor = groups.parentAnchor(groupAddress)
2669 val oldIndex = parentAnchorToIndex(oldAnchor)
2670 val newAnchor = parentIndexToAnchor(oldIndex, index)
2671 if (newAnchor != oldAnchor) {
2672 groups.updateParentAnchor(groupAddress, newAnchor)
2673 }
2674 groupAddress++
2675 if (groupAddress == index) groupAddress += gapLen
2676 }
2677 }
2678 this.groupGapStart = index
2679 }
2680
2681 /**
2682 * Move the gap in [slots] to [index] where [group] is expected to receive any new slots added.
2683 */
moveSlotGapTonull2684 private fun moveSlotGapTo(index: Int, group: Int) {
2685 val gapLen = slotsGapLen
2686 val gapStart = slotsGapStart
2687 val slotsGapOwner = slotsGapOwner
2688 if (gapStart != index) {
2689 val slots = slots
2690 if (index < gapStart) {
2691 // move the gap down to index by shifting the data up.
2692 slots.fastCopyInto(
2693 destination = slots,
2694 destinationOffset = index + gapLen,
2695 startIndex = index,
2696 endIndex = gapStart
2697 )
2698 } else {
2699 // Shift the data down, leaving the gap at index
2700 slots.fastCopyInto(
2701 destination = slots,
2702 destinationOffset = gapStart,
2703 startIndex = gapStart + gapLen,
2704 endIndex = index + gapLen
2705 )
2706 }
2707 }
2708
2709 // Update the data anchors affected by the move
2710 val newSlotsGapOwner = min(group + 1, size)
2711 if (slotsGapOwner != newSlotsGapOwner) {
2712 val slotsSize = slots.size - gapLen
2713 if (newSlotsGapOwner < slotsGapOwner) {
2714 var updateAddress = groupIndexToAddress(newSlotsGapOwner)
2715 val stopUpdateAddress = groupIndexToAddress(slotsGapOwner)
2716 val groupGapStart = groupGapStart
2717 while (updateAddress < stopUpdateAddress) {
2718 val anchor = groups.dataAnchor(updateAddress)
2719 runtimeCheck(anchor >= 0) {
2720 "Unexpected anchor value, expected a positive anchor"
2721 }
2722 groups.updateDataAnchor(updateAddress, -(slotsSize - anchor + 1))
2723 updateAddress++
2724 if (updateAddress == groupGapStart) updateAddress += groupGapLen
2725 }
2726 } else {
2727 var updateAddress = groupIndexToAddress(slotsGapOwner)
2728 val stopUpdateAddress = groupIndexToAddress(newSlotsGapOwner)
2729 while (updateAddress < stopUpdateAddress) {
2730 val anchor = groups.dataAnchor(updateAddress)
2731 runtimeCheck(anchor < 0) {
2732 "Unexpected anchor value, expected a negative anchor"
2733 }
2734 groups.updateDataAnchor(updateAddress, slotsSize + anchor + 1)
2735 updateAddress++
2736 if (updateAddress == groupGapStart) updateAddress += groupGapLen
2737 }
2738 }
2739 this.slotsGapOwner = newSlotsGapOwner
2740 }
2741 this.slotsGapStart = index
2742 }
2743
clearSlotGapnull2744 private fun clearSlotGap() {
2745 val slotsGapStart = slotsGapStart
2746 val slotsGapEnd = slotsGapStart + slotsGapLen
2747 slots.fill(null, slotsGapStart, slotsGapEnd)
2748 }
2749
2750 /**
2751 * Insert [size] number of groups in front of [currentGroup]. These groups are implicitly a
2752 * child of [parent].
2753 */
insertGroupsnull2754 private fun insertGroups(size: Int) {
2755 if (size > 0) {
2756 val currentGroup = currentGroup
2757 moveGroupGapTo(currentGroup)
2758 val gapStart = groupGapStart
2759 var gapLen = groupGapLen
2760 val oldCapacity = groups.size / Group_Fields_Size
2761 val oldSize = oldCapacity - gapLen
2762 if (gapLen < size) {
2763 // Create a bigger gap
2764 val groups = groups
2765
2766 // Double the size of the array, but at least MinGrowthSize and >= size
2767 val newCapacity = max(max(oldCapacity * 2, oldSize + size), MinGroupGrowthSize)
2768 val newGroups = IntArray(newCapacity * Group_Fields_Size)
2769 val newGapLen = newCapacity - oldSize
2770 val oldGapEndAddress = gapStart + gapLen
2771 val newGapEndAddress = gapStart + newGapLen
2772
2773 // Copy the old arrays into the new arrays
2774 groups.copyInto(
2775 destination = newGroups,
2776 destinationOffset = 0,
2777 startIndex = 0,
2778 endIndex = gapStart * Group_Fields_Size
2779 )
2780 groups.copyInto(
2781 destination = newGroups,
2782 destinationOffset = newGapEndAddress * Group_Fields_Size,
2783 startIndex = oldGapEndAddress * Group_Fields_Size,
2784 endIndex = oldCapacity * Group_Fields_Size
2785 )
2786
2787 // Update the gap and slots
2788 this.groups = newGroups
2789 gapLen = newGapLen
2790 }
2791
2792 // Move the currentGroupEnd to account for inserted groups.
2793 val currentEnd = currentGroupEnd
2794 if (currentEnd >= gapStart) this.currentGroupEnd = currentEnd + size
2795
2796 // Update the gap start and length
2797 this.groupGapStart = gapStart + size
2798 this.groupGapLen = gapLen - size
2799
2800 // Replicate the current group data index to the new slots
2801 val index = if (oldSize > 0) dataIndex(currentGroup + size) else 0
2802
2803 // If the slotGapOwner is before the current location ensure we get end relative offsets
2804 val anchor =
2805 dataIndexToDataAnchor(
2806 index,
2807 if (slotsGapOwner < gapStart) 0 else slotsGapStart,
2808 slotsGapLen,
2809 slots.size
2810 )
2811 for (groupAddress in gapStart until gapStart + size) {
2812 groups.updateDataAnchor(groupAddress, anchor)
2813 }
2814 val slotsGapOwner = slotsGapOwner
2815 if (slotsGapOwner >= gapStart) {
2816 this.slotsGapOwner = slotsGapOwner + size
2817 }
2818 }
2819 }
2820
2821 /**
2822 * Insert room into the slot table. This is performed by first moving the gap to [currentSlot]
2823 * and then reducing the gap [size] slots. If the gap is smaller than [size] the gap is grown to
2824 * at least accommodate [size] slots. The new slots are associated with [group].
2825 */
insertSlotsnull2826 private fun insertSlots(size: Int, group: Int) {
2827 if (size > 0) {
2828 moveSlotGapTo(currentSlot, group)
2829 val gapStart = slotsGapStart
2830 var gapLen = slotsGapLen
2831 if (gapLen < size) {
2832 val slots = slots
2833
2834 // Create a bigger gap
2835 val oldCapacity = slots.size
2836 val oldSize = oldCapacity - gapLen
2837
2838 // Double the size of the array, but at least MinGrowthSize and >= size
2839 val newCapacity = max(max(oldCapacity * 2, oldSize + size), MinSlotsGrowthSize)
2840 val newData = Array<Any?>(newCapacity) { null }
2841 val newGapLen = newCapacity - oldSize
2842 val oldGapEndAddress = gapStart + gapLen
2843 val newGapEndAddress = gapStart + newGapLen
2844
2845 // Copy the old arrays into the new arrays
2846 slots.fastCopyInto(
2847 destination = newData,
2848 destinationOffset = 0,
2849 startIndex = 0,
2850 endIndex = gapStart
2851 )
2852 slots.fastCopyInto(
2853 destination = newData,
2854 destinationOffset = newGapEndAddress,
2855 startIndex = oldGapEndAddress,
2856 endIndex = oldCapacity
2857 )
2858
2859 // Update the gap and slots
2860 this.slots = newData
2861 gapLen = newGapLen
2862 }
2863 val currentDataEnd = currentSlotEnd
2864 if (currentDataEnd >= gapStart) this.currentSlotEnd = currentDataEnd + size
2865 this.slotsGapStart = gapStart + size
2866 this.slotsGapLen = gapLen - size
2867 }
2868 }
2869
2870 /** Remove [len] group from [start]. */
removeGroupsnull2871 private fun removeGroups(start: Int, len: Int): Boolean {
2872 return if (len > 0) {
2873 var anchorsRemoved = false
2874 val anchors = anchors
2875
2876 // Move the gap to start of the removal and grow the gap
2877 moveGroupGapTo(start)
2878 if (anchors.isNotEmpty()) {
2879 anchorsRemoved = removeAnchors(start, len, sourceInformationMap)
2880 }
2881 groupGapStart = start
2882 val previousGapLen = groupGapLen
2883 val newGapLen = previousGapLen + len
2884 groupGapLen = newGapLen
2885
2886 // Adjust the gap owner if necessary.
2887 val slotsGapOwner = slotsGapOwner
2888 if (slotsGapOwner > start) {
2889 // Use max here as if we delete the current owner this group becomes the owner.
2890 this.slotsGapOwner = max(start, slotsGapOwner - len)
2891 }
2892 if (currentGroupEnd >= groupGapStart) currentGroupEnd -= len
2893
2894 val parent = parent
2895 // Update markers if necessary
2896 if (containsGroupMark(parent)) {
2897 updateContainsMark(parent)
2898 }
2899
2900 // Remove the group from its parent source information
2901 anchorsRemoved
2902 } else false
2903 }
2904
sourceInformationOfnull2905 internal fun sourceInformationOf(group: Int): GroupSourceInformation? =
2906 sourceInformationMap?.let { map -> tryAnchor(group)?.let { anchor -> map[anchor] } }
2907
tryAnchornull2908 internal fun tryAnchor(group: Int) =
2909 if (group in 0 until size) anchors.find(group, size) else null
2910
2911 /** Remove [len] slots from [start]. */
2912 private fun removeSlots(start: Int, len: Int, group: Int) {
2913 if (len > 0) {
2914 val gapLen = slotsGapLen
2915 val removeEnd = start + len
2916 moveSlotGapTo(removeEnd, group)
2917 slotsGapStart = start
2918 slotsGapLen = gapLen + len
2919 slots.fill(null, start, start + len)
2920 val currentDataEnd = currentSlotEnd
2921 if (currentDataEnd >= start) this.currentSlotEnd = currentDataEnd - len
2922 }
2923 }
2924
2925 /** A helper function to update the number of nodes in a group. */
updateNodeOfGroupnull2926 private fun updateNodeOfGroup(index: Int, value: Any?) {
2927 val address = groupIndexToAddress(index)
2928 runtimeCheck(address < groups.size && groups.isNode(address)) {
2929 "Updating the node of a group at $index that was not created with as a node group"
2930 }
2931 slots[dataIndexToDataAddress(groups.nodeIndex(address))] = value
2932 }
2933
2934 /** A helper function to update the anchors as the gap in [groups] moves. */
updateAnchorsnull2935 private fun updateAnchors(previousGapStart: Int, newGapStart: Int) {
2936 val gapLen = groupGapLen
2937 val size = capacity - gapLen
2938 if (previousGapStart < newGapStart) {
2939 // Gap is moving up
2940 // All anchors between the new gap and the old gap switch to be anchored to the
2941 // front of the table instead of the end.
2942 var index = anchors.locationOf(previousGapStart, size)
2943 while (index < anchors.size) {
2944 val anchor = anchors[index]
2945 val location = anchor.location
2946 if (location < 0) {
2947 val newLocation = size + location
2948 if (newLocation < newGapStart) {
2949 anchor.location = size + location
2950 index++
2951 } else break
2952 } else break
2953 }
2954 } else {
2955 // Gap is moving down. All anchors between newGapStart and previousGapStart need now
2956 // to be anchored to the end of the table instead of the front of the table.
2957 var index = anchors.locationOf(newGapStart, size)
2958 while (index < anchors.size) {
2959 val anchor = anchors[index]
2960 val location = anchor.location
2961 if (location >= 0) {
2962 anchor.location = -(size - location)
2963 index++
2964 } else break
2965 }
2966 }
2967 }
2968
2969 /** A helper function to remove the anchors for groups that are removed. */
removeAnchorsnull2970 private fun removeAnchors(
2971 gapStart: Int,
2972 size: Int,
2973 sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?
2974 ): Boolean {
2975 val gapLen = groupGapLen
2976 val removeEnd = gapStart + size
2977 val groupsSize = capacity - gapLen
2978 var index =
2979 anchors.locationOf(gapStart + size, groupsSize).let {
2980 if (it >= anchors.size) it - 1 else it
2981 }
2982 var removeAnchorEnd = 0
2983 var removeAnchorStart = index + 1
2984 while (index >= 0) {
2985 val anchor = anchors[index]
2986 val location = anchorIndex(anchor)
2987 if (location >= gapStart) {
2988 if (location < removeEnd) {
2989 anchor.location = Int.MIN_VALUE
2990 sourceInformationMap?.remove(anchor)
2991 removeAnchorStart = index
2992 if (removeAnchorEnd == 0) removeAnchorEnd = index + 1
2993 }
2994 index--
2995 } else break
2996 }
2997 return (removeAnchorStart < removeAnchorEnd).also {
2998 if (it) anchors.subList(removeAnchorStart, removeAnchorEnd).clear()
2999 }
3000 }
3001
3002 /** A helper function to update anchors for groups that have moved. */
moveAnchorsnull3003 private fun moveAnchors(originalLocation: Int, newLocation: Int, size: Int) {
3004 val end = originalLocation + size
3005 val groupsSize = this.size
3006
3007 // Remove all the anchors in range from the original location
3008 val index = anchors.locationOf(originalLocation, groupsSize)
3009 val removedAnchors = mutableListOf<Anchor>()
3010 if (index >= 0) {
3011 while (index < anchors.size) {
3012 val anchor = anchors[index]
3013 val location = anchorIndex(anchor)
3014 @Suppress("ConvertTwoComparisonsToRangeCheck")
3015 if (location >= originalLocation && location < end) {
3016 removedAnchors.add(anchor)
3017 anchors.removeAt(index)
3018 } else break
3019 }
3020 }
3021
3022 // Insert the anchors into there new location
3023 val moveDelta = newLocation - originalLocation
3024 removedAnchors.fastForEach { anchor ->
3025 val anchorIndex = anchorIndex(anchor)
3026 val newAnchorIndex = anchorIndex + moveDelta
3027 if (newAnchorIndex >= groupGapStart) {
3028 anchor.location = -(groupsSize - newAnchorIndex)
3029 } else {
3030 anchor.location = newAnchorIndex
3031 }
3032 val insertIndex = anchors.locationOf(newAnchorIndex, groupsSize)
3033 anchors.add(insertIndex, anchor)
3034 }
3035 }
3036
3037 /** A debugging aid that emits [groups] as a string. */
3038 @Suppress("unused")
<lambda>null3039 fun toDebugString(): String = buildString {
3040 appendLine(this@SlotWriter.toString())
3041 appendLine(" parent: $parent")
3042 appendLine(" current: $currentGroup")
3043 appendLine(" group gap: $groupGapStart-${groupGapStart + groupGapLen}($groupGapLen)")
3044 appendLine(" slots gap: $slotsGapStart-${slotsGapStart + slotsGapLen}($slotsGapLen)")
3045 appendLine(" gap owner: $slotsGapOwner")
3046 for (index in 0 until size) {
3047 groupAsString(index)
3048 append('\n')
3049 }
3050 }
3051
3052 /** A debugging aid that emits a group as a string into a string builder. */
groupAsStringnull3053 private fun StringBuilder.groupAsString(index: Int) {
3054 val address = groupIndexToAddress(index)
3055 append("Group(")
3056 if (index < 10) append(' ')
3057 if (index < 100) append(' ')
3058 if (index < 1000) append(' ')
3059 append(index)
3060 if (address != index) {
3061 append("(")
3062 append(address)
3063 append(")")
3064 }
3065 append('#')
3066 append(groups.groupSize(address))
3067 append('^')
3068 append(parentAnchorToIndex(groups.parentAnchor(address)))
3069 append(": key=")
3070 append(groups.key(address))
3071 append(", nodes=")
3072 append(groups.nodeCount(address))
3073 append(", dataAnchor=")
3074 append(groups.dataAnchor(address))
3075 append(", parentAnchor=")
3076 append(groups.parentAnchor(address))
3077 if (groups.isNode(address)) {
3078 append(
3079 ", node=${
3080 slots[
3081 dataIndexToDataAddress(groups.nodeIndex(address))
3082 ].toString().summarize(10)
3083 }"
3084 )
3085 }
3086
3087 val startData = groups.slotIndex(address)
3088 val successorAddress = groupIndexToAddress(index + 1)
3089 val endData = groups.dataIndex(successorAddress)
3090 if (endData > startData) {
3091 append(", [")
3092 for (dataIndex in startData until endData) {
3093 if (dataIndex != startData) append(", ")
3094 val dataAddress = dataIndexToDataAddress(dataIndex)
3095 append(slots[dataAddress].toString().summarize(10))
3096 }
3097 append(']')
3098 }
3099 append(")")
3100 }
3101
verifyDataAnchorsnull3102 internal fun verifyDataAnchors() {
3103 var previousDataIndex = 0
3104 val owner = slotsGapOwner
3105 var ownerFound = false
3106 val slotsSize = slots.size - slotsGapLen
3107 for (index in 0 until size) {
3108 val address = groupIndexToAddress(index)
3109 val dataAnchor = groups.dataAnchor(address)
3110 val dataIndex = groups.dataIndex(address)
3111 checkPrecondition(dataIndex >= previousDataIndex) {
3112 "Data index out of order at $index, previous = $previousDataIndex, current = " +
3113 "$dataIndex"
3114 }
3115 checkPrecondition(dataIndex <= slotsSize) {
3116 "Data index, $dataIndex, out of bound at $index"
3117 }
3118 if (dataAnchor < 0 && !ownerFound) {
3119 checkPrecondition(owner == index) {
3120 "Expected the slot gap owner to be $owner found gap at $index"
3121 }
3122 ownerFound = true
3123 }
3124 previousDataIndex = dataIndex
3125 }
3126 }
3127
3128 @Suppress("unused")
verifyParentAnchorsnull3129 internal fun verifyParentAnchors() {
3130 val gapStart = groupGapStart
3131 val gapLen = groupGapLen
3132 val capacity = capacity
3133 for (groupAddress in 0 until gapStart) {
3134 val parentAnchor = groups.parentAnchor(groupAddress)
3135 checkPrecondition(parentAnchor > parentAnchorPivot) {
3136 "Expected a start relative anchor at $groupAddress"
3137 }
3138 }
3139 for (groupAddress in gapStart + gapLen until capacity) {
3140 val parentAnchor = groups.parentAnchor(groupAddress)
3141 val parentIndex = parentAnchorToIndex(parentAnchor)
3142 if (parentIndex < gapStart) {
3143 checkPrecondition(parentAnchor > parentAnchorPivot) {
3144 "Expected a start relative anchor at $groupAddress"
3145 }
3146 } else {
3147 checkPrecondition(parentAnchor <= parentAnchorPivot) {
3148 "Expected an end relative anchor at $groupAddress"
3149 }
3150 }
3151 }
3152 }
3153
3154 internal val size
3155 get() = capacity - groupGapLen
3156
3157 private val capacity
3158 get() = groups.size / Group_Fields_Size
3159
groupIndexToAddressnull3160 private fun groupIndexToAddress(index: Int) =
3161 // Branch-less if (index < groupGapStart) index else index + groupGapLen
3162 index + groupGapLen * if (index < groupGapStart) 0 else 1
3163
3164 private fun dataIndexToDataAddress(dataIndex: Int) =
3165 // Branch-less if (dataIndex < slotsGapStart) dataIndex else dataIndex + slotsGapLen
3166 dataIndex + slotsGapLen * if (dataIndex < slotsGapStart) 0 else 1
3167
3168 private fun IntArray.parent(index: Int) =
3169 parentAnchorToIndex(parentAnchor(groupIndexToAddress(index)))
3170
3171 private fun dataIndex(index: Int) = groups.dataIndex(groupIndexToAddress(index))
3172
3173 private fun IntArray.dataIndex(address: Int) =
3174 if (address >= capacity) slots.size - slotsGapLen
3175 else dataAnchorToDataIndex(dataAnchor(address), slotsGapLen, slots.size)
3176
3177 private fun IntArray.slotIndex(address: Int) =
3178 if (address >= capacity) slots.size - slotsGapLen
3179 else dataAnchorToDataIndex(slotAnchor(address), slotsGapLen, slots.size)
3180
3181 private fun IntArray.updateDataIndex(address: Int, dataIndex: Int) {
3182 updateDataAnchor(
3183 address,
3184 dataIndexToDataAnchor(dataIndex, slotsGapStart, slotsGapLen, slots.size)
3185 )
3186 }
3187
IntArraynull3188 private fun IntArray.nodeIndex(address: Int) = dataIndex(address)
3189
3190 private fun IntArray.auxIndex(address: Int) =
3191 dataIndex(address) + countOneBits(groupInfo(address) shr (Aux_Shift + 1))
3192
3193 @Suppress("unused")
3194 private fun IntArray.dataIndexes() =
3195 groups
3196 .dataAnchors()
3197 .let {
3198 it.slice(0 until groupGapStart) +
3199 it.slice(groupGapStart + groupGapLen until (size / Group_Fields_Size))
3200 }
anchornull3201 .fastMap { anchor -> dataAnchorToDataIndex(anchor, slotsGapLen, slots.size) }
3202
3203 @Suppress("unused")
keysnull3204 private fun keys() =
3205 groups.keys().fastFilterIndexed { index, _ ->
3206 index < groupGapStart || index >= groupGapStart + groupGapLen
3207 }
3208
dataIndexToDataAnchornull3209 private fun dataIndexToDataAnchor(index: Int, gapStart: Int, gapLen: Int, capacity: Int) =
3210 if (index > gapStart) -((capacity - gapLen) - index + 1) else index
3211
3212 private fun dataAnchorToDataIndex(anchor: Int, gapLen: Int, capacity: Int) =
3213 if (anchor < 0) (capacity - gapLen) + anchor + 1 else anchor
3214
3215 private fun parentIndexToAnchor(index: Int, gapStart: Int) =
3216 if (index < gapStart) index else -(size - index - parentAnchorPivot)
3217
3218 private fun parentAnchorToIndex(index: Int) =
3219 if (index > parentAnchorPivot) index else size + index - parentAnchorPivot
3220 }
3221
3222 // Summarize the toString of an object.
3223 private fun String.summarize(size: Int) =
3224 this.replace("androidx.", "a.")
3225 .replace("compose.", "c.")
3226 .replace("runtime.", "r.")
3227 .replace("internal.", "ι.")
3228 .replace("ui.", "u.")
3229 .replace("Modifier", "μ")
3230 .replace("material.", "m.")
3231 .replace("Function", "λ")
3232 .replace("OpaqueKey", "κ")
3233 .replace("MutableState", "σ")
3234 .let { it.substring(0, min(size, it.length)) }
3235
compositionGroupOfnull3236 internal fun SlotTable.compositionGroupOf(group: Int): CompositionGroup {
3237 return SlotTableGroup(this, group, this.version)
3238 }
3239
3240 private class SlotTableGroup(
3241 val table: SlotTable,
3242 val group: Int,
3243 val version: Int = table.version
3244 ) : CompositionGroup, Iterable<CompositionGroup> {
3245 override val isEmpty: Boolean
3246 get() = table.groups.groupSize(group) == 0
3247
3248 override val key: Any
3249 get() =
3250 if (table.groups.hasObjectKey(group)) table.slots[table.groups.objectKeyIndex(group)]!!
3251 else table.groups.key(group)
3252
3253 override val sourceInfo: String?
3254 get() =
3255 if (table.groups.hasAux(group)) table.slots[table.groups.auxIndex(group)] as? String
3256 else table.sourceInformationOf(group)?.sourceInformation
3257
3258 override val node: Any?
3259 get() = if (table.groups.isNode(group)) table.slots[table.groups.nodeIndex(group)] else null
3260
3261 override val data: Iterable<Any?>
3262 get() =
<lambda>null3263 table.sourceInformationOf(group)?.let {
3264 SourceInformationGroupDataIterator(table, group, it)
3265 } ?: DataIterator(table, group)
3266
3267 override val identity: Any
3268 get() {
3269 validateRead()
<lambda>null3270 return table.read { it.anchor(group) }
3271 }
3272
3273 override val compositionGroups: Iterable<CompositionGroup>
3274 get() = this
3275
iteratornull3276 override fun iterator(): Iterator<CompositionGroup> {
3277 validateRead()
3278 return table.sourceInformationOf(group)?.let {
3279 SourceInformationGroupIterator(table, group, it, AnchoredGroupPath(group))
3280 } ?: GroupIterator(table, group + 1, group + table.groups.groupSize(group))
3281 }
3282
3283 override val groupSize: Int
3284 get() = table.groups.groupSize(group)
3285
3286 override val slotsSize: Int
3287 get() {
3288 val nextGroup = group + groupSize
3289 val nextSlot =
3290 if (nextGroup < table.groupsSize) table.groups.dataAnchor(nextGroup)
3291 else table.slotsSize
3292 return nextSlot - table.groups.dataAnchor(group)
3293 }
3294
validateReadnull3295 private fun validateRead() {
3296 if (table.version != version) {
3297 throwConcurrentModificationException()
3298 }
3299 }
3300
findnull3301 override fun find(identityToFind: Any): CompositionGroup? {
3302 fun findAnchoredGroup(anchor: Anchor): CompositionGroup? {
3303 if (table.ownsAnchor(anchor)) {
3304 val anchorGroup = table.anchorIndex(anchor)
3305 if (anchorGroup >= group && (anchorGroup - group < table.groups.groupSize(group))) {
3306 return SlotTableGroup(table, anchorGroup, version)
3307 }
3308 }
3309 return null
3310 }
3311
3312 fun findRelativeGroup(group: CompositionGroup, index: Int): CompositionGroup? =
3313 group.compositionGroups.drop(index).firstOrNull()
3314
3315 return when (identityToFind) {
3316 is Anchor -> findAnchoredGroup(identityToFind)
3317 is SourceInformationSlotTableGroupIdentity ->
3318 find(identityToFind.parentIdentity)?.let {
3319 findRelativeGroup(it, identityToFind.index)
3320 }
3321 else -> null
3322 }
3323 }
3324 }
3325
3326 private data class SourceInformationSlotTableGroupIdentity(val parentIdentity: Any, val index: Int)
3327
3328 private sealed class SourceInformationGroupPath {
getIdentitynull3329 abstract fun getIdentity(table: SlotTable): Any
3330 }
3331
3332 private class AnchoredGroupPath(val group: Int) : SourceInformationGroupPath() {
3333 override fun getIdentity(table: SlotTable): Any {
3334 return table.anchor(group)
3335 }
3336 }
3337
3338 private class RelativeGroupPath(val parent: SourceInformationGroupPath, val index: Int) :
3339 SourceInformationGroupPath() {
getIdentitynull3340 override fun getIdentity(table: SlotTable): Any {
3341 return SourceInformationSlotTableGroupIdentity(parent.getIdentity(table), index)
3342 }
3343 }
3344
3345 private class SourceInformationSlotTableGroup(
3346 val table: SlotTable,
3347 val parent: Int,
3348 val sourceInformation: GroupSourceInformation,
3349 val identityPath: SourceInformationGroupPath
3350 ) : CompositionGroup, Iterable<CompositionGroup> {
3351 override val key: Any = sourceInformation.key
3352 override val sourceInfo: String?
3353 get() = sourceInformation.sourceInformation
3354
3355 override val node: Any?
3356 get() = null
3357
3358 override val data: Iterable<Any?>
3359 get() = SourceInformationGroupDataIterator(table, parent, sourceInformation)
3360
3361 override val compositionGroups: Iterable<CompositionGroup> = this
3362 override val identity: Any
3363 get() = identityPath.getIdentity(table)
3364
3365 override val isEmpty: Boolean
3366 get() = sourceInformation.groups?.isEmpty() != false
3367
iteratornull3368 override fun iterator(): Iterator<CompositionGroup> =
3369 SourceInformationGroupIterator(table, parent, sourceInformation, identityPath)
3370 }
3371
3372 private class GroupIterator(val table: SlotTable, start: Int, val end: Int) :
3373 Iterator<CompositionGroup> {
3374 private var index = start
3375 private val version = table.version
3376
3377 init {
3378 if (table.writer) throwConcurrentModificationException()
3379 }
3380
3381 override fun hasNext() = index < end
3382
3383 override fun next(): CompositionGroup {
3384 validateRead()
3385 val group = index
3386
3387 index += table.groups.groupSize(group)
3388 return SlotTableGroup(table, group, version)
3389 }
3390
3391 private fun validateRead() {
3392 if (table.version != version) {
3393 throwConcurrentModificationException()
3394 }
3395 }
3396 }
3397
3398 private class DataIterator(
3399 val table: SlotTable,
3400 group: Int,
3401 ) : Iterable<Any?>, Iterator<Any?> {
3402 val start = table.groups.dataAnchor(group)
3403 val end =
3404 if (group + 1 < table.groupsSize) table.groups.dataAnchor(group + 1) else table.slotsSize
3405 var index = start
3406
iteratornull3407 override fun iterator(): Iterator<Any?> = this
3408
3409 override fun hasNext(): Boolean = index < end
3410
3411 override fun next(): Any? =
3412 (if (index >= 0 && index < table.slots.size) table.slots[index] else null).also { index++ }
3413 }
3414
3415 private class SourceInformationGroupDataIterator(
3416 val table: SlotTable,
3417 group: Int,
3418 sourceInformation: GroupSourceInformation,
3419 ) : Iterable<Any?>, Iterator<Any?> {
3420 private val base = table.groups.dataAnchor(group)
3421 private val start: Int = sourceInformation.dataStartOffset
3422 private val end: Int =
<lambda>null3423 sourceInformation.dataEndOffset.let {
3424 if (it > 0) it
3425 else
3426 (if (group + 1 < table.groupsSize) table.groups.dataAnchor(group + 1)
3427 else table.slotsSize) - base
3428 }
3429 private val filter =
<lambda>null3430 BitVector().also {
3431 // Filter any groups
3432 val groups = sourceInformation.groups ?: return@also
3433 groups.fastForEach { info ->
3434 if (info is GroupSourceInformation) {
3435 it.setRange(info.dataStartOffset, info.dataEndOffset)
3436 }
3437 }
3438 }
3439 private var index = filter.nextClear(start)
3440
iteratornull3441 override fun iterator(): Iterator<Any?> = this
3442
3443 override fun hasNext() = index < end
3444
3445 override fun next(): Any? =
3446 (if (index in 0 until end) table.slots[base + index] else null).also {
3447 index = filter.nextClear(index + 1)
3448 }
3449 }
3450
3451 private val EmptyLongArray = LongArray(0)
3452
3453 internal class BitVector {
3454 private var first: Long = 0
3455 private var second: Long = 0
3456 private var others: LongArray = EmptyLongArray
3457
3458 val size
3459 get() = (others.size + 2) * 64
3460
getnull3461 operator fun get(index: Int): Boolean {
3462 if (index < 64) return first and (1L shl index) != 0L
3463 if (index < 128) return second and (1L shl (index - 64)) != 0L
3464
3465 val others = others
3466 val size = others.size
3467 if (size == 0) return false
3468
3469 val address = (index / 64) - 2
3470 if (address >= size) return false
3471
3472 val bit = index % 64
3473 return (others[address] and (1L shl bit)) != 0L
3474 }
3475
setnull3476 operator fun set(index: Int, value: Boolean) {
3477 if (index < 64) {
3478 val mask = 1L shl index
3479 first = (first and mask.inv()) or (value.toBit().toLong() shl index)
3480 return
3481 }
3482
3483 if (index < 128) {
3484 val mask = 1L shl (index - 64)
3485 second = (second and mask.inv()) or (value.toBit().toLong() shl index)
3486 return
3487 }
3488
3489 val address = (index / 64) - 2
3490 val newIndex = index % 64
3491 val mask = 1L shl newIndex
3492 var others = others
3493 if (address >= others.size) {
3494 others = others.copyOf(address + 1)
3495 this.others = others
3496 }
3497
3498 val bits = others[address]
3499 others[address] = (bits and mask.inv()) or (value.toBit().toLong() shl newIndex)
3500 }
3501
<lambda>null3502 fun nextSet(index: Int) = nextBit(index) { it }
3503
<lambda>null3504 fun nextClear(index: Int) = nextBit(index) { it.inv() }
3505
3506 /**
3507 * Returns the index of the next bit in this bit vector, starting at index. The [valueSelector]
3508 * lets the caller modify the value before finding its first bit set.
3509 */
3510 @Suppress("NAME_SHADOWING")
nextBitnull3511 private inline fun nextBit(index: Int, valueSelector: (Long) -> Long): Int {
3512 if (index < 64) {
3513 // We shift right (unsigned) then back left to drop the first "index"
3514 // bits. This will set them all to 0, thus guaranteeing that the search
3515 // performed by [firstBitSet] will start at index
3516 val bit = (valueSelector(first) ushr index shl index).firstBitSet
3517 if (bit < 64) return bit
3518 }
3519
3520 if (index < 128) {
3521 val index = index - 64
3522 val bit = (valueSelector(second) ushr index shl index).firstBitSet
3523 if (bit < 64) return 64 + bit
3524 }
3525
3526 val index = max(index, 128)
3527 val start = (index / 64) - 2
3528 val others = others
3529
3530 for (i in start until others.size) {
3531 var value = valueSelector(others[i])
3532 // For the first element, the start index may be in the middle of the
3533 // 128 bit word, so we apply the same shift trick as for [first] and
3534 // [second] to start at the right spot in the bit field.
3535 if (i == start) {
3536 val shift = index % 64
3537 value = value ushr shift shl shift
3538 }
3539 val bit = value.firstBitSet
3540 if (bit < 64) return 128 + i * 64 + bit
3541 }
3542
3543 return Int.MAX_VALUE
3544 }
3545
3546 @Suppress("NAME_SHADOWING")
setRangenull3547 fun setRange(start: Int, end: Int) {
3548 var start = start
3549
3550 // If the range is valid we will use ~0L as our mask to create strings of 1s below,
3551 // otherwise we use 0 so we don't set any bits. We could return when start >= end
3552 // but this won't be a common case, so skip the branch
3553 val bits = if (start < end) -1L else 0L
3554
3555 // Set the bits to 0 if we don't need to set any bit in the first word
3556 var selector = bits * (start < 64).toBit()
3557 // Take our selector (either all 0s or all 1s), perform an unsigned shift to the
3558 // right to create a new word with "clampedEnd - start" bits, then shift it back
3559 // left to where the range begins. This lets us set up to 64 bits at a time without
3560 // doing an expensive loop that calls set()
3561 val firstValue = (selector ushr (64 - (min(64, end) - start))) shl start
3562 first = first or firstValue
3563 // If we need to set bits in the second word, clamp our start otherwise return now
3564 if (end > 64) start = max(start, 64) else return
3565
3566 // Set the bits to 0 if we don't need to set any bit in the second word
3567 selector = bits * (start < 128).toBit()
3568 // See firstValue above
3569 val secondValue = (selector ushr (128 - (min(128, end) - start))) shl start
3570 second = second or secondValue
3571 // If we need to set bits in the remainder array, clamp our start otherwise return now
3572 if (end > 128) start = max(start, 128) else return
3573
3574 for (bit in start until end) this[bit] = true
3575 }
3576
<lambda>null3577 override fun toString(): String = buildString {
3578 var first = true
3579 append("BitVector [")
3580 for (i in 0 until size) {
3581 if (this@BitVector[i]) {
3582 if (!first) append(", ")
3583 first = false
3584 append(i)
3585 }
3586 }
3587 append(']')
3588 }
3589 }
3590
3591 private val Long.firstBitSet
3592 inline get() = this.countTrailingZeroBits()
3593
3594 private class SourceInformationGroupIterator(
3595 val table: SlotTable,
3596 val parent: Int,
3597 val group: GroupSourceInformation,
3598 val path: SourceInformationGroupPath
3599 ) : Iterator<CompositionGroup> {
3600 private val version = table.version
3601 private var index = 0
3602
<lambda>null3603 override fun hasNext(): Boolean = group.groups?.let { index < it.size } ?: false
3604
nextnull3605 override fun next(): CompositionGroup {
3606 return when (val group = group.groups?.get(index++)) {
3607 is Anchor -> SlotTableGroup(table, group.location, version)
3608 is GroupSourceInformation ->
3609 SourceInformationSlotTableGroup(
3610 table = table,
3611 parent = parent,
3612 sourceInformation = group,
3613 identityPath = RelativeGroupPath(path, index - 1)
3614 )
3615 else -> composeRuntimeError("Unexpected group information structure")
3616 }
3617 }
3618 }
3619
3620 // Parent -1 is reserved to be the root parent index so the anchor must pivot on -2.
3621 private const val parentAnchorPivot = -2
3622
3623 // Group layout
3624 // 0 | 1 | 2 | 3 | 4 |
3625 // Key | Group info | Parent anchor | Size | Data anchor |
3626 private const val Key_Offset = 0
3627 private const val GroupInfo_Offset = 1
3628 private const val ParentAnchor_Offset = 2
3629 private const val Size_Offset = 3
3630 private const val DataAnchor_Offset = 4
3631 private const val Group_Fields_Size = 5
3632
3633 // Key is the key parameter passed into startGroup
3634
3635 // Group info is laid out as follows,
3636 // 31 30 29 28_27 26 25 24_23 22 21 20_19 18 17 16__15 14 13 12_11 10 09 08_07 06 05 04_03 02 01 00
3637 // 0 n ks ds m cm| node count |
3638 // where n is set when the group represents a node
3639 // where ks is whether the group has a object key slot
3640 // where ds is whether the group has a group data slot
3641 // where m is whether the group is marked
3642 // where cm is whether the group contains a mark
3643
3644 // Parent anchor is a group anchor to the parent, as the group gap is moved this value is updated to
3645 // refer to the parent.
3646
3647 // Slot count is the total number of group slots, including itself, occupied by the group.
3648
3649 // Data anchor is an anchor to the group data. The value is positive if it is before the data gap
3650 // and it is negative if it is after the data gap. As gaps are moved, these values are updated.
3651
3652 // Masks and flags
3653 private const val NodeBit_Mask = 0b0100_0000_0000_0000__0000_0000_0000_0000
3654 private const val NodeBit_Shift = 30
3655 private const val ObjectKey_Mask = 0b0010_0000_0000_0000__0000_0000_0000_0000
3656 private const val ObjectKey_Shift = 29
3657 private const val Aux_Mask = 0b0001_0000_0000_0000__0000_0000_0000_0000
3658 private const val Aux_Shift = 28
3659 private const val Mark_Mask = 0b0000_1000_0000_0000__0000_0000_0000_0000
3660 private const val Mark_Shift = 27
3661 private const val ContainsMark_Mask = 0b0000_0100_0000_0000__0000_0000_0000_0000
3662 private const val ContainsMark_Shift = 26
3663 private const val Slots_Shift = Aux_Shift
3664 private const val NodeCount_Mask = 0b0000_0011_1111_1111__1111_1111_1111_1111
3665
3666 // Special values
3667
3668 // The minimum number of groups to allocate the group table
3669 private const val MinGroupGrowthSize = 32
3670
3671 // The minimum number of data slots to allocate in the data slot table
3672 private const val MinSlotsGrowthSize = 32
3673
groupInfonull3674 private inline fun IntArray.groupInfo(address: Int): Int =
3675 this[address * Group_Fields_Size + GroupInfo_Offset]
3676
3677 private inline fun IntArray.isNode(address: Int) =
3678 this[address * Group_Fields_Size + GroupInfo_Offset] and NodeBit_Mask != 0
3679
3680 private inline fun IntArray.nodeIndex(address: Int) =
3681 this[address * Group_Fields_Size + DataAnchor_Offset]
3682
3683 private inline fun IntArray.hasObjectKey(address: Int) =
3684 this[address * Group_Fields_Size + GroupInfo_Offset] and ObjectKey_Mask != 0
3685
3686 private fun IntArray.objectKeyIndex(address: Int) =
3687 (address * Group_Fields_Size).let { slot ->
3688 this[slot + DataAnchor_Offset] +
3689 countOneBits(this[slot + GroupInfo_Offset] shr (ObjectKey_Shift + 1))
3690 }
3691
hasAuxnull3692 private inline fun IntArray.hasAux(address: Int) =
3693 this[address * Group_Fields_Size + GroupInfo_Offset] and Aux_Mask != 0
3694
3695 private fun IntArray.addAux(address: Int) {
3696 val arrayIndex = address * Group_Fields_Size + GroupInfo_Offset
3697 this[arrayIndex] = this[arrayIndex] or Aux_Mask
3698 }
3699
hasMarknull3700 private inline fun IntArray.hasMark(address: Int) =
3701 this[address * Group_Fields_Size + GroupInfo_Offset] and Mark_Mask != 0
3702
3703 private fun IntArray.updateMark(address: Int, value: Boolean) {
3704 val arrayIndex = address * Group_Fields_Size + GroupInfo_Offset
3705 val element = this[arrayIndex]
3706 this[arrayIndex] = (element and Mark_Mask.inv()) or (value.toBit() shl Mark_Shift)
3707 }
3708
containsMarknull3709 private inline fun IntArray.containsMark(address: Int) =
3710 this[address * Group_Fields_Size + GroupInfo_Offset] and ContainsMark_Mask != 0
3711
3712 private fun IntArray.updateContainsMark(address: Int, value: Boolean) {
3713 val arrayIndex = address * Group_Fields_Size + GroupInfo_Offset
3714 val element = this[arrayIndex]
3715 this[arrayIndex] =
3716 (element and ContainsMark_Mask.inv()) or (value.toBit() shl ContainsMark_Shift)
3717 }
3718
containsAnyMarknull3719 private inline fun IntArray.containsAnyMark(address: Int) =
3720 this[address * Group_Fields_Size + GroupInfo_Offset] and (ContainsMark_Mask or Mark_Mask) != 0
3721
3722 private fun IntArray.auxIndex(address: Int) =
3723 (address * Group_Fields_Size).let { slot ->
3724 if (slot >= size) size
3725 else
3726 this[slot + DataAnchor_Offset] +
3727 countOneBits(this[slot + GroupInfo_Offset] shr (Aux_Shift + 1))
3728 }
3729
IntArraynull3730 private fun IntArray.slotAnchor(address: Int) =
3731 (address * Group_Fields_Size).let { slot ->
3732 this[slot + DataAnchor_Offset] + countOneBits(this[slot + GroupInfo_Offset] shr Slots_Shift)
3733 }
3734
countOneBitsnull3735 private inline fun countOneBits(value: Int) = value.countOneBits()
3736
3737 // Key access
3738 private inline fun IntArray.key(address: Int) = this[address * Group_Fields_Size]
3739
3740 private fun IntArray.keys(len: Int = size) = slice(Key_Offset until len step Group_Fields_Size)
3741
3742 // Node count access
3743 private inline fun IntArray.nodeCount(address: Int) =
3744 this[address * Group_Fields_Size + GroupInfo_Offset] and NodeCount_Mask
3745
3746 private fun IntArray.updateNodeCount(address: Int, value: Int) {
3747 @Suppress("ConvertTwoComparisonsToRangeCheck")
3748 debugRuntimeCheck(value >= 0 && value < NodeCount_Mask)
3749 this[address * Group_Fields_Size + GroupInfo_Offset] =
3750 (this[address * Group_Fields_Size + GroupInfo_Offset] and NodeCount_Mask.inv()) or value
3751 }
3752
IntArraynull3753 private fun IntArray.nodeCounts(len: Int = size) =
3754 slice(GroupInfo_Offset until len step Group_Fields_Size).fastMap { it and NodeCount_Mask }
3755
3756 // Parent anchor
parentAnchornull3757 private inline fun IntArray.parentAnchor(address: Int) =
3758 this[address * Group_Fields_Size + ParentAnchor_Offset]
3759
3760 private inline fun IntArray.updateParentAnchor(address: Int, value: Int) {
3761 this[address * Group_Fields_Size + ParentAnchor_Offset] = value
3762 }
3763
IntArraynull3764 private fun IntArray.parentAnchors(len: Int = size) =
3765 slice(ParentAnchor_Offset until len step Group_Fields_Size)
3766
3767 // Slot count access
3768 private fun IntArray.groupSize(address: Int) = this[address * Group_Fields_Size + Size_Offset]
3769
3770 private fun IntArray.updateGroupSize(address: Int, value: Int) {
3771 debugRuntimeCheck(value >= 0)
3772 this[address * Group_Fields_Size + Size_Offset] = value
3773 }
3774
IntArraynull3775 private fun IntArray.slice(indices: Iterable<Int>): List<Int> {
3776 val list = mutableListOf<Int>()
3777 for (index in indices) {
3778 list.add(get(index))
3779 }
3780 return list
3781 }
3782
3783 @Suppress("unused")
groupSizesnull3784 private fun IntArray.groupSizes(len: Int = size) =
3785 slice(Size_Offset until len step Group_Fields_Size)
3786
3787 // Data anchor access
3788 private inline fun IntArray.dataAnchor(address: Int) =
3789 this[address * Group_Fields_Size + DataAnchor_Offset]
3790
3791 private inline fun IntArray.updateDataAnchor(address: Int, anchor: Int) {
3792 this[address * Group_Fields_Size + DataAnchor_Offset] = anchor
3793 }
3794
IntArraynull3795 private fun IntArray.dataAnchors(len: Int = size) =
3796 slice(DataAnchor_Offset until len step Group_Fields_Size)
3797
3798 // Update data
3799 private fun IntArray.initGroup(
3800 address: Int,
3801 key: Int,
3802 isNode: Boolean,
3803 hasDataKey: Boolean,
3804 hasData: Boolean,
3805 parentAnchor: Int,
3806 dataAnchor: Int
3807 ) {
3808 val arrayIndex = address * Group_Fields_Size
3809 this[arrayIndex + Key_Offset] = key
3810 // We turn each boolean into its corresponding bit field at the same time as we "or"
3811 // the fields together so we the generated aarch64 code can use left shifted operands
3812 // in the "orr" instructions directly.
3813 this[arrayIndex + GroupInfo_Offset] =
3814 (isNode.toBit() shl NodeBit_Shift) or
3815 (hasDataKey.toBit() shl ObjectKey_Shift) or
3816 (hasData.toBit() shl Aux_Shift)
3817 this[arrayIndex + ParentAnchor_Offset] = parentAnchor
3818 this[arrayIndex + Size_Offset] = 0
3819 this[arrayIndex + DataAnchor_Offset] = dataAnchor
3820 }
3821
toBitnull3822 private inline fun Boolean.toBit() = if (this) 1 else 0
3823
3824 private fun IntArray.updateGroupKey(
3825 address: Int,
3826 key: Int,
3827 ) {
3828 val arrayIndex = address * Group_Fields_Size
3829 this[arrayIndex + Key_Offset] = key
3830 }
3831
getOrAddnull3832 private inline fun ArrayList<Anchor>.getOrAdd(
3833 index: Int,
3834 effectiveSize: Int,
3835 block: () -> Anchor
3836 ): Anchor {
3837 val location = search(index, effectiveSize)
3838 return if (location < 0) {
3839 val anchor = block()
3840 add(-(location + 1), anchor)
3841 anchor
3842 } else get(location)
3843 }
3844
findnull3845 private fun ArrayList<Anchor>.find(index: Int, effectiveSize: Int): Anchor? {
3846 val location = search(index, effectiveSize)
3847 return if (location >= 0) get(location) else null
3848 }
3849
3850 /** This is inlined here instead to avoid allocating a lambda for the compare when this is used. */
searchnull3851 private fun ArrayList<Anchor>.search(location: Int, effectiveSize: Int): Int {
3852 var low = 0
3853 var high = size - 1
3854
3855 while (low <= high) {
3856 val mid = (low + high) ushr 1 // safe from overflows
3857 val midVal = get(mid).location.let { if (it < 0) effectiveSize + it else it }
3858 val cmp = midVal.compareTo(location)
3859
3860 when {
3861 cmp < 0 -> low = mid + 1
3862 cmp > 0 -> high = mid - 1
3863 else -> return mid // key found
3864 }
3865 }
3866 return -(low + 1) // key not found
3867 }
3868
3869 /**
3870 * A wrapper on [search] that always returns an index in to [this] even if [index] is not in the
3871 * array list.
3872 */
ArrayListnull3873 private fun ArrayList<Anchor>.locationOf(index: Int, effectiveSize: Int) =
3874 search(index, effectiveSize).let { if (it >= 0) it else -(it + 1) }
3875
3876 /**
3877 * PropertySet implements a set which allows recording integers into a set an efficiently extracting
3878 * the greatest max value out of the set. It does this using the heap structure from a heap sort
3879 * that ensures that adding or removing a value is O(log N) operation even if values are repeatedly
3880 * added and removed.
3881 */
3882 @JvmInline
3883 internal value class PrioritySet(private val list: MutableIntList = mutableIntListOf()) {
3884 // Add a value to the heap
addnull3885 fun add(value: Int) {
3886 // Filter trivial duplicates
3887 if (list.isNotEmpty() && (list[0] == value || list[list.size - 1] == value)) return
3888
3889 var index = list.size
3890 list.add(value)
3891
3892 // Shift the value up the heap.
3893 while (index > 0) {
3894 val parent = ((index + 1) ushr 1) - 1
3895 val parentValue = list[parent]
3896 if (value > parentValue) {
3897 list[index] = parentValue
3898 } else break
3899 index = parent
3900 }
3901 list[index] = value
3902 }
3903
isEmptynull3904 fun isEmpty() = list.isEmpty()
3905
3906 fun isNotEmpty() = list.isNotEmpty()
3907
3908 fun peek() = list.first()
3909
3910 // Remove a de-duplicated value from the heap
3911 fun takeMax(): Int {
3912 debugRuntimeCheck(list.size > 0) { "Set is empty" }
3913 val value = list[0]
3914
3915 // Skip duplicates. It is not time efficient to remove duplicates from the list while
3916 // adding so remove them when they leave the list. This also implies that the underlying
3917 // list's size is not an accurate size of the list so this set doesn't implement size.
3918 // If size is needed later consider de-duping on insert which might require companion map.
3919 while (list.isNotEmpty() && list[0] == value) {
3920 // Shift the last value down.
3921 list[0] = list.last()
3922 list.removeAt(list.size - 1)
3923 var index = 0
3924 val size = list.size
3925 val max = list.size ushr 1
3926 while (index < max) {
3927 val indexValue = list[index]
3928 val left = (index + 1) * 2 - 1
3929 val leftValue = list[left]
3930 val right = (index + 1) * 2
3931 if (right < size) {
3932 // Note: only right can exceed size because of the constraint on index being
3933 // less than floor(list.size / 2)
3934 val rightValue = list[right]
3935 if (rightValue > leftValue) {
3936 if (rightValue > indexValue) {
3937 list[index] = rightValue
3938 list[right] = indexValue
3939 index = right
3940 continue
3941 } else break
3942 }
3943 }
3944 if (leftValue > indexValue) {
3945 list[index] = leftValue
3946 list[left] = indexValue
3947 index = left
3948 } else break
3949 }
3950 }
3951 return value
3952 }
3953
3954 @Suppress("ExceptionMessage")
validateHeapnull3955 fun validateHeap() {
3956 val size = list.size
3957 for (index in 0 until size / 2) {
3958 val left = (index + 1) * 2 - 1
3959 val right = (index + 1) * 2
3960 checkPrecondition(list[index] >= list[left])
3961 checkPrecondition(right >= size || list[index] >= list[right])
3962 }
3963 }
3964 }
3965
3966 private const val LIVE_EDIT_INVALID_KEY = -3
3967
addnull3968 private fun MutableIntObjectMap<MutableIntSet>.add(key: Int, value: Int) {
3969 (this[key] ?: MutableIntSet().also { set(key, it) }).add(value)
3970 }
3971
throwConcurrentModificationExceptionnull3972 internal fun throwConcurrentModificationException() {
3973 throw ConcurrentModificationException()
3974 }
3975