LoginSignup
1
1

More than 1 year has passed since last update.

Jetpack ComposeのSlotTableへの変更の適応のコードリーディング

Last updated at Posted at 2021-10-03

の続きです。

今度まとめた記事を書く予定なので、この記事は読まなくていいです。

前回までで、recomposeの実装を読んでいくことができました。
SlotTableに反映していくところつまり、4を呼んでいきます。

1: recordComposerModificationsLocked() を呼ぶ。Recomposer.snapshotInvalidationsを見て、IdentityScopeMapを使って変更点に対しての影響を受けるスコープを取得し、Recomposer.compositionInvalidationsやComposition.invalidationsというフィールドに変更を入れる。
2: compositionInvalidationsをtoRecomposeに入れる。
3: toRecomposeに対して、performRecompose(composition, modifiedValues)、(内部でdoCompose())することでtoApplyを作成。SlotTableを見て、ここで木を見ていき、木への変更をrecordしていく。
4: recordされたtoApplyに対してapplyChanges()をすることで、SlotTableの書き換えが走る。 (予想。コード未読)

runRecomposeAndApplyChanges()で今までアプリ側の関数を呼び出したりして、SlotTableへの変更をchangesというプロパティに入れていきました。
このchangeが入っているのが、toApplyになります。このapplyChanges()を呼んで、SlotTableへの変更を行っていくところを見ていきましょう。

*Recomposer.runRecomposeAndApplyChanges()* ← Now reading

toApplyは size = 1 でperformRecompose()で呼び出した結果が入っている。
一個だけ入っているが、この中に8個SlotTableに反映すべきchagnesが入っている。

                    if (toApply.isNotEmpty()) {
                        changeCount++ // 0から1に。

                        // Perform apply changes
                        try {
                            toApply.fastForEach { composition ->
                                composition.applyChanges()
                            }
                        } finally {
                            toApply.clear()
                        }
                    }

                    synchronized(stateLock) {
                        deriveStateLocked()
                    }

CompositionImpl.applyChanges()

Recomposer.runRecomposeAndApplyChanges()
└── *CompositionImpl.applyChanges()* ← Now reading
    override fun applyChanges() {
        synchronized(lock) {
// abandonSetは何も入っていない。
// RememberEventDispatcherはComposable関数側にあるLaunchedEffectなどの実装を呼び出すための仕組みのように見えます。
            val manager = RememberEventDispatcher(abandonSet)
            try {
// Applyerに変更が始まることを通知する。ここでは何もしない。
                applier.onBeginChanges()

                // Apply all changes
// openして終わったらcloseする。
                slotTable.write { slots ->
                    val applier = applier
                    changes.fastForEach { change ->
                        change(applier, slots, manager)
                    }
                    changes.clear()
                }

                applier.onEndChanges()

                // Side effects run after lifecycle observers so that any remembered objects
                // that implement RememberObserver receive onRemembered before a side effect
                // that captured it and operates on it can run.
                manager.dispatchRememberObservers()
                manager.dispatchSideEffects()

                if (pendingInvalidScopes) {
                    pendingInvalidScopes = false
                    observations.removeValueIf { scope -> !scope.valid }
                    derivedStates.removeValueIf { derivedValue -> derivedValue !in observations }
                }
            } finally {
                manager.dispatchAbandons()
            }
            drainPendingModificationsLocked()
        }
    }
    inline fun <T> write(block: (writer: SlotWriter) -> T): T = openWriter()
        .let { writer ->
            try {
                block(writer)
            } finally {
                writer.close()
            }

このchangesを一つずつ行っていきます。

image.png

内容は前回の記事から。
まずはこのadvancebyになるのかな? :eyes:

0 = record { _, slots, _ -> slots.advanceBy(distance) }
10 進める

1 = private val startRootGroup: Change = { _, slots, _ -> slots.ensureStarted(0) }
ensureStartedGroupが始まったので、いろんなWriterのcurrentGroupなどを更新をかけるもの。で、引数が0なのでrootでやる。

2 = recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
ensureStartedGroupが始まったので、いろんなWriterのcurrentGroupなどを更新をかけるもの。で、引数がanchorなのでcurrent groupでやる

3 = private val removeCurrentGroupInstance: Change = { _, slots, rememberManager ->
slots.removeCurrentGroup(rememberManager)
}

4 = { _, slots, _ -> slots.endGroup() }

5 = { applier, _, _ -> applier.remove(removeIndex, count) }

6 = { _, slots, _ -> slots.advanceBy(distance) }// (distance = 3)
7 = { _, slots, _ -> slots.endGroup() }

現状整理

これからテーブルに変更を加えていくので、現状を記録しておきます。

slotTable:
androidx.compose.runtime.SlotTable@5935541
Group(0) key=100, nodes=2, size=16, slots=[0: {}]
 Group(1) key=1000, nodes=2, size=15
  Group(2) key=200, nodes=2, size=14 objectKey=OpaqueKey(key=provider)
   Group(3) key=-985533281, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.internal.ComposableLambdaImpl@3b52827]
    Group(4) key=-337788286, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@b882ad4]
     Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=false)@167707773]
     Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=false)@167707773, Function2<kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object>]
     Group(7) key=1036442245, nodes=0, size=2 aux=C(LaunchedEffect)P(1)336@14101L58:Effects.kt#9igjgp
      Group(8) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[13: kotlin.Unit, androidx.compose.runtime.LaunchedEffectImpl@8d3f428]
     Group(9) key=-337788139, nodes=1, size=4
      Group(10) key=1815931685, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@a4ed0be]
       Group(11) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp
        Group(12) key=125, nodes=0, size=1 node=Node1(name=node1), slots=[18: node1]
     Group(13) key=1815931958, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@81cf51f]
      Group(14) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp
       Group(15) key=125, nodes=0, size=1 node=Node2(name=node2), slots=[22: node2]
"SlotWriter.toString():"+toString()+
        "\ngroupsAsString():\n"+groupsAsString()
SlotWriter.toString():SlotWriter(current = 0 end=16 size = 16 gap=16-32)
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^9: key=1815931685, nodes=1, dataAnchor=15, parentAnchor=9)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=16, parentAnchor=10)
Group(  12#1^11: key=125, nodes=0, dataAnchor=17, parentAnchor=11)
Group(  13#3^4: key=1815931958, nodes=1, dataAnchor=19, parentAnchor=4)
Group(  14#2^13: key=1546164276, nodes=1, dataAnchor=20, parentAnchor=13)
Group(  15#1^14: key=125, nodes=0, dataAnchor=21, parentAnchor=14)

デバッガーでchangeごとの動きを見てみる

ちょっと普通に見ていくとけっこう大変なので、どのあたりを読むべきかのあたりをつけるために、デバッガーで動きを見てみます。

image.png
デバッガーで以下のコードが実行されるようにします。

val slotWriter: SlotWriter = slots
val nodeCountStackString = ""
for (index in 0 until slotWriter.nodeCountStack.size) {
    nodeCountStackString += slotWriter.nodeCountStack.peek(index).toString() + ","
}
val startStackString = ""
for (index in 0 until slotWriter.startStack.size) {
    startStackString += slotWriter.startStack.peek(index).toString() + ","
}
val endStackString = ""
for (index in 0 until slotWriter.endStack.size) {
    endStackString += slotWriter.endStack.peek(index).toString() + ","
}
"before${changes.indexOf(change)}" +
        "\nSlotWriter.toString():" + slotWriter.toString() +
        "\nnodeCountStack.size:" + slotWriter.nodeCountStack.size + " nodeCountStack:" + nodeCountStackString +
        "\nstartStack.size:" + slotWriter.startStack.size + " startStack:" + startStackString +
        "\nendStack.size:" + slotWriter.endStack.size + " endStack:" + endStackString +
        "\nnodeCount:${slotWriter.nodeCount}" +
        "\ngroupsAsString():\n" + slotWriter.groupsAsString()
before0
SlotWriter.toString():SlotWriter(current = 0 end=16 size = 16 gap=16-32)
nodeCountStack.size:0 nodeCountStack:
startStack.size:0 startStack:
endStack.size:0 endStack:
nodeCount:0
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^9: key=1815931685, nodes=1, dataAnchor=15, parentAnchor=9)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=16, parentAnchor=10)
Group(  12#1^11: key=125, nodes=0, dataAnchor=17, parentAnchor=11)
Group(  13#3^4: key=1815931958, nodes=1, dataAnchor=19, parentAnchor=4)
Group(  14#2^13: key=1546164276, nodes=1, dataAnchor=20, parentAnchor=13)
Group(  15#1^14: key=125, nodes=0, dataAnchor=21, parentAnchor=14)

0 = record { _, slots, _ -> slots.advanceBy(distance) }
10 進める

before1
SlotWriter.toString():SlotWriter(current = 10 end=16 size = 16 gap=16-32)
nodeCountStack.size:0 nodeCountStack:
startStack.size:0 startStack:
endStack.size:0 endStack:
nodeCount:0
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^9: key=1815931685, nodes=1, dataAnchor=15, parentAnchor=9)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=16, parentAnchor=10)
Group(  12#1^11: key=125, nodes=0, dataAnchor=17, parentAnchor=11)
Group(  13#3^4: key=1815931958, nodes=1, dataAnchor=19, parentAnchor=4)
Group(  14#2^13: key=1546164276, nodes=1, dataAnchor=20, parentAnchor=13)
Group(  15#1^14: key=125, nodes=0, dataAnchor=21, parentAnchor=14)



1 = private val startRootGroup: Change = { _, slots, _ -> slots.ensureStarted(0) }
ensureStartedGroupが始まったので、いろんなWriterのcurrentGroupなどを更新をかけるもの。で、引数が0なのでrootでやる。

before2
SlotWriter.toString():SlotWriter(current = 10 end=16 size = 16 gap=16-32)
nodeCountStack.size:1 nodeCountStack:0,
startStack.size:1 startStack:-1,
endStack.size:1 endStack:0,
nodeCount:2
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^9: key=1815931685, nodes=1, dataAnchor=15, parentAnchor=9)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=16, parentAnchor=10)
Group(  12#1^11: key=125, nodes=0, dataAnchor=17, parentAnchor=11)
Group(  13#3^4: key=1815931958, nodes=1, dataAnchor=19, parentAnchor=4)
Group(  14#2^13: key=1546164276, nodes=1, dataAnchor=20, parentAnchor=13)
Group(  15#1^14: key=125, nodes=0, dataAnchor=21, parentAnchor=14)


2 = recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
ensureStartedGroupが始まったので、いろんなWriterのcurrentGroupなどを更新をかけるもの。で、引数がanchorなのでcurrent groupでやる


before3
SlotWriter.toString():SlotWriter(current = 10 end=13 size = 16 gap=16-32)
nodeCountStack.size:2 nodeCountStack:0,2,
startStack.size:2 startStack:-1,0,
endStack.size:2 endStack:0,0,
nodeCount:1
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^9: key=1815931685, nodes=1, dataAnchor=15, parentAnchor=9)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=16, parentAnchor=10)
Group(  12#1^11: key=125, nodes=0, dataAnchor=17, parentAnchor=11)
Group(  13#3^4: key=1815931958, nodes=1, dataAnchor=19, parentAnchor=4)
Group(  14#2^13: key=1546164276, nodes=1, dataAnchor=20, parentAnchor=13)
Group(  15#1^14: key=125, nodes=0, dataAnchor=21, parentAnchor=14)

3 = private val removeCurrentGroupInstance: Change = { _, slots, rememberManager ->
slots.removeCurrentGroup(rememberManager)
}


before4
SlotWriter.toString():SlotWriter(current = 10 end=10 size = 13 gap=10-29)
nodeCountStack.size:2 nodeCountStack:0,2,
startStack.size:2 startStack:-1,0,
endStack.size:2 endStack:0,0,
nodeCount:0
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^4: key=1815931958, nodes=1, dataAnchor=-5, parentAnchor=4)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=-4, parentAnchor=-5)
Group(  12#1^11: key=125, nodes=0, dataAnchor=-3, parentAnchor=-4)

4 = { _, slots, _ -> slots.endGroup() }

before5
SlotWriter.toString():SlotWriter(current = 10 end=13 size = 13 gap=10-29)
nodeCountStack.size:1 nodeCountStack:0,
startStack.size:1 startStack:-1,
endStack.size:1 endStack:0,
nodeCount:1
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#12^0: key=1000, nodes=1, dataAnchor=1, parentAnchor=0)
Group(   2#11^1: key=200, nodes=1, dataAnchor=1, parentAnchor=1)
Group(   3#10^2: key=-985533281, nodes=1, dataAnchor=2, parentAnchor=2)
Group(   4#9^3: key=-337788286, nodes=1, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#1^4: key=-337788139, nodes=0, dataAnchor=15, parentAnchor=4)
Group(  10#3^4: key=1815931958, nodes=1, dataAnchor=-5, parentAnchor=4)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=-4, parentAnchor=-5)
Group(  12#1^11: key=125, nodes=0, dataAnchor=-3, parentAnchor=-4)

5 = { applier, _, _ -> applier.remove(removeIndex, count) }

before6
SlotWriter.toString():SlotWriter(current = 10 end=13 size = 13 gap=10-29)
nodeCountStack.size:1 nodeCountStack:0,
startStack.size:1 startStack:-1,
endStack.size:1 endStack:0,
nodeCount:1
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#12^0: key=1000, nodes=1, dataAnchor=1, parentAnchor=0)
Group(   2#11^1: key=200, nodes=1, dataAnchor=1, parentAnchor=1)
Group(   3#10^2: key=-985533281, nodes=1, dataAnchor=2, parentAnchor=2)
Group(   4#9^3: key=-337788286, nodes=1, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#1^4: key=-337788139, nodes=0, dataAnchor=15, parentAnchor=4)
Group(  10#3^4: key=1815931958, nodes=1, dataAnchor=-5, parentAnchor=4)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=-4, parentAnchor=-5)
Group(  12#1^11: key=125, nodes=0, dataAnchor=-3, parentAnchor=-4)

6 = { _, slots, _ -> slots.advanceBy(distance) }// (distance = 3)

before7
SlotWriter.toString():SlotWriter(current = 13 end=13 size = 13 gap=10-29)
nodeCountStack.size:1 nodeCountStack:0,
startStack.size:1 startStack:-1,
endStack.size:1 endStack:0,
nodeCount:1
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#12^0: key=1000, nodes=1, dataAnchor=1, parentAnchor=0)
Group(   2#11^1: key=200, nodes=1, dataAnchor=1, parentAnchor=1)
Group(   3#10^2: key=-985533281, nodes=1, dataAnchor=2, parentAnchor=2)
Group(   4#9^3: key=-337788286, nodes=1, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#1^4: key=-337788139, nodes=0, dataAnchor=15, parentAnchor=4)
Group(  10#3^4: key=1815931958, nodes=1, dataAnchor=-5, parentAnchor=4)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=-4, parentAnchor=-5)
Group(  12#1^11: key=125, nodes=0, dataAnchor=-3, parentAnchor=-4)

7 = { _, slots, _ -> slots.endGroup() }

after end
SlotWriter.toString():SlotWriter(current = 13 end=13 size = 13 gap=10-29)
nodeCountStack.size:0 nodeCountStack:
startStack.size:0 startStack:
endStack.size:0 endStack:
nodeCount:-1
groupsAsString():
Group(   0#13^-1: key=100, nodes=1, dataAnchor=0, parentAnchor=-1)
Group(   1#12^0: key=1000, nodes=1, dataAnchor=1, parentAnchor=0)
Group(   2#11^1: key=200, nodes=1, dataAnchor=1, parentAnchor=1)
Group(   3#10^2: key=-985533281, nodes=1, dataAnchor=2, parentAnchor=2)
Group(   4#9^3: key=-337788286, nodes=1, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#1^4: key=-337788139, nodes=0, dataAnchor=15, parentAnchor=4)
Group(  10#3^4: key=1815931958, nodes=1, dataAnchor=-5, parentAnchor=4)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=-4, parentAnchor=-5)
Group(  12#1^11: key=125, nodes=0, dataAnchor=-3, parentAnchor=-4)

予想通りではありますが、

3 = private val removeCurrentGroupInstance: Change = { _, slots, rememberManager ->
slots.removeCurrentGroup(rememberManager)
}

で激しい動きをします。

なので、途中まで以下は読んじゃいましたが、 slots.removeCurrentGroup(rememberManager)を読んでいきましょう。


changes[0] = SlotTable.advancedBy(10)

前のフェーズで記録されていた操作を一個ずつ行っていきます。

record { _, slots, _ -> slots.advanceBy(distance) }

Recomposer.runRecomposeAndApplyChanges()
└── CompositionImpl.applyChanges()
    └── *SlotTable.advancedBy(10)* ← Now reading

currentGroupというカーソルみたいなものがあって10進めるみたい。

    /**
     * Advance [currentGroup] by [amount]. The [currentGroup] group cannot be advanced outside the
     * currently started [parent].
     */
    fun advanceBy(amount: Int) {
        require(amount >= 0) { "Cannot seek backwards" }
        check(insertCount <= 0) { "Cannot call seek() while inserting" }
// currentGroupは0 amountは10なので、indexは10になる。
        val index = currentGroup + amount
        @Suppress("ConvertTwoComparisonsToRangeCheck")
        runtimeCheck(index >= parent && index <= currentGroupEnd) {
            "Cannot seek outside the current group ($parent-$currentGroupEnd)"
        }
// currentGroupは10になる。
        this.currentGroup = index
        val newSlot = groups.dataIndex(groupIndexToAddress(index))
// currentSlotは15。currentSlotEndも15。
        this.currentSlot = newSlot
        this.currentSlotEnd = newSlot
    }
slotTable:
androidx.compose.runtime.SlotTable@5935541
Group(0) key=100, nodes=2, size=16, slots=[0: {}]
 Group(1) key=1000, nodes=2, size=15
  Group(2) key=200, nodes=2, size=14 objectKey=OpaqueKey(key=provider)
   Group(3) key=-985533281, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.internal.ComposableLambdaImpl@3b52827]
    Group(4) key=-337788286, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@b882ad4]
     Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=false)@167707773]
     Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=false)@167707773, Function2<kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object>]
     Group(7) key=1036442245, nodes=0, size=2 aux=C(LaunchedEffect)P(1)336@14101L58:Effects.kt#9igjgp
      Group(8) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[13: kotlin.Unit, androidx.compose.runtime.LaunchedEffectImpl@8d3f428]
     Group(9) key=-337788139, nodes=1, size=4
// ↓ ここがcurrentGroup currentSlotは15。currentSlotEndも15。
      Group(10) key=1815931685, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@a4ed0be]
       Group(11) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp
        Group(12) key=125, nodes=0, size=1 node=Node1(name=node1), slots=[18: node1]
     Group(13) key=1815931958, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@81cf51f]
      Group(14) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp
       Group(15) key=125, nodes=0, size=1 node=Node2(name=node2), slots=[22: node2]

changes[1] = { _, slots, _ -> slots.ensureStarted(0) }

private val startRootGroup: Change = { _, slots, _ -> slots.ensureStarted(0) }

SlotWriter
    /**
     * If the start of a group was skipped using [skip], calling [ensureStarted] puts the writer
     * into the same state as if [startGroup] or [startNode] was called on the group starting at
     * [index]. If, after starting, the group, [currentGroup] is not a the end of the group or
     * [currentGroup] is not at the start of a group for which [index] is not location the parent
     * group, an exception is thrown.
     *
     * Calling [ensureStarted] implies that an [endGroup] should be called once the end of the
     * group is reached.
     * [skip]を使ってグループの線っとうがスキップされた場合、[ensureStarted]を呼んでwriterを同じ状態にする。
     * どのような状態かというとindexでグループの開始で[startGroup] or [startNode]が呼ばれたかのような状態。
     * 開始後に[currentGroup]がgroupの終わりか、[index]が[currentGroup]の親グループのロケーションでない時に例外を投げる
     */
    fun ensureStarted(index: Int) {
        require(insertCount <= 0) { "Cannot call ensureStarted() while inserting" }
        val parent = parent
// parentは-1が入っている
// indexは0
        if (parent != index) {
            // The new parent a child of the current group.
            @Suppress("ConvertTwoComparisonsToRangeCheck")
            require(index >= parent && index < currentGroupEnd) {
                "Started group must be a subgroup of the group at $parent"
            }

            val oldCurrent = currentGroup
            val oldCurrentSlot = currentSlot
            val oldCurrentSlotEnd = currentSlotEnd
// currentGroupが10から0に戻される
            currentGroup = index
            startGroup()
            currentGroup = oldCurrent
            currentSlot = oldCurrentSlot
            currentSlotEnd = oldCurrentSlotEnd
        }
    }

    fun startGroup() {
        require(insertCount == 0) { "Key must be supplied when inserting" }
        startGroup(key = 0, objectKey = Composer.Empty, isNode = false, aux = Composer.Empty)
    }

startGroup()

Recomposer.runRecomposeAndApplyChanges()
└── CompositionImpl.applyChanges()
    ├── SlotWriter.advancedBy(10)
    └── SlotWriter.ensureStarted(0)
        └── startGroup()
            └── *startGroup(key=0, objectKey=Empty, isNode=false, aux=Empty)* ← Now reading
    private fun startGroup(key: Int, objectKey: Any?, isNode: Boolean, aux: Any?) {
        val inserting = insertCount > 0
        nodeCountStack.push(nodeCount)

        currentGroupEnd = if (inserting) {
            insertGroups(1)
            val current = currentGroup
            val currentAddress = groupIndexToAddress(current)
            val hasObjectKey = objectKey !== Composer.Empty
            val hasAux = !isNode && aux !== Composer.Empty
            groups.initGroup(
                address = currentAddress,
                key = key,
                isNode = isNode,
                hasDataKey = hasObjectKey,
                hasData = hasAux,
                parentAnchor = parent,
                dataAnchor = currentSlot
            )
            currentSlotEnd = currentSlot

            val dataSlotsNeeded = (if (isNode) 1 else 0) +
                (if (hasObjectKey) 1 else 0) +
                (if (hasAux) 1 else 0)
            if (dataSlotsNeeded > 0) {
                insertSlots(dataSlotsNeeded, current)
                val slots = slots
                var currentSlot = currentSlot
                if (isNode) slots[currentSlot++] = aux
                if (hasObjectKey) slots[currentSlot++] = objectKey
                if (hasAux) slots[currentSlot++] = aux
                this.currentSlot = currentSlot
            }
            nodeCount = 0
            val newCurrent = current + 1
            this.parent = current
            this.currentGroup = newCurrent
            newCurrent
        } else {
            val previousParent = parent
            startStack.push(previousParent)
            saveCurrentGroupEnd()
            val currentGroup = currentGroup
            val currentGroupAddress = groupIndexToAddress(currentGroup)
            if (aux != Composer.Empty) {
                if (isNode)
                    updateNode(aux)
                else
                    updateAux(aux)
            }
            currentSlot = groups.slotIndex(currentGroupAddress)
            currentSlotEnd = groups.dataIndex(
                groupIndexToAddress(this.currentGroup + 1)
            )
            nodeCount = groups.nodeCount(currentGroupAddress)

            this.parent = currentGroup
            this.currentGroup = currentGroup + 1
            currentGroup + groups.groupSize(currentGroupAddress)
        }
    }

changes[3] = slots.removeCurrentGroup(rememberManager)

以下の状態変化がどのように起こるのか見ていきましょう。

before3
SlotWriter.toString():SlotWriter(current = 10 end=13 size = 16 gap=16-32)
nodeCountStack.size:2 nodeCountStack:0,2,
startStack.size:2 startStack:-1,0,
endStack.size:2 endStack:0,0,
nodeCount:1
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^9: key=1815931685, nodes=1, dataAnchor=15, parentAnchor=9)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=16, parentAnchor=10)
Group(  12#1^11: key=125, nodes=0, dataAnchor=17, parentAnchor=11)
Group(  13#3^4: key=1815931958, nodes=1, dataAnchor=19, parentAnchor=4)
Group(  14#2^13: key=1546164276, nodes=1, dataAnchor=20, parentAnchor=13)
Group(  15#1^14: key=125, nodes=0, dataAnchor=21, parentAnchor=14)

3 = private val removeCurrentGroupInstance: Change = { _, slots, rememberManager ->
slots.removeCurrentGroup(rememberManager)
}


before4
SlotWriter.toString():SlotWriter(current = 10 end=10 size = 13 gap=10-29)
nodeCountStack.size:2 nodeCountStack:0,2,
startStack.size:2 startStack:-1,0,
endStack.size:2 endStack:0,0,
nodeCount:0
groupsAsString():
Group(   0#16^-1: key=100, nodes=2, dataAnchor=0, parentAnchor=-1)
Group(   1#15^0: key=1000, nodes=2, dataAnchor=1, parentAnchor=0)
Group(   2#14^1: key=200, nodes=2, dataAnchor=1, parentAnchor=1)
Group(   3#13^2: key=-985533281, nodes=2, dataAnchor=2, parentAnchor=2)
Group(   4#12^3: key=-337788286, nodes=2, dataAnchor=4, parentAnchor=3)
Group(   5#1^4: key=-3687241, nodes=0, dataAnchor=6, parentAnchor=4)
Group(   6#1^4: key=-3686930, nodes=0, dataAnchor=8, parentAnchor=4)
Group(   7#2^4: key=1036442245, nodes=0, dataAnchor=11, parentAnchor=4)
Group(   8#1^7: key=-3686930, nodes=0, dataAnchor=12, parentAnchor=7)
Group(   9#4^4: key=-337788139, nodes=1, dataAnchor=15, parentAnchor=4)
Group(  10#3^4: key=1815931958, nodes=1, dataAnchor=-5, parentAnchor=4)
Group(  11#2^10: key=1546164276, nodes=1, dataAnchor=-4, parentAnchor=-5)
Group(  12#1^11: key=125, nodes=0, dataAnchor=-3, parentAnchor=-4)
Composer.kt
internal fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
    // Notify the lifecycle manager of any observers leaving the slot table
    // The notification order should ensure that listeners are notified of leaving
    // in opposite order that they are notified of entering.

    // To ensure this order, we call `enters` as a pre-order traversal
    // of the group tree, and then call `leaves` in the inverse order.
// まず、変更があれば、pendingInvalidScopesをtrueにするのと、Rememberがそこにいればそれを忘れるということを行います。
    for (slot in groupSlots()) {
        @Suppress("DEPRECATION")
        when (slot) {
            is RememberObserver -> {
                rememberManager.forgetting(slot)
            }
            is RecomposeScopeImpl -> {
// ここに1度だけ入り、`pendingInvalidScopes = true`をtrueにして、
// RecomposeScopeImplがもつcompositionをnullにします。
                val composition = slot.composition
                if (composition != null) {
                    composition.pendingInvalidScopes = true
                    slot.composition = null
                }
            }
        }
    }

    removeGroup()
}

    /**
     * Returns an iterator for all the slots of group and all the children of the group.
     */
    fun groupSlots(): Iterator<Any?> {
        val start = groups.dataIndex(groupIndexToAddress(currentGroup))
        val end = groups.dataIndex(
            groupIndexToAddress(currentGroup + groupSize(currentGroup))
        )
        return object : Iterator<Any?> {
            var current = start
            override fun hasNext(): Boolean = current < end
            override fun next(): Any? =
                if (hasNext()) slots[dataIndexToDataAddress(current++)] else null
        }
    }
Recomposer.runRecomposeAndApplyChanges()
└── CompositionImpl.applyChanges()
    ├── SlotWriter.advancedBy(10)
    ├── SlotWriter.ensureStarted(0)
    │   └── SlotWriter.startGroup()
    │       └── SlotWriter.startGroup(key=0, objectKey=Empty, isNode=false, aux=Empty)
    │           ├── SlotWriter.saveCurrentGroupEnd()
    │           ├── SlotWriter.groupIndexToAddress(0)
    │           └── IntArray.slotIndex(0)
    │               ├── IntArray.slotAnchor(0)
    │               └── SlotWriter.dataAnchorToDataIndex(0)
    ├── ...
    └── SlotWriter.removeCurrentGroup()
        └── SlotWriter.removeGroup()
            ├── SlotWriter.skipGroup()
            └── SlotWriter.removeGroups(10, 3)
                └── *moveGroupGapTo(10)* ← Now reading

以下でデバッグ
"<- group $gapStart -><- gap $gapLen -><- group ${capacity-gapStart-gapLen} ->"

元はgroupsの中身はこれになっていて、
<- group 16 -><- gap 16 -><- group 0 ->
以下の関数を通過するとgroupsは以下のようになる
<- group 10 -><- gap 16 -><- group 6 ->

    /**
     * Move the gap in [groups] to [index].
     * groupsにあるgapをindexに移動する。
     */
    private fun moveGroupGapTo(index: Int) {
        val gapLen = groupGapLen
        val gapStart = groupGapStart
// gapStart = 16
// index = 10
        if (gapStart != index) {
            if (anchors.isNotEmpty()) updateAnchors(gapStart, index)
            if (gapLen > 0) {
// gapLen = 16
                val groups = groups
                // Here physical is used to mean an index of the actual first int of the group in the
                // array as opposed ot the logical address which is in groups of Group_Field_Size
                // integers. IntArray.copyInto expects physical indexes.
                val groupPhysicalAddress = index * Group_Fields_Size
                val groupPhysicalGapLen = gapLen * Group_Fields_Size
                val groupPhysicalGapStart = gapStart * Group_Fields_Size
                if (index < gapStart) {
// gapStart = 16
// index = 10
                    groups.copyInto(
                        destination = groups,
// groupPhysicalAddress + groupPhysicalGapLen = 130
                        destinationOffset = groupPhysicalAddress + groupPhysicalGapLen,
// groupPhysicalAddress = 50
                        startIndex = groupPhysicalAddress,
// groupPhysicalGapStart = 80
                        endIndex = groupPhysicalGapStart
                    )
// つまりgroup 11 - 13をgroupsの一番後ろに移動する。
                } else {
                    groups.copyInto(
                        destination = groups,
                        destinationOffset = groupPhysicalGapStart,
                        startIndex = groupPhysicalGapStart + groupPhysicalGapLen,
                        endIndex = groupPhysicalAddress + groupPhysicalGapLen
                    )
                }
            }

            // Gap has moved so the anchor for the groups that moved have changed so the parent
            // anchors that refer to these groups must be updated.
// Gapが動いたので、動いたgroupのanchorとparent anchorはアップデートされる。
// 確かに前後比較で見るとdataAnchorが移動していることが分かる。
            var groupAddress = if (index < gapStart) index + gapLen else gapStart
            val capacity = capacity
            runtimeCheck(groupAddress < capacity)
            while (groupAddress < capacity) {
                val oldAnchor = groups.parentAnchor(groupAddress)
                val oldIndex = parentAnchorToIndex(oldAnchor)
                val newAnchor = parentIndexToAnchor(oldIndex, index)
                if (newAnchor != oldAnchor) {
                    groups.updateParentAnchor(groupAddress, newAnchor)
                }
                groupAddress++
                if (groupAddress == index) groupAddress += gapLen
            }
        }
        this.groupGapStart = index
    }

そして戻ったremoveGroups()では。。

Recomposer.runRecomposeAndApplyChanges()
└── CompositionImpl.applyChanges()
    ├── SlotWriter.advancedBy(10)
    ├── SlotWriter.ensureStarted(0)
       └── SlotWriter.startGroup()
           └── SlotWriter.startGroup(key=0, objectKey=Empty, isNode=false, aux=Empty)
               ├── SlotWriter.saveCurrentGroupEnd()
               ├── SlotWriter.groupIndexToAddress(0)
               └── IntArray.slotIndex(0)
                   ├── IntArray.slotAnchor(0)
                   └── SlotWriter.dataAnchorToDataIndex(0)
    ├── ...
    └── SlotWriter.removeCurrentGroup()
        └── SlotWriter.removeGroup()
            ├── SlotWriter.skipGroup()
            └── *SlotWriter.removeGroups(10, 3)*  Now reading
                └── moveGroupGapTo(10)

    /**
     * Remove [len] group from [start].
     */
    private fun removeGroups(start: Int, len: Int): Boolean {
        return if (len > 0) {
            var anchorsRemoved = false
            val anchors = anchors

            // Move the gap to start of the removal and grow the gap
            moveGroupGapTo(start)
            if (anchors.isNotEmpty()) anchorsRemoved = removeAnchors(start, len)
            groupGapStart = start
            val previousGapLen = groupGapLen
            val newGapLen = previousGapLen + len
            groupGapLen = newGapLen

            // Adjust the gap owner if necessary.
            val slotsGapOwner = slotsGapOwner
            if (slotsGapOwner > start) {
                this.slotsGapOwner = slotsGapOwner - len
            }
            if (currentGroupEnd >= groupGapStart) currentGroupEnd -= len
            anchorsRemoved
        } else false
    }

これが終わった後、以下のようになります。
groupsにデータは入っているけど、それが無視される形になります。

<- group 10 -><- gap 19 -><- group 3 ->

書き込みが終わった後にどうなるか?

Gapが今以下のようになっていますが、これを一旦gapを後ろに戻すことで次のrecomposeに備えます。

<- group 10 -><- gap 19 -><- group 3 ->

<- group 13 -><- gap 19 -><- group 0 ->

SlotTable
    /**
     * Write to the slot table in [block]. Only one writer can be created for a slot table at a
     * time and all readers must be closed an do readers can be created while the slot table is
     * being written to.
     *
     * @see SlotWriter
     */
    inline fun <T> write(block: (writer: SlotWriter) -> T): T = openWriter()
        .let { writer ->
            try {
                block(writer)
            } finally {
                writer.close()
            }
        }
SlotWriter
    /**
     * Close the writer
     */
    fun close() {
        closed = true
        // Ensure, for readers, there is no gap
        moveGroupGapTo(size)
        moveSlotGapTo(slots.size - slotsGapLen, groupGapStart)
        table.close(
            writer = this,
            groups = groups,
            groupsSize = groupGapStart,
            slots = slots,
            slotsSize = slotsGapStart,
            anchors = anchors
        )
    }
SlotTable

    /**
     * Close [writer] and adopt the slot arrays returned. The [SlotTable] is invalid until
     * [SlotWriter.close] is called as the [SlotWriter] is modifying [groups] and [slots]
     * directly and will only make copies of the arrays if the slot table grows.
     */
    internal fun close(
        writer: SlotWriter,
        groups: IntArray,
        groupsSize: Int,
        slots: Array<Any?>,
        slotsSize: Int,
        anchors: ArrayList<Anchor>
    ) {
        require(writer.table === this && this.writer) { "Unexpected writer close()" }
        this.writer = false
        setTo(groups, groupsSize, slots, slotsSize, anchors)
    }

    /**
     * Used internally by [SlotWriter.moveFrom] to swap arrays with a slot table target
     * [SlotTable] is empty.
     */
    internal fun setTo(
        groups: IntArray,
        groupsSize: Int,
        slots: Array<Any?>,
        slotsSize: Int,
        anchors: ArrayList<Anchor>
    ) {
        // Adopt the slots from the writer
        this.groups = groups
        this.groupsSize = groupsSize
        this.slots = slots
        this.slotsSize = slotsSize
        this.anchors = anchors
    }
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1