の続きです。
今度まとめた記事を書く予定なので、この記事は読まなくていいです。
前回までで、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を一つずつ行っていきます。
内容は前回の記事から。
まずはこのadvancebyになるのかな?
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ごとの動きを見てみる
ちょっと普通に見ていくとけっこう大変なので、どのあたりを読むべきかのあたりをつけるために、デバッガーで動きを見てみます。
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) }
/**
* 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)
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 ->
/**
* 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()
}
}
/**
* 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
)
}
/**
* 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
}