Lesson 1
What the slot table stores
Use the gap-buffer implementation to distinguish groups, slots, anchors, and the target node tree.
Status: The distinction between composition memory and rendered nodes is Durable . The gap-buffer layout and private class names are Version-specific at the pinned revision.
Outcome
Explain what a group owns and what an anchor means after insertion.
Start with runtime memory, not a widget tree
A composition runs the same source more than once. Between runs, the runtime needs to know which execution region is being revisited, where its values are stored, and how many target nodes belong to that region. The slot table is that memory. It is not the rendered tree.
flowchart LR
Calls[Composer calls] --> Groups[Group records]
Groups --> Slots[Slots and node data]
Groups --> Anchors[Anchors]
Groups --> Reconcile[Compare structure]
Reconcile --> Changes[Node changes]At the pinned revision, SlotTable keeps a packed groups integer array and a separate slots object array. A group describes a region of the group array and its slot data. Its fields encode a runtime key, node count, group size, parent/data locations, and flags. Groups form a preorder tree: a parent is followed by its children. This favors linear scans; random access is more expensive unless an anchor is available (SlotTable.kt).
A slot is an element in the slot array owned by a group. It can hold a remembered value, scope, node, object key, or auxiliary value. A node recorded in composition memory is not the target node tree; the applier owns that tree. remember lifecycle and explicit keys are Chapter 4 topics.
Why a gap buffer needs anchors
The gap-buffer path reserves unused ranges in its backing arrays. An insertion or deletion can move array elements around a gap, so a plain integer index can become stale. A GapAnchor tracks a logical group position: before the gap it stores a forward location; after it stores a distance from the end. It resolves to the current index as the gap moves. If its group is removed, the anchor becomes invalid (GapAnchor.kt).
An anchor locates a group in composition memory; it is not a global UI ID.
Readers and writers enforce a boundary
SlotTable permits multiple SlotReaders but only one SlotWriter. Readers may coexist with readers, but a writer excludes readers and other writers; a reader cannot open while a writer is active, and a writer cannot open while readers remain. A traversal is therefore not interleaved with a writer’s mutation. This is access exclusion, not an atomic transaction or rollback guarantee (SlotTable.kt).
GapComposer orchestrates this storage-facing work. During a structural change it uses GapPending to track candidate groups and node positions, then matches emitted groups as composition proceeds. The pending structure is reconciliation work, not a second tree (GapComposer.kt).
Reproduce anchor movement
This is a pinned upstream-test procedure, not an experiment executed by this production edit. In SlotTableTests.kt, use the testSlotsNumbered() helper and these tests:
- Temporarily print
slots.toDebugString()before and after the writer block intestAnchorsTrackInserts; it reports group index/key, node count, group size, and slot ranges, with indentation showing parents. In the final reader, print eachanchor.toIndexFor(slots),reader.groupKey(anchor), andanchor.valid. - Run the control and mutation cases at AndroidX revision
cc1caf65677fc10a8ce8116eba46e716f4cef222:
./gradlew :compose:runtime:runtime:desktopTest \
--tests 'androidx.compose.runtime.composer.gapbuffer.SlotTableTests.testAllocateAnchors' \
--tests 'androidx.compose.runtime.composer.gapbuffer.SlotTableTests.testAnchorsTrackInserts' \
--tests 'androidx.compose.runtime.composer.gapbuffer.SlotTableTests.testAnchorTrackRemoves'
testAllocateAnchors is the untouched control: anchors resolve to keys 10…70. After the 30-group insertion, the same keys still resolve, although later indexes move. After the removal, anchors in the removed range become invalid while surviving anchors still resolve. The result demonstrates pinned gap-buffer movement and invalidation only; it does not establish UI identity, concurrent access, rollback, or behavior of another storage path.
Misconceptions
- “The slot table is the rendered tree.” It stores composition structure; an applier owns target nodes.
- “Every slot is remembered state.” Slots carry several kinds of runtime data.
- “A source line uniquely identifies state.” Surrounding group structure matters.
Check yourself
Why can an anchor locate a group after an insertion while a cached integer index cannot? Answer using logical position, gap movement, and invalidation on removal.
Evidence notes
| Claim | Direct evidence | Status |
|---|---|---|
| Composition memory is distinct from the rendered target tree | SlotTable.kt | Durable |
| The pinned gap-buffer representation packs groups and slots separately | SlotTable.kt | Version-specific |
| Anchors track edits and invalidate on removal | GapAnchor.kt | Version-specific |
Pending reconciliation belongs to GapComposer | GapComposer.kt and SlotTableTests.kt | Version-specific |
Freshness
Refresh when group fields, anchor encoding, reader/writer rules, or GapPending change.
Finished this lesson?
Your progress stays only in this browser.