2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Jetpack ComposeのComposable関数のdoCompose()でノードが消えたときにどう動くのかのコードリーディング

Last updated at Posted at 2021-09-23

https://qiita.com/takahirom/items/0e72bee081de8cf4f05f
の続きのstateが書き換わってからの動きを追っています。

@Composable
fun Content() {
    var state by remember { mutableStateOf(true) }
    LaunchedEffect(Unit) {
        delay(12000)
        state = false
    }
    if (state) {
        Node1()
    }
    Node2()
}

自分用のコードリーディイングメモ記事で、もっとわかりやすいのを後で出すと思います。

以下の中で3:の部分を読んでいきます。3の途中までになります。

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の書き換えが走る。 (予想。コード未読)

Content() startRestartGroup()

まずContent()のデコンパイル後のコード
挫けそうになりますが、やっていきます
このfinal int $changedには引数には渡された1が入っています。
composerには呼び元のComposerImplが入ってます。

Content()
   @Composable
   public static final void Content(@Nullable Composer $composer, final int $changed) {
      $composer = $composer.startRestartGroup(-337788314);
      ComposerKt.sourceInformation($composer, "C(Content)");
      if ($changed == 0 && $composer.getSkipping()) {
         $composer.skipToGroupEnd();
      } else {
         int $changed$iv = false;
         int $i$f$remember = false;
         $composer.startReplaceableGroup(-3687241);
         ComposerKt.sourceInformation($composer, "C(remember):Composables.kt#9igjgp");
         boolean invalid$iv$iv = false;
         int $i$f$cache = false;
         Object var8 = $composer.rememberedValue();
         boolean var9 = false;
         boolean var10 = false;
         int var12 = false;
         boolean var13;
         Object var10000;
         if (var8 == Composer.Companion.getEmpty()) {
            var13 = false;
            MutableState var14 = SnapshotStateKt.mutableStateOf$default(true, (SnapshotMutationPolicy)null, 2, (Object)null);
            $composer.updateRememberedValue(var14);
            var10000 = var14;
         } else {
            var10000 = var8;
         }

         Object var15 = var10000;
         $composer.endReplaceableGroup();
         final MutableState state$delegate = (MutableState)var15;
         Unit var19 = Unit.INSTANCE;
         $changed$iv = true;
         $i$f$remember = false;
         $composer.startReplaceableGroup(-3686930);
         ComposerKt.sourceInformation($composer, "C(remember)P(1):Composables.kt#9igjgp");
         invalid$iv$iv = $composer.changed(state$delegate);
         $i$f$cache = false;
         var8 = $composer.rememberedValue();
         var9 = false;
         var10 = false;
         var12 = false;
         Object var10001;
         if (!invalid$iv$iv && var8 != Composer.Companion.getEmpty()) {
            var10001 = var8;
         } else {
            Unit var16 = var19;
            var13 = false;
            Function2 var17 = (Function2)(new Function2((Continuation)null) {
               int label;

               @Nullable
               public final Object invokeSuspend(@NotNull Object $result) {
                  Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  switch(this.label) {
                  case 0:
                     ResultKt.throwOnFailure($result);
                     Continuation var10001 = (Continuation)this;
                     this.label = 1;
                     if (DelayKt.delay(10000L, var10001) == var2) {
                        return var2;
                     }
                     break;
                  case 1:
                     ResultKt.throwOnFailure($result);
                     break;
                  default:
                     throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                  }

                  MainKt.Content$lambda-3(state$delegate, false);
                  return Unit.INSTANCE;
               }

               @NotNull
               public final Continuation create(@Nullable Object value, @NotNull Continuation $completion) {
                  return (Continuation)(new <anonymous constructor>($completion));
               }

               @Nullable
               public final Object invoke(@NotNull CoroutineScope p1, @Nullable Continuation p2) {
                  return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
               }
            });
            var19 = var16;
            $composer.updateRememberedValue(var17);
            var10001 = var17;
         }

         var15 = var10001;
         $composer.endReplaceableGroup();
         EffectsKt.LaunchedEffect(var19, (Function2)var15, $composer, 0);
         $composer.startReplaceableGroup(-337788167);
         if (Content$lambda-2(state$delegate)) {
            Node1((String)null, $composer, 0, 1);
         }

         $composer.endReplaceableGroup();
         Node2((String)null, $composer, 0, 1);
      }

      ScopeUpdateScope var18 = $composer.endRestartGroup();
      if (var18 != null) {
         var18.updateScope((Function2)(new Function2() {
            public final void invoke(@Nullable Composer $composer, int $force) {
               MainKt.Content($composer, $changed | 1);
            }
         }));
      }

   }

startRestartGroup()

start()とaddRecomposeScope()が呼ばれる。

    /**
     * Start a restart group. A restart group creates a recompose scope and sets it as the current
     * recompose scope of the composition. If the recompose scope is invalidated then this group
     * will be recomposed. A recompose scope can be invalidated by calling invalidate on the object
     * returned by [androidx.compose.runtime.currentRecomposeScope].
     * restart groupをスタートする。
     * restart groupはrecompose scopeを作り、compositionの今のrecompose scopeとして作ったrecompose scopeをセットする。
     * recompose scopeが無効(invalidated)になっていたら、groupはrecomposeされる。
     * recompose scopeはcurrentRecomposeScope()の呼び出し結果のinvalidate()を呼ぶことで無効にできる
     */
    @ComposeCompilerApi
    override fun startRestartGroup(key: Int): Composer {
        start(key, null, false, null)
        addRecomposeScope()
        return this
    }
Content()
└── *startRestartGroup()* ← Now reading
    ├── start()
    └── addRecomposeScope()

このstart()は一つ前の記事のstart()と同じ。処理の内容は以下

Content()
└── startRestartGroup()
    ├── *start()* ← reading
    └── addRecomposeScope()

1: 基本的にはReaderの現在のcurrentGroupとキーを比較

キーが同じである事がわかる

      $composer = $composer.startRestartGroup(-337788314);
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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@716a3a3, androidx.compose.runtime.internal.ComposableLambdaImpl@d9186a0]
↓ ここと比較!!!
    Group(4) key=-337788314, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@dbb4259]
     Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=true)@13143582]
     Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=true)@13143582, 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@8642d5d]
     Group(9) key=-337788167, nodes=1, size=4
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@6986ccc]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@1d4ab15]
      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]

2: キーが同じであればSlotReaderを一つ進める

以下でデバッガーで前後比較する

reader.run {
    "SlotReader(current = $currentGroup, groupKey = $groupKey, groupEnd = $groupEnd )"
}

SlotReader(current = 4, groupKey = -337788314, groupEnd = 16 )

SlotReader(current = 5, groupKey = -3687241, groupEnd = 16 )
になり、一つ進む

そして enterGroup(isNode, newPending)
によってComposer側の状態も更新される。

addRecomposeScope()

Content()
└── startRestartGroup()
    ├── start()
    └── *addRecomposeScope()* ← Now reading
ComposerImpl
    private fun addRecomposeScope() {
// insertingはfalseになる
        if (inserting) {
            val scope = RecomposeScopeImpl(composition as CompositionImpl)
            invalidateStack.push(scope)
            updateValue(scope)
            scope.start(snapshot.id)
        } else {
            val invalidation = invalidations.removeLocation(reader.parent)
            val scope = reader.next() as RecomposeScopeImpl
            scope.requiresRecompose = invalidation != null
            invalidateStack.push(scope)
            scope.start(snapshot.id)
        }
    }

invalidateStackのpush後は以下

image.png

invalidateStackはendRestartGroup()のときに取り出して使うみたい。

start()を呼ぶと以下のようになり、このスコープでcompositionが始まるときに呼ばれる関数ということが書いてあります。

Content()
└── startRestartGroup()
    ├── start()
    └── addRecomposeScope()
        └── *RecomposeScopeImpl.start()* ← Now reading
RecomposeScopeImpl
    /**
     * Called when composition start composing into this scope. The [token] is a value that is
     * unique everytime this is called. This is currently the snapshot id but that shouldn't be
     * relied on.
     */
    fun start(token: Int) {
        currentToken = token
        skipped = false
    }

Content()

まず、$changedは固定の1が渡されたので、 getSkipping()は呼ばれない。
そしてまたstartReplaceableGroup()によってGroupの開始が呼ばれる。。

   @Composable
   public static final void Content(@Nullable Composer $composer, final int $changed) {
      $composer = $composer.startRestartGroup(-337788314);
      ComposerKt.sourceInformation($composer, "C(Content)");
      if ($changed == 0 && $composer.getSkipping()) {
         $composer.skipToGroupEnd();
      } else {
         int $changed$iv = false;
         int $i$f$remember = false;
         $composer.startReplaceableGroup(-3687241);
         ComposerKt.sourceInformation($composer, "C(remember):Composables.kt#9igjgp");
         boolean invalid$iv$iv = false;
         int $i$f$cache = false;
         Object var8 = $composer.rememberedValue();
         boolean var9 = false;
         boolean var10 = false;
         int var12 = false;
         boolean var13;
         Object var10000;
         if (var8 == Composer.Companion.getEmpty()) {
            var13 = false;
            MutableState var14 = SnapshotStateKt.mutableStateOf$default(true, (SnapshotMutationPolicy)null, 2, (Object)null);
            $composer.updateRememberedValue(var14);
            var10000 = var14;
         } else {
            var10000 = var8;
         }

SlotReaderの以下の比較が行われるんだろうなと思いはしつつ、startRestartGroup()との違いに注目すると、addRecomposeScopeがあるかないかの違いになります。なので基本的に今のSlotTableと比較して一個currentGroupを進めるだけですね。

    @ComposeCompilerApi
    override fun startRestartGroup(key: Int): Composer {
        start(key, null, false, null)
        addRecomposeScope()
        return this
    }
    
    override fun startReplaceableGroup(key: Int) = start(key, null, false, null)
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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@716a3a3, androidx.compose.runtime.internal.ComposableLambdaImpl@d9186a0]
    Group(4) key=-337788314, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@dbb4259]
↓ ここと比較!!!
     Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=true)@13143582]
     Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=true)@13143582, 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@8642d5d]
     Group(9) key=-337788167, nodes=1, size=4
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@6986ccc]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@1d4ab15]
      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]

rememberedValue()、 nextSlot()

   @Composable
   public static final void Content(@Nullable Composer $composer, final int $changed) {
      $composer = $composer.startRestartGroup(-337788314);
      ComposerKt.sourceInformation($composer, "C(Content)");
      if ($changed == 0 && $composer.getSkipping()) {
         $composer.skipToGroupEnd();
      } else {
         int $changed$iv = false;
         int $i$f$remember = false;
         $composer.startReplaceableGroup(-3687241);
         ComposerKt.sourceInformation($composer, "C(remember):Composables.kt#9igjgp");
         boolean invalid$iv$iv = false;
         int $i$f$cache = false;
         Object var8 = $composer.rememberedValue(); // **←**
         boolean var9 = false;
         boolean var10 = false;
         int var12 = false;
         boolean var13;
         Object var10000;
         if (var8 == Composer.Companion.getEmpty()) {
            var13 = false;
            MutableState var14 = SnapshotStateKt.mutableStateOf$default(true, (SnapshotMutationPolicy)null, 2, (Object)null);
            $composer.updateRememberedValue(var14);
            var10000 = var14;
         } else {
            var10000 = var8;
         }
    override fun rememberedValue(): Any? = nextSlot()
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
└── composer.rememberedValue()
    └── *composer.nextSlot()* ← Now reading

insertingとreusingはfalseで、itにreader.next()によって受け取ったMutableState()を受け取る。

ComposerImpl
    @PublishedApi
    @OptIn(InternalComposeApi::class)
    internal fun nextSlot(): Any? = if (inserting) {
        validateNodeNotExpected()
        Composer.Empty
    } else reader.next().let { if (reusing) Composer.Empty else it }

image.png

MutableState()がrememberedValue()によって返るので、 if (var8 == Composer.Companion.getEmpty())はfalseになり、

$composer.endReplaceableGroup();
が呼ばれる。

Content()
   @Composable
   public static final void Content(@Nullable Composer $composer, final int $changed) {
      $composer = $composer.startRestartGroup(-337788314);
      ComposerKt.sourceInformation($composer, "C(Content)");
      if ($changed == 0 && $composer.getSkipping()) {
         $composer.skipToGroupEnd();
      } else {
         int $changed$iv = false;
         int $i$f$remember = false;
         $composer.startReplaceableGroup(-3687241);
         ComposerKt.sourceInformation($composer, "C(remember):Composables.kt#9igjgp");
         boolean invalid$iv$iv = false;
         int $i$f$cache = false;
         Object var8 = $composer.rememberedValue();
         boolean var9 = false;
         boolean var10 = false;
         int var12 = false;
         boolean var13;
         Object var10000;
         if (var8 == Composer.Companion.getEmpty()) {
            var13 = false;
            MutableState var14 = SnapshotStateKt.mutableStateOf$default(true, (SnapshotMutationPolicy)null, 2, (Object)null);
            $composer.updateRememberedValue(var14);
            var10000 = var14;
         } else {
            var10000 = var8;
         }

         Object var15 = var10000;
         $composer.endReplaceableGroup();
         final MutableState state$delegate = (MutableState)var15;
         Unit var19 = Unit.INSTANCE;
         $changed$iv = true;
         $i$f$remember = false;
         $composer.startReplaceableGroup(-3686930);
         ComposerKt.sourceInformation($composer, "C(remember)P(1):Composables.kt#9igjgp");
         invalid$iv$iv = $composer.changed(state$delegate);
         $i$f$cache = false;
         var8 = $composer.rememberedValue();
         var9 = false;
         var10 = false;
         var12 = false;
         Object var10001;
// 以下は
         if (!invalid$iv$iv && var8 != Composer.Companion.getEmpty()) {
            var10001 = var8;
         } else {
            Unit var16 = var19;
            var13 = false;
            Function2 var17 = (Function2)(new Function2((Continuation)null) {
               int label;

               @Nullable
               public final Object invokeSuspend(@NotNull Object $result) {
                  Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  switch(this.label) {
                  case 0:
                     ResultKt.throwOnFailure($result);
                     Continuation var10001 = (Continuation)this;
                     this.label = 1;
                     if (DelayKt.delay(10000L, var10001) == var2) {
                        return var2;
                     }
                     break;
                  case 1:
                     ResultKt.throwOnFailure($result);
                     break;
                  default:
                     throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                  }

                  MainKt.Content$lambda-3(state$delegate, false);
                  return Unit.INSTANCE;
               }

               @NotNull
               public final Continuation create(@Nullable Object value, @NotNull Continuation $completion) {
                  return (Continuation)(new <anonymous constructor>($completion));
               }

               @Nullable
               public final Object invoke(@NotNull CoroutineScope p1, @Nullable Continuation p2) {
                  return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
               }
            });
            var19 = var16;
            $composer.updateRememberedValue(var17);
            var10001 = var17;
         }

endReplaceableGroup() 始めからendGroup()まで

長い。。やっていきます。

ComposerImpl
    @ComposeCompilerApi
    override fun endReplaceableGroup() = endGroup()
ComposerImpl
    private fun endGroup() = end(isNode = false)
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── *composer.end(isNode = false)* ← Now reading

まず、updateCompoundKeyWhenWeExitGroup()関連はSavedState関連なので今回は飛ばします。

ComposerImpl
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
// pendingがnullなので、以下はfalseになるのでif文に入らない。
        if (pending != null && pending.keyInfos.size > 0) {
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
        val removeIndex = nodeIndex
//reader.isGroupEnd == trueつまりcurrentGroup == currentEndなので、繰り返さない。
        while (!reader.isGroupEnd) {
            val startSlot = reader.currentGroup
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
// inserting = falseになる
        if (inserting) {
...
        } else {
//isNode = fasle
            if (isNode) recordUp()
// 以下では判定によりなにもしない。
            recordEndGroup()
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
// 以下も変化が今回はないので、色々飛ばされます。
            if (expectedNodeCount != parentNodeCount) {
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
// 動かなかったからか、なにもしない。
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── *composer.recordEndGroup()* ← Now reading

recordEndGroup()は判定がfalseになるので何もせずに終わる。

image.png

    private fun recordEndGroup() {
        val location = reader.parent
        val currentStartedGroup = startedGroups.peekOr(-1)
        runtimeCheck(currentStartedGroup <= location) { "Missed recording an endGroup" }
        if (startedGroups.peekOr(-1) == location) {
            startedGroups.pop()
            recordSlotTableOperation(change = endGroupInstance)
        }
    }
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordEndGroup()
            └── *SlotReader.endGroup()* ← Now reading
SlotReader
    /**
     * End the current group. Must be called after the corresponding [startGroup].
     */
    fun endGroup() {
// emptyCount=0なのでtrueになる
        if (emptyCount == 0) {
            require(currentGroup == currentEnd) { "endGroup() not called at the end of a group" }
            val parent = groups.parentAnchor(parent)
            this.parent = parent
// parent < 0はfalseになる parentは4になっている。
// groupSizeは12になる。
// 12がcurrentEndになる。
            currentEnd = if (parent < 0)
                groupsSize
            else
                parent + groups.groupSize(parent)
        }
    }

以下にデバッグ用のコード変えています。

reader.run {
    "SlotReader(current = $currentGroup, groupKey = $groupKey, currentEnd = $currentEnd )"
}

SlotReader(current = 6, groupKey = 0, currentEnd = 6 )が

SlotReader(current = 6, groupKey = -3686930, currentEnd = 16 )
に変化する。

exitGroup()

expectedNodeCount=0
inserting = falseで呼ばれる。

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordEndGroup()
            ├── SlotReader.endGroup()
            └── *composer.exitGroup(expectedNodeCount = 0, inserting = false)* ← Now reading

基本的にComposerの状態を合わせるのみ。

ComposerImpl
    private fun exitGroup(expectedNodeCount: Int, inserting: Boolean) {
        // Restore the parent's state updating them if they have changed based on changes in the
        // children. For example, if a group generates nodes then the number of generated nodes will
        // increment the node index and the group's node count. If the parent is tracking structural
        // changes in pending then restore that too.
        val previousPending = pendingStack.pop()
        if (previousPending != null && !inserting) {
            previousPending.groupIndex++
        }
        this.pending = previousPending
        this.nodeIndex = nodeIndexStack.pop() + expectedNodeCount
        this.groupNodeCount = this.groupNodeCountStack.pop() + expectedNodeCount
    }

composer.changed(state$delegate)

Content()

   @Composable
   public static final void Content(@Nullable Composer $composer, final int $changed) {
      $composer = $composer.startRestartGroup(-337788314);
      ComposerKt.sourceInformation($composer, "C(Content)");
      if ($changed == 0 && $composer.getSkipping()) {
         $composer.skipToGroupEnd();
      } else {
         int $changed$iv = false;
         int $i$f$remember = false;
         $composer.startReplaceableGroup(-3687241);
         ComposerKt.sourceInformation($composer, "C(remember):Composables.kt#9igjgp");
         boolean invalid$iv$iv = false;
         int $i$f$cache = false;
         Object var8 = $composer.rememberedValue();
         boolean var9 = false;
         boolean var10 = false;
         int var12 = false;
         boolean var13;
         Object var10000;
         if (var8 == Composer.Companion.getEmpty()) {
            var13 = false;
            MutableState var14 = SnapshotStateKt.mutableStateOf$default(true, (SnapshotMutationPolicy)null, 2, (Object)null);
            $composer.updateRememberedValue(var14);
            var10000 = var14;
         } else {
            var10000 = var8;
         }

         Object var15 = var10000;
         $composer.endReplaceableGroup();
         final MutableState state$delegate = (MutableState)var15;
         Unit var19 = Unit.INSTANCE;
         $changed$iv = true;
         $i$f$remember = false;
         $composer.startReplaceableGroup(-3686930);
         ComposerKt.sourceInformation($composer, "C(remember)P(1):Composables.kt#9igjgp");
         invalid$iv$iv = $composer.changed(state$delegate); // ← ここ
         $i$f$cache = false;
         var8 = $composer.rememberedValue();
         var9 = false;
         var10 = false;
         var12 = false;
         Object var10001;
         if (!invalid$iv$iv && var8 != Composer.Companion.getEmpty()) {
            var10001 = var8;
         } else {
            Unit var16 = var19;
            var13 = false;
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
└── *composer.changed(MutableState(value = false))* ← Now reading
    └── nextSlot()
ComposerImpl
    /**
     * Determine if the current slot table value is equal to the given value, if true, the value
     * is scheduled to be skipped during [ControlledComposition.applyChanges] and [changes] return
     * false; otherwise [ControlledComposition.applyChanges] will update the slot table to [value].
     * In either case the composer's slot table is advanced.
     *
     * @param value the value to be compared.
     */
    @ComposeCompilerApi
    override fun changed(value: Any?): Boolean {
        return if (nextSlot() != value) {
            updateValue(value)
            true
        } else {
            false
        }
    }

ここではfalseになる。
ちなみにnextSlot()でまたMutableStateを取り出して比較する。
image.png

つまり、if (!invalid$iv$iv && var8 != Composer.Companion.getEmpty())!invalid$iv$iv はtrueになる。

2回目のrememberedValue()ではSlotTableから値を取り出すのだが、LaunchedEffectで使われていたラムダが取り出される。

         var8 = $composer.rememberedValue();

image.png

つまり var8 != Composer.Companion.getEmpty()も trueになるので、else部分は今回は実行されない。

Content()
   @Composable
   public static final void Content(@Nullable Composer $composer, final int $changed) {
      $composer = $composer.startRestartGroup(-337788314);
      ComposerKt.sourceInformation($composer, "C(Content)");
      if ($changed == 0 && $composer.getSkipping()) {
         $composer.skipToGroupEnd();
      } else {
         int $changed$iv = false;
         int $i$f$remember = false;
         $composer.startReplaceableGroup(-3687241);
         ComposerKt.sourceInformation($composer, "C(remember):Composables.kt#9igjgp");
         boolean invalid$iv$iv = false;
         int $i$f$cache = false;
         Object var8 = $composer.rememberedValue();
         boolean var9 = false;
         boolean var10 = false;
         int var12 = false;
         boolean var13;
         Object var10000;
         if (var8 == Composer.Companion.getEmpty()) {
            var13 = false;
            MutableState var14 = SnapshotStateKt.mutableStateOf$default(true, (SnapshotMutationPolicy)null, 2, (Object)null);
            $composer.updateRememberedValue(var14);
            var10000 = var14;
         } else {
            var10000 = var8;
         }

         Object var15 = var10000;
         $composer.endReplaceableGroup();
         final MutableState state$delegate = (MutableState)var15;
         Unit var19 = Unit.INSTANCE;
         $changed$iv = true;
         $i$f$remember = false;
         $composer.startReplaceableGroup(-3686930);
         ComposerKt.sourceInformation($composer, "C(remember)P(1):Composables.kt#9igjgp");
         invalid$iv$iv = $composer.changed(state$delegate);
         $i$f$cache = false;
         var8 = $composer.rememberedValue();
         var9 = false;
         var10 = false;
         var12 = false;
         Object var10001;
         if (!invalid$iv$iv && var8 != Composer.Companion.getEmpty()) {
            var10001 = var8;
         } else {
... //ここは実行されない
         }

         var15 = var10001;
         $composer.endReplaceableGroup();
// 次はここだが、一旦これはスキップする。
         EffectsKt.LaunchedEffect(var19, (Function2)var15, $composer, 0);
// 始まる。
         $composer.startReplaceableGroup(-337788167);
// ここはfalseになる。
         if (state$delegate.getValue()) {
            Node1((String)null, $composer, 0, 1);
         }
// そして以下で変化が現れるはず!!!
         $composer.endReplaceableGroup();
         Node2((String)null, $composer, 0, 1);
      }

      ScopeUpdateScope var18 = $composer.endRestartGroup();
      if (var18 != null) {
         var18.updateScope((Function2)(new Function2() {
            public final void invoke(@Nullable Composer $composer, int $force) {
               MainKt.Content($composer, $changed | 1);
            }
         }));
      }

   }

そしてついにnodeの削除の変更が見れます

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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@716a3a3, androidx.compose.runtime.internal.ComposableLambdaImpl@d9186a0]
    Group(4) key=-337788314, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@dbb4259]
     Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=true)@13143582]
     Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=true)@13143582, Function2<kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object>]
↓ おそらくここはLaunchedEffectが進めてくれている。
     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@8642d5d]
↓ ここと比較!!!
     Group(9) key=-337788167, nodes=1, size=4
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@6986ccc]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@1d4ab15]
      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]

Nodeが消えたときのendReplaceableGroup()

         EffectsKt.LaunchedEffect(var19, (Function2)var15, $composer, 0);
         $composer.startReplaceableGroup(-337788167);
         if (Content$lambda-2(state$delegate)) {
            Node1((String)null, $composer, 0, 1);
         }

         $composer.endReplaceableGroup();
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── *composer.end(isNode = false)* ← Now reading
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

// savedState関連なので、このif文は飛ばす。
        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
        if (pending != null && pending.keyInfos.size > 0) {
// ここはfalseなので通らない
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
// 削除の検知。
// この場合ペンディングに入れない。予想していたよりもノードが多かっただけ。
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
// ここのwhileに入る!
            val startSlot = reader.currentGroup
// startSlotは10になっている
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
        if (inserting) {
            if (isNode) {
                registerInsertUpFixup()
                expectedNodeCount = 1
            }
            reader.endEmpty()
            val parentGroup = writer.parent
            writer.endGroup()
            if (!reader.inEmpty) {
                val virtualIndex = insertedGroupVirtualIndex(parentGroup)
                writer.endInsert()
                writer.close()
                recordInsert(insertAnchor)
                this.inserting = false
                if (!slotTable.isEmpty) {
                    updateNodeCount(virtualIndex, 0)
                    updateNodeCountOverrides(virtualIndex, expectedNodeCount)
                }
            }
        } else {
            if (isNode) recordUp()
            recordEndGroup()
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
            if (expectedNodeCount != parentNodeCount) {
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }
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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@a94b610, androidx.compose.runtime.internal.ComposableLambdaImpl@12a9509]
    Group(4) key=-337788314, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@680630e]
     Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=false)@229417263]
     Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=false)@229417263, 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@f73cc2]
     Group(9) key=-337788167, nodes=1, size=4
// ↓ 今ここになっている
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@65e78c5]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@c63a21a]
      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]

recordDelete()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── *composer.recordSlotEditingOperation(removeCurrentGroupInstance)* ← Now reading
                    ├── composer.realizeOperationLocation()
                    ├── composer.recordSlotEditing()
                    └── composer.record(removeCurrentGroupInstance)

removeCurrentGroupInstanceについては後で触れます。次はrecordSlotEditingOperation()。

ComposerImpl
    /**
     * When a group is removed the reader will move but the writer will not so to ensure both the
     * writer and reader are tracking the same slot we advance the [writersReaderDelta] to
     * account for the removal.
     * グループが削除された時readerはmoveするが、writerはそうではないので、同じスロットを追跡するように
     * writersReaderDeltaを進めて、削除分を考慮。
     */
    private fun recordDelete() {
        recordSlotEditingOperation(change = removeCurrentGroupInstance)
        writersReaderDelta += reader.groupSize
    }
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── *composer.recordSlotEditingOperation(removeCurrentGroupInstance)* ← Now reading
                    ├── composer.recordDelete()
                    ├── composer.recordSlotEditing()
                    └── composer.record(removeCurrentGroupInstance)
ComposerImpl
    /**
     * Record a change that will insert, remove or move a slot table group. This ensures the slot
     * table is prepared for the change by ensuring the parent group is started and then ended
     * as the group is left.
     * slotTableへのinsert、move、removeなどのchangeをrecordする。
     * これにより、SlotTableは、親グループが開始され、グループが離脱すると終了するようにすることで、変更に備えます。(ちょっと意味が分かっていません)
     */
    private fun recordSlotEditingOperation(change: Change) {
        realizeOperationLocation()
        recordSlotEditing()
        record(change)
    }
Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
                    ├── *composer.realizeOperationLocation()* ← Now reading
                    ├── composer.recordSlotEditing()
                    └── composer.record(removeCurrentGroupInstance)
    private fun realizeOperationLocation(forParent: Boolean = false) {
        val location = if (forParent) reader.parent else reader.currentGroup
        val distance = location - writersReaderDelta
        require(distance >= 0) { "Tried to seek backward" }
// distanceは10になる
// この時点ではwritersReaderDeltaは0になっている
        if (distance > 0) {
// つまりrerodという関数で、slotsという引数でSlotWriterが渡ってくるので、
// ラムダの中ではSlotWriterに対して10進めてといっていることになる。
// この操作は一旦保存しておいて次のフェーズで利用される。
            record { _, slots, _ -> slots.advanceBy(distance) }
// そして、10をwritersReaderDeltaに入れる。
            writersReaderDelta = location
        }
    }

record()関数

ここでchange listにchangeを入れる

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
                    ├── composer.realizeOperationLocation()
                    │   └── *composer.record { _, slots, _ -> slots.advanceBy(distance) }* ← Now reading
                    ├── composer.recordSlotEditing()
                    └── composer.record(removeCurrentGroupInstance)
    /**
     * Add a raw change to the change list. Once [record] is called, the operation is realized
     * into the change list. The helper routines below reduce the number of operations that must
     * be realized to change the previous tree to the new tree as well as update the slot table
     * to prepare for the next composition.
     * 変更をchanged listに追加する。recordが一度呼ばれたら、その操作はchange listに入る。
     * 前のツリーから新しいツリーに変更するために実現しなくてはならない操作の数を減らし、
     * SlotTableを更新して次のcompositionに備える。
     */
    private fun record(change: Change) {
        changes.add(change)
    }

recordSlotEditing()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
                    ├── composer.realizeOperationLocation()
                    │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
                    ├── *composer.recordSlotEditing()* ← Now reading
                    └── composer.record(removeCurrentGroupInstance)

ここでは、recordSlotTableOperationを使って、後で、SlotWriter側で操作できるような操作をrecordする。
どういう操作かというと、Groupが始まったので、いろんなWriterのcurrentGroupなどを更新をかけるもの。

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

...

    private fun recordSlotEditing() {
        // During initial composition (when the slot table is empty), no group needs
        // to be started.
        if (!slotTable.isEmpty) {
// ここは最初だけ空らしいので、trueで通る。
            val reader = reader
            val location = reader.parent
// locationは9

            if (startedGroups.peekOr(-1) != location) {
// startedGroupsは0が9個入っているStackで、0を受け取るので、ここに入る。
                if (!startedGroup) {
// startedGroupはfalseなので、ここに入る
                    // We need to ensure the root group is started.
// ルートグループが始まっていることを確認する必要がある。
                    recordSlotTableOperation(change = startRootGroup)
// startedGroupがtrueに。
                    startedGroup = true
                }
                //
                val anchor = reader.anchor(location)
                startedGroups.push(location)
                recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
            }
        }
    }

image.png

    /**
     * Record a change ensuring, when it is applied, the write matches the current slot in the
     * reader.
     */
    private fun recordSlotTableOperation(forParent: Boolean = false, change: Change) {
// ここではすでに移動しているので、移動しない。
        realizeOperationLocation(forParent)
        record(change)
    }

SlotReader.anchor(location)

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
                    ├── composer.realizeOperationLocation()
                    │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
                    ├── composer.recordSlotEditing()
                    │   ├── composer.recordSlotTableOperation()
                    │   └── *slotReader.anchor(location = 9)* ← Now reading
                    └── composer.record(removeCurrentGroupInstance)
    fun anchor(index: Int = currentGroup) = table.anchors.getOrAdd(index, groupsSize) {
// まだ作られていなかったようで、作って返す。このときにtable.anchorsにも追加される。
        Anchor(index)
    } 

tableからindexに相当するanchorを取り出す。anchorは追跡するためのものらしい。

/**
 * An [Anchor] tracks a groups as its index changes due to other groups being inserted and
 * removed before it. If the group the [Anchor] is tracking is removed, directly or indirectly,
 * [valid] will return false. The current index of the group can be determined by passing either
 * the [SlotTable] or [SlotWriter] to [toIndexFor]. If a [SlotWriter] is active, it must be used
 * instead of the [SlotTable] as the anchor index could have shifted due to operations performed
 * on the writer.
 * Anchorは他のgroupが前に挿入されたり削除されたりすることでindexが変化するグループを追跡する。
 * もしAnchorで追跡していたgroupが消された場合、直接が非直接的にvalidがfalseになる。
 * そのグループの今のindexはSlotTableかSlotWriterのtoIndexForにわたすことで決定する。
 * もしSlotWriterがActiveであれば、操作が行われることによってindexがシャッフルされる可能性があるので、SlotWriterがSlotTableの代わりに使われる必要がある、
 */
internal class Anchor(loc: Int) {
    internal var location: Int = loc
    val valid get() = location != Int.MIN_VALUE
    fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this)
    fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this)
}

recordSlotEditing() 途中から

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

...

    private fun recordSlotEditing() {
        // During initial composition (when the slot table is empty), no group needs
        // to be started.
        if (!slotTable.isEmpty) {
// ここは最初だけ空らしいので、trueで通る。
            val reader = reader
            val location = reader.parent
// locationは9

            if (startedGroups.peekOr(-1) != location) {
// startedGroupsは0が9個入っているStackで、0を受け取るので、ここに入る。
                if (!startedGroup) {
// startedGroupはfalseなので、ここに入る
                    // We need to ensure the root group is started.
// ルートグループが始まっていることを確認する必要がある。
                    recordSlotTableOperation(change = startRootGroup)
// startedGroupがtrueに。
                    startedGroup = true
                }
                // このlocation = 9を追跡するためにanchorを作る
                val anchor = reader.anchor(location)
                // startedGroupsに9を追加する
                startedGroups.push(location)
                // anchorに対してensureStartedして、change listでWriterを使うときにlocationを動かせるように
                recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
            }
        }
    }

image.png

あとは呼び出し元に戻っていきます。

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── composer.recordDelete()
                └── *composer.recordSlotEditingOperation(removeCurrentGroupInstance)* ← Now reading
                    ├── composer.realizeOperationLocation()
                    │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
                    ├── composer.recordSlotEditing()
                    │   ├── composer.recordSlotTableOperation()
                    │   └── slotReader.anchor(location = 9)
                    └── composer.record(removeCurrentGroupInstance)
    private fun recordSlotEditingOperation(change: Change) {
        realizeOperationLocation()
        recordSlotEditing()
// 以下でrecordします。このchnageってなんでしたっけ?っていうことで戻ってきましょう
        record(change)
    }

recordDelete() 途中から

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            └── *composer.recordDelete()* ← Now reading
                └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
                    ├── composer.realizeOperationLocation()
                    │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
                    ├── composer.recordSlotEditing()
                    │   ├── composer.recordSlotTableOperation()
                    │   └── slotReader.anchor(location = 9)
                    └── composer.record(removeCurrentGroupInstance)

このchangeで渡していたのはslotWriterに対してのremoveCurrentGroupを呼ぶものですね。これで今のグループがあとで削除されそうです。

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

    private fun recordDelete() {
        recordSlotEditingOperation(change = removeCurrentGroupInstance)
        writersReaderDelta += reader.groupSize
    

そして、reader.groupSizeをwritersReaderDeltaに入れます。reader.groupSizeは3になるのでwritersReaderDeltaは10+3で13になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com
/0/27388/b82263d6-7de3-8376-83ad-3fc4c1709506.png)

end()に戻る

recordDelete()が終わったので、次はval nodesToRemove = reader.skipGroup()になります。

ComposerImpl
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

// savedState関連なので、このif文は飛ばす。
        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
        if (pending != null && pending.keyInfos.size > 0) {
// ここはfalseなので通らない
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
// 削除の検知。
// この場合ペンディングに入れない。予想していたよりもノードが多かっただけ。
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
// ここのwhileに入る!
            val startSlot = reader.currentGroup
// startSlotは10になっている
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
        if (inserting) {
            if (isNode) {
                registerInsertUpFixup()
                expectedNodeCount = 1
            }
            reader.endEmpty()
            val parentGroup = writer.parent
            writer.endGroup()
            if (!reader.inEmpty) {
                val virtualIndex = insertedGroupVirtualIndex(parentGroup)
                writer.endInsert()
                writer.close()
                recordInsert(insertAnchor)
                this.inserting = false
                if (!slotTable.isEmpty) {
                    updateNodeCount(virtualIndex, 0)
                    updateNodeCountOverrides(virtualIndex, expectedNodeCount)
                }
            }
        } else {
            if (isNode) recordUp()
            recordEndGroup()
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
            if (expectedNodeCount != parentNodeCount) {
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }

上記の以下の部分を見ていっています。

ComposerImpl.end
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
            val startSlot = reader.currentGroup
            recordDelete()
            val nodesToRemove = reader.skipGroup() // ←
            recordRemoveNode(removeIndex, nodesToRemove)
            invalidations.removeRange(startSlot, reader.currentGroup)
        

skipGroup()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            └── *slotReader.skipGroup()* ← Now reading
SlotReader
    /**
     *  Skip a group. Must be called at the start of a group.
     */
    fun skipGroup(): Int {
        require(emptyCount == 0) { "Cannot skip while in an empty region" }
        val count = if (groups.isNode(currentGroup)) 1 else groups.nodeCount(currentGroup)
        currentGroup += groups.groupSize(currentGroup)
        return count
    }

currentGroupが10だったのが、13になります。
countは1になります。
つまり recordDelete()はchange listに追加する処理で、skipGroup()ではSlotReaderを進める処理という感じかな?

image.png
image.png

recordRemoveNode()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            └── *composer.recordRemoveNode(count = 1, nodeIndex = 0)* ← Now reading

次はrecordRemoveNodeです。

ComposerImpl.end()
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
            val startSlot = reader.currentGroup
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove) // ←
            invalidations.removeRange(startSlot, reader.currentGroup)

その前のchange listのおさらいです。
change listは次のフェーズで、slot writerに書き込んでいくためのリストなんですが、
recordという名前がつくメソッドでそのchange listに追加していっているようです。

そのため、確認しておきます。

image.png

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)
}
currentGroupを削除。
つまり今のanchorの場所が削除される。ということになりそうですね :eyes:

さて、もう次のフェーズでGroupが削除されることは分かっているのですがこれ以上に何をするのかを見ていきましょう。

nodeIndex、countには1、0が入っています。
また引数として渡されているのはremoveIndex, nodesToRemoveなので、nodeIndex1から1個削除したいということですね。

ComposerImpl
    private fun recordRemoveNode(nodeIndex: Int, count: Int) {
        if (count > 0) {
            runtimeCheck(nodeIndex >= 0) { "Invalid remove index $nodeIndex" }
            if (previousRemove == nodeIndex) previousCount += count
            else {
// 動かなかったからか、if文で何もしなそう。
                realizeMovement()
                previousRemove = nodeIndex
                previousCount = count
            }
        }
    }

image.png

ここでは基本的に何もしないですが、フィールドの変更が入ります。
previousRemoveが-1だったのが、0になり
previousCountが1に変わります。
削除された場所と数を保管します。

endに戻る

ComposerImpl
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

// savedState関連なので、このif文は飛ばす。
        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
        if (pending != null && pending.keyInfos.size > 0) {
// ここはfalseなので通らない
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
// 削除の検知。
// この場合ペンディングに入れない。予想していたよりもノードが多かっただけ。
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
// ここのwhileに入る!
            val startSlot = reader.currentGroup
// startSlotは10になっている
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            // 前に削除されているので、削除するものはない。一応startSlotは10、reader.currentGroupは13になる。
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
        if (inserting) {
            // falseになるのでここは通らない。
...
        } else {
            if (isNode) recordUp() // falseなので何もしない
            recordEndGroup() // 次はここ
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
            if (expectedNodeCount != parentNodeCount) {
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }

recordEndGroup()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            └── *composer.recordEndGroup()* ← Now reading
ComposerImpl

private val endGroupInstance: Change = { _, slots, _ -> slots.endGroup() }

    private fun recordEndGroup() {
// locationは9になります
// またcurrentStartedGroupも9になります
// startedGroupsはrecordDeleteから呼ばれるrecordSlotEditing()で追加されていましたね
        val location = reader.parent
        val currentStartedGroup = startedGroups.peekOr(-1)
        runtimeCheck(currentStartedGroup <= location) { "Missed recording an endGroup" }
        if (startedGroups.peekOr(-1) == location) {
// trueになる
            startedGroups.pop()
// 9をpop
// change listに `slots.endGroup()` の操作を保存。
            recordSlotTableOperation(change = endGroupInstance)
        }
    }

image.png

このメソッドを呼び終わったあとのchange listの様子はこちら
image.png

end()に戻る

ComposerImpl
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

// savedState関連なので、このif文は飛ばす。
        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
        if (pending != null && pending.keyInfos.size > 0) {
// ここはfalseなので通らない
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
// 削除の検知。
// この場合ペンディングに入れない。予想していたよりもノードが多かっただけ。
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
// ここのwhileに入る!
            val startSlot = reader.currentGroup
// startSlotは10になっている
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            // 前に削除されているので、削除するものはない。一応startSlotは10、reader.currentGroupは13になる。
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
        if (inserting) {
            // falseになるのでここは通らない。
...
        } else {
            if (isNode) recordUp() // falseなので何もしない
            recordEndGroup() // endの操作を保存
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
            if (expectedNodeCount != parentNodeCount) {
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }

updatedNodeCount(parentGroup)

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            ├── composer.recordEndGroup()
            └── *composer.updatedNodeCount(group = 9)* ← Now reading

ここではslotReaderからgroupに対応するnodeの数を取得している。

ComposerImpl
    private fun updatedNodeCount(group: Int): Int {
        // groupには9が入っている
        if (group < 0) return nodeCountVirtualOverrides?.let { it[group] } ?: 0
        val nodeCounts = nodeCountOverrides
        // nodeCountOverridesおよびnodeCountsはnull
        if (nodeCounts != null) {
            val override = nodeCounts[group]
            if (override >= 0) return override
        }
// SlotTableに保管されているnodeの数を取得する
        return reader.nodeCount(group)
// 1が返る
    }

// SlotReader内
    fun nodeCount(index: Int) = groups.nodeCount(index)


private fun IntArray.nodeCount(address: Int) =
    this[address * Group_Fields_Size + GroupInfo_Offset] and NodeCount_Mask

end()に戻る

ComposerImpl
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

// savedState関連なので、このif文は飛ばす。
        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
        if (pending != null && pending.keyInfos.size > 0) {
// ここはfalseなので通らない
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
// 削除の検知。
// この場合ペンディングに入れない。予想していたよりもノードが多かっただけ。
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
// ここのwhileに入る!
            val startSlot = reader.currentGroup
// startSlotは10になっている
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            // 前に削除されているので、削除するものはない。一応startSlotは10、reader.currentGroupは13になる。
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
        if (inserting) {
            // falseになるのでここは通らない。
...
        } else {
            if (isNode) recordUp() // falseなので何もしない
            recordEndGroup() // endの操作を保存
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
            if (expectedNodeCount != parentNodeCount) {
// expectedNodeCount = 0 で parentNodeCountは1! 変更が検知されてif文に入る!
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }

image.png

updateNodeCountOverrides()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            ├── composer.recordEndGroup()
            ├── composer.updatedNodeCount(group = 9)
            └── *composer.updateNodeCountOverrides(group = 9, newCount = 0)* ← Now reading
ComposerImpl
    /**
     * As operations to insert and remove nodes are recorded, the number of nodes that will be in
     * the group after changes are applied is maintained in a side overrides table. This method
     * updates that count and then updates any parent groups that include the nodes this group
     * emits.
     * insertやremove nodeがrecordされると、side overrides tableに変更を適応したあとの
     * グループのノード数が保管される。
     * このメソッドはカウントを更新し、このグループがemitしたノードを含む親グループをアップデートする。
     */
    private fun updateNodeCountOverrides(group: Int, newCount: Int) {
// groupはgroup, newCouuntは0

        // The value of group can be negative which indicates it is tracking an inserted group
        // instead of an existing group. The index is a virtual index calculated by
        // insertedGroupVirtualIndex which corresponds to the location of the groups to insert in
        // the insertTable.
// グループの値はマイナスになる場合がある。それは既存のグループの代わりにinsertされたグループを
// 追跡していることを示す。
// indexはvertual indexで、insertedGroupVirtualIndexによって計算される。
// insertedGroupVirtualIndexはinsertTableのgroupのinsertされる位置に相当する。
        val currentCount = updatedNodeCount(group)
        if (currentCount != newCount) {
// currentCount = 1, newCount = 0なので、if文に入る!
            // Update the overrides
            val delta = newCount - currentCount
            var current = group

            var minPending = pendingStack.size - 1

// データの状況
// delta = -1
// group = 9
// pendingStack.size = 4 (pendingStackのアイテムたちはすべてnull)
// minPending = 3
// current = 9

            while (current != -1) {
// updatedNodeCountはまた1を返すので deltaは-1なので、0になる。
                val newCurrentNodes = updatedNodeCount(current) + delta
// current = 9, newCurrentNodes = 0でupdateNodeCount()を呼び出す。
                updateNodeCount(current, newCurrentNodes)
                for (pendingIndex in minPending downTo 0) {
                    val pending = pendingStack.peek(pendingIndex)
                    if (pending != null && pending.updateNodeCount(current, newCurrentNodes)) {
                        minPending = pendingIndex - 1
                        break
                    }
                }
                @Suppress("LiftReturnOrAssignment")
                if (current < 0) {
                    current = reader.parent
                } else {
                    if (reader.isNode(current)) break
                    current = reader.parent(current)
                }
            }
        }
    }

updatedNodeCount()はgroupに対応するnodeの数を返して1を返す。

    private fun updatedNodeCount(group: Int): Int {
// group = 9が渡る

// falseになる
        if (group < 0) return nodeCountVirtualOverrides?.let { it[group] } ?: 0


        val nodeCounts = nodeCountOverrides
        if (nodeCounts != null) {
// nodeCountOverrides = nodeCounts = nullなので、ここは実行されない
// 今回は実行されないが、後述のupdateNodeCount()で代入されて使えるようになる
            val override = nodeCounts[group]
            if (override >= 0) return override
        }
// 以下は1になる。
// これは今の返されたあとcurrentCountに入るので、前の値が取れるみたい。
        return reader.nodeCount(group)
    }

updateNodeCount()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            ├── composer.recordEndGroup()
            ├── composer.updatedNodeCount(group = 9)
            └── composer.updateNodeCountOverrides(group = 9, newCount = 0)
                ├── composer.updatedNodeCount(group = 9)
                └── *composer.updateNodeCount(group = 9, count = 0)* ← Now reading

IntArrayを作成し、groupの数だけ-1で埋めて、
nodeCountOverridesに代入し、nodeCountOverridesを作成する。
nodeCounts[group] = countで今変更があったgroupのカウントだけ更新する。

つまりnodeCountOverridesに変更があって0個になった場所を書き込む。

    private fun updateNodeCount(group: Int, count: Int) {
        if (updatedNodeCount(group) != count) {
            if (group < 0) {
// group = 9
// count = 0
// updatedNodeCount(group) = 1
                val virtualCounts = nodeCountVirtualOverrides ?: run {
                    val newCounts = HashMap<Int, Int>()
                    nodeCountVirtualOverrides = newCounts
                    newCounts
                }
                virtualCounts[group] = count
            } else {
                val nodeCounts = nodeCountOverrides ?: run {
                    val newCounts = IntArray(reader.size)
                    newCounts.fill(-1)
                    nodeCountOverrides = newCounts
                    newCounts
                }
                nodeCounts[group] = count
            }
        }
    }

実行後のnodeCountOverridesは以下の通り。

image.png

updateNodeCountOverrides()に戻る

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            ├── composer.recordEndGroup()
            ├── composer.updatedNodeCount(group = 9)
            └── *composer.updateNodeCountOverrides(group = 9, newCount = 0)* ← Now reading
                └── composer.updatedNodeCount(group = 9)
ComposerImpl
    /**
     * As operations to insert and remove nodes are recorded, the number of nodes that will be in
     * the group after changes are applied is maintained in a side overrides table. This method
     * updates that count and then updates any parent groups that include the nodes this group
     * emits.
     * insertやremove nodeがrecordされると、side overrides tableに変更を適応したあとの
     * グループのノード数が保管される。
     * このメソッドはカウントを更新し、このグループがemitしたノードを含む親グループをアップデートする。
     */
    private fun updateNodeCountOverrides(group: Int, newCount: Int) {
// groupはgroup, newCouuntは0

        // The value of group can be negative which indicates it is tracking an inserted group
        // instead of an existing group. The index is a virtual index calculated by
        // insertedGroupVirtualIndex which corresponds to the location of the groups to insert in
        // the insertTable.
// グループの値はマイナスになる場合がある。それは既存のグループの代わりにinsertされたグループを
// 追跡していることを示す。
// indexはvertual indexで、insertedGroupVirtualIndexによって計算される。
// insertedGroupVirtualIndexはinsertTableのgroupのinsertされる位置に相当する。
        val currentCount = updatedNodeCount(group)
        if (currentCount != newCount) {
// currentCount = 1, newCount = 0なので、if文に入る!
            // Update the overrides
            val delta = newCount - currentCount
            var current = group

            var minPending = pendingStack.size - 1

// データの状況
// delta = -1
// group = 9
// pendingStack.size = 4 (pendingStackのアイテムたちはすべてnull)
// minPending = 3
// current = 9

            while (current != -1) {
// updatedNodeCountはまた1を返すので deltaは-1なので、0になる。
                val newCurrentNodes = updatedNodeCount(current) + delta
// current = 9, newCurrentNodes = 0でupdateNodeCount()を呼び出す。
                updateNodeCount(current, newCurrentNodes)
// minPendingは3になっていおり、そのため3 2 1と繰り返す
// pendingStackはサイズが4ですべてnullが入っているため、何も行われずにこのループを抜ける。
                for (pendingIndex in minPending downTo 0) {
                    val pending = pendingStack.peek(pendingIndex)
                    if (pending != null && pending.updateNodeCount(current, newCurrentNodes)) {
                        minPending = pendingIndex - 1
                        break
                    }
                }
                @Suppress("LiftReturnOrAssignment")
// currentは9なので、elseに入る
                if (current < 0) {
                    current = reader.parent
                } else {
// reader.isNode(current)はfalse
                    if (reader.isNode(current)) break
// currentが9から4に変化する
                    current = reader.parent(current)
                }
            }
        }
    }

今のslotTableがこちらでcurrentが9から4に変化する。

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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.internal.ComposableLambdaImpl@3b52827]
// 4↓に変わる
    Group(4) key=-337788314, 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]
// 9↓から
     Group(9) key=-337788167, nodes=1, size=4
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@7421fc3]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@a72040]
      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]

updateNodeCountOverrides()に戻る

updateNodeCountOverrides()のwhile文は-1になるまで繰り返す。つまり親のカウントをpendingStackに書き込んでいくということになる。

ComposerImpl
    /**
     * As operations to insert and remove nodes are recorded, the number of nodes that will be in
     * the group after changes are applied is maintained in a side overrides table. This method
     * updates that count and then updates any parent groups that include the nodes this group
     * emits.
     * insertやremove nodeがrecordされると、side overrides tableに変更を適応したあとの
     * グループのノード数が保管される。
     * このメソッドはカウントを更新し、このグループがemitしたノードを含む親グループをアップデートする。
     */
    private fun updateNodeCountOverrides(group: Int, newCount: Int) {
// groupはgroup, newCouuntは0

        // The value of group can be negative which indicates it is tracking an inserted group
        // instead of an existing group. The index is a virtual index calculated by
        // insertedGroupVirtualIndex which corresponds to the location of the groups to insert in
        // the insertTable.
// グループの値はマイナスになる場合がある。それは既存のグループの代わりにinsertされたグループを
// 追跡していることを示す。
// indexはvertual indexで、insertedGroupVirtualIndexによって計算される。
// insertedGroupVirtualIndexはinsertTableのgroupのinsertされる位置に相当する。
        val currentCount = updatedNodeCount(group)
        if (currentCount != newCount) {
// currentCount = 1, newCount = 0なので、if文に入る!
            // Update the overrides
            val delta = newCount - currentCount
            var current = group

            var minPending = pendingStack.size - 1

// データの状況
// delta = -1
// group = 9
// pendingStack.size = 4 (pendingStackのアイテムたちはすべてnull)
// minPending = 3
// current = 9

            while (current != -1) {
// updatedNodeCountはまた1を返すので deltaは-1なので、0になる。
                val newCurrentNodes = updatedNodeCount(current) + delta
// current = 9, newCurrentNodes = 0でupdateNodeCount()を呼び出す。
                updateNodeCount(current, newCurrentNodes)
// minPendingは3になっていおり、そのため3 2 1と繰り返す
// pendingStackはサイズが4ですべてnullが入っているため、何も行われずにこのループを抜ける。
                for (pendingIndex in minPending downTo 0) {
                    val pending = pendingStack.peek(pendingIndex)
                    if (pending != null && pending.updateNodeCount(current, newCurrentNodes)) {
                        minPending = pendingIndex - 1
                        break
                    }
                }
                @Suppress("LiftReturnOrAssignment")
// currentは9なので、elseに入る
                if (current < 0) {
                    current = reader.parent
                } else {
// reader.isNode(current)はfalse
                    if (reader.isNode(current)) break
// currentが9から
                    current = reader.parent(current)
                }
            }
        }
    }

このループが終わった後
nodeCountOverridesは以下のようになる。

image.png

node削除に伴って、これは以下のSlotTableにあるnodesのカウントを更新するためのロジックに見える。

androidx.compose.runtime.SlotTable@5935541
↓nodes=2になっているが、nodes=1にしたい!など
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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.internal.ComposableLambdaImpl@3b52827]
    Group(4) key=-337788314, 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=-337788167, nodes=1, size=4
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@7421fc3]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@a72040]
      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]

end()に戻る

ComposerImpl
    private fun end(isNode: Boolean) {
        // All the changes to the group (or node) have been recorded. All new nodes have been
        // inserted but it has yet to determine which need to be removed or moved. Note that the
        // changes are relative to the first change in the list of nodes that are changing.

// savedState関連なので、このif文は飛ばす。
        if (inserting) {
            val parent = writer.parent
            updateCompoundKeyWhenWeExitGroup(
                writer.groupKey(parent),
                writer.groupObjectKey(parent),
                writer.groupAux(parent)
            )
        } else {
            val parent = reader.parent
            updateCompoundKeyWhenWeExitGroup(
                reader.groupKey(parent),
                reader.groupObjectKey(parent),
                reader.groupAux(parent)
            )
        }
        var expectedNodeCount = groupNodeCount
        val pending = pending
        if (pending != null && pending.keyInfos.size > 0) {
// ここはfalseなので通らない
...
        }

        // Detect removing nodes at the end. No pending is created in this case we just have more
        // nodes in the previous composition than we expect (i.e. we are not yet at an end)
// 削除の検知。
// この場合ペンディングに入れない。予想していたよりもノードが多かっただけ。
        val removeIndex = nodeIndex
        while (!reader.isGroupEnd) {
// ここのwhileに入る!
            val startSlot = reader.currentGroup
// startSlotは10になっている
            recordDelete()
            val nodesToRemove = reader.skipGroup()
            recordRemoveNode(removeIndex, nodesToRemove)
            // 前に削除されているので、削除するものはない。一応startSlotは10、reader.currentGroupは13になる。
            invalidations.removeRange(startSlot, reader.currentGroup)
        }

        val inserting = inserting
        if (inserting) {
            // falseになるのでここは通らない。
...
        } else {
            if (isNode) recordUp() // falseなので何もしない
            recordEndGroup() // endの操作を保存
            val parentGroup = reader.parent
            val parentNodeCount = updatedNodeCount(parentGroup)
            if (expectedNodeCount != parentNodeCount) {
// expectedNodeCount = 0 で parentNodeCountは1! 変更が検知されてif文に入る!
// 以下で変更後のnodeの数をフィールドに保存する。
                updateNodeCountOverrides(parentGroup, expectedNodeCount)
            }
// isNode = false
            if (isNode) {
                expectedNodeCount = 1
            }
            reader.endGroup()
            realizeMovement()
        }

        exitGroup(expectedNodeCount, inserting)
    }

reader.endGroup()

これは一度読んでいる関数なので、さらっと

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            ├── composer.recordEndGroup()
            ├── composer.updatedNodeCount(group = 9)
            ├── composer.updateNodeCountOverrides(group = 9, newCount = 0)
            │   ├── composer.updatedNodeCount(group = 9)
            │   ├── *composer.updateNodeCount(group = 9, count = 0)* ← Now reading
            │   ├── *composer.updateNodeCount(group = 4, count = 1)* ← Now reading
            │   └── *composer.updateNodeCount(group = ..., count = 1)* ← Now reading
            └── *slotReader.endGroup()* ← Now reading

currentGroupは13になっている。

次に読んでいくためにcurrentEndを更新をかけているっぽい?
この関数を通して、これまでのcurrentEndが13だったのが16になる。

SlotReader
    /**
     * End the current group. Must be called after the corresponding [startGroup].
     */
    fun endGroup() {
// emptyCount=0なのでtrueになる
        if (emptyCount == 0) {
            require(currentGroup == currentEnd) { "endGroup() not called at the end of a group" }
            val parent = groups.parentAnchor(parent)
            this.parent = parent
// parent < 0はfalseになる parentは4になっている。
// groups.groupSize(parent)は12になる。
// 16がcurrentEndになる。
            currentEnd = if (parent < 0)
                groupsSize
            else
                parent + groups.groupSize(parent)
        }
    }
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=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.internal.ComposableLambdaImpl@3b52827]
// ↓ parent
    Group(4) key=-337788314, 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=-337788167, nodes=1, size=4
      Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@7421fc3]
       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=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@a72040]
      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]

realizeMovement()

Content()
├── composer.startRestartGroup()
│   ├── composer.start()
│   └── composer.addRecomposeScope()
│       └── RecomposeScopeImpl.start()
├── composer.rememberedValue()
│   └── composer.nextSlot()
├── composer.endReplaceableGroup()
│   └── composer.endGroup()
│       └── composer.end(isNode = false)
│           ├── composer.recordEndGroup()
│           ├── SlotReader.endGroup()
│           └── composer.exitGroup(expectedNodeCount = 0, inserting = false)
├── composer.changed(MutableState(value = false))
│   └── nextSlot()
├── EffectsKt.LaunchedEffect()
├── composer.startReplaceableGroup()
└── composer.endReplaceableGroup()
    └── composer.endGroup()
        └── composer.end(isNode = false)
            ├── composer.recordDelete()
            │   └── composer.recordSlotEditingOperation(removeCurrentGroupInstance)
            │       ├── composer.realizeOperationLocation()
            │       │   └── composer.record { _, slots, _ -> slots.advanceBy(distance) }
            │       ├── composer.recordSlotEditing()
            │       │   ├── composer.recordSlotTableOperation()
            │       │   └── slotReader.anchor(location = 9)
            │       └── composer.record(removeCurrentGroupInstance)
            ├── slotReader.skipGroup()
            ├── composer.recordRemoveNode(count = 1, nodeIndex = 0)
            ├── composer.recordEndGroup()
            ├── composer.updatedNodeCount(group = 9)
            ├── composer.updateNodeCountOverrides(group = 9, newCount = 0)
            │   ├── composer.updatedNodeCount(group = 9)
            │   ├── composer.updateNodeCount(group = 9, count = 0)
            │   ├── composer.updateNodeCount(group = 4, count = 1)
            │   └── composer.updateNodeCount(group = ..., count = 1)
            ├── slotReader.endGroup()
            └── *composer.realizeMovement()* ← Now reading

    private fun realizeMovement() {
        val count = previousCount
        previousCount = 0
        if (count > 0) {
            if (previousRemove >= 0) {
// previousCountは1
// countは1
// previousRemoveは0
                val removeIndex = previousRemove
                previousRemove = -1
                recordApplierOperation { applier, _, _ -> applier.remove(removeIndex, count) }
            } else {
                val from = previousMoveFrom
                previousMoveFrom = -1
                val to = previousMoveTo
                previousMoveTo = -1
                recordApplierOperation { applier, _, _ -> applier.move(from, to, count) }
            }
        }
    }

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

ここではrealizeUps()やrealizeDowns()では判定によって何も行われずに、recordとなるので
applier.remove(removeIndex, count)の操作が保存される。
このapplierは実際のnodeをいじるAPIになっています。

composer
    /**
     * Record a change ensuring, when it is applied, that the applier is focused on the current
     * node.
     */
    private fun recordApplierOperation(change: Change) {
        realizeUps()
        realizeDowns()
        record(change)
    }

これによって、ついに。ついに。end()が終わりました :tada:

まとめ

基本的にSlotReaderからの取得結果と呼び出した実装を比較し、変更をchangesやnodeCountOverridesに保存していきました。
まだSlotTableに対しての実際の変更は行われていません。

今のchangesをおさらいしましょう。

image.png

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) }

今回は削除があったときのchangesを追っていくことができました。
これらのchangesはまだ実行しておらず、次のフェーズでは実際にSlotTableに反映されていくわけです。
次は残ったNode2()は変化がないため、最適化されて呼び出されないのではないかと思われるので、そのあたりを追っていきます。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?