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