Jetpack Compose の Snapshot

:::note warn
本記事には間違った内容が含まれている可能性が高いため参考にする程度でお願いします。間違った点を見つけた場合はコメントなどで教えていただけると幸いです。
また一次ソースとして本記事で参考にしている[Jetpack Compose internals](https://jorgecastillo.dev/book/) や最新の Jetpack Compose のソースコードを参照することを推奨します。
:::

---

## スナップショットとは

**ある瞬間** の **状態** を保持したもの

![sc-239 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/98bb7380-4b12-0d23-578e-6de12ee7e8a3.png)

イラスト: https://simpc.jp/tec/item/sc-239/

---

## でも `Snapshot`って Compose のコードを書いていて見たことなくないですか...?

---

## と思いきやめちゃくちゃ当たりまえに使っています。

---

## Snapshot の身近な例を見てみましょう。

---

## mutableStateOf の実装を見る

```kotlin
var isOn by remember {
    mutableStateOf(false) // 実装を見る
}
```

---

```kotlin
fun <T> mutableStateOf(
    value: T,
    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
```

**Snapshot**MutationPolicy<T> や create **Snapshot**MutableState というものが使われています。

---

## mutableStateOf は Snapshot と密接に関係がある

---

## もし mutableStateOf を使わなかったらどうなるか試してみましょう

---

mutableStateOf とそっくりな myMutableStateOf を作ります。

```kotlin
fun <T> myMutableStateOf(initial: T): MyMutableState<T> {
    return MyMutableState(initial)
}

class MyMutableState<T>(initial: T) {
    var value = initial
}
```

`by` が使えるように Delegated property も書いておきます。

```kotlin
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MyMutableState<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value

@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MyMutableState<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) {
    this.value = value
}
```
---

```kotlin
var isOn by remember {
    myMutableStateOf(false)  // 差し替える
}
```

---

## すると

---

状態は変化せず何も起きなくなりります :innocent: 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/e9d330a6-646d-20a4-b8a0-ef8ab316f391.png)

いくらタップしてもスイッチの状態は変わりません。

---

## でも `Flow#collectAsState()` とかは `mutableStateOf` を使っていないけど状態の変化を検出できている気がする...?

---

## `Flow#collectAsState()` の中身を見る

---

## `StateFlow#collectAsState()`

```kotlin
@Composable
fun <T : R, R> Flow<T>.collectAsState(
    initial: R,
    context: CoroutineContext = EmptyCoroutineContext
): State<R> = produceState(initial, this, context) {
    if (context == EmptyCoroutineContext) {
        collect { value = it }
    } else withContext(context) {
        collect { value = it }
    }
}
```

内部で `produceState` が使われていることが分かる。

---

## `produceState`

```kotlin
@Composable
fun <T> produceState(
    initialValue: T,
    key1: Any?,
    key2: Any?,
    producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
    val result = remember { mutableStateOf(initialValue) }  // mutableStateOf が使われている
    LaunchedEffect(key1, key2) {
        ProduceStateScopeImpl(result, coroutineContext).producer()
    }
    return result
}
```

内部で `mutableStateOf` が使われていることが分かる。

---

## 結局 Compose の状態には必ず `mutableStateOf` が使われている

---

# パート1のまとめ

* Snapshot とは 「ある瞬間の状態を保持したもの」である
* Snapshot は `mutableStateOf` と密接に関わっており、Compose に組み込まれたシステムである
* `mutableStateOf` を使わないと状態の変化を表現することができない


---

# パート2 ✌️ Snapshot の仕組み

---

## Snapshot の仕組み

* ここからは結構深い話になります
* 業務で直接役に立つことはあんまりないかもれしれません
* しかし、**Compose の魔法** の秘密に迫る興味深い内容になっていると思います！

---

## :warning: 完全に理解しきれていない部分も多々あります。ご了承ください :bow: 

---

## Snapshot がどのように動作するか

Snapshot は `@Composable` 外でも動きます。
実際にコードで Snapshot の挙動を観察してみましょう。

Snapshot は Compose のアーキテクチャレイヤの最も下位のレベルの Compose Runtime に含まれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/4b5fa7f4-7bea-7289-9f7b-8fd89c21cccf.png)

---

# サンプルコード

```kotlin
@Test
fun sample() {
    val state = mutableStateOf("ツバス")
    val snapshot = Snapshot.takeSnapshot()

    state.value = "ハマチ"
    println(state.value)
    
    snapshot.enter {
        println(state.value)
    }

    snapshot.dispose()
}
```

順に説明します。

---

```kotlin
val state = mutableStateOf("ツバス")
```

`mutableStateOf` を使って `MutableState` を作成します。
初期値はツバスとします。

※ ツバスはブリの20-40cmサイズの関西での呼び名です。

---

```kotlin
val snapshot = Snapshot.takeSnapshot()
```

次に [Snapshot#takeSnapshot()](https://developer.android.com/reference/kotlin/androidx/compose/runtime/snapshots/Snapshot#takeSnapshot(kotlin.Function1)) を呼び出しでスナップショットを取ります。

`Snapshot` クラスは普通に Compose のコードを書いている限り見たことがないと思います。
なぜなら Compose ではコンパイラが自動的に Snapshot 周りの処理をしてくれるからです。

---

```kotlin
state.value = "ハマチ"
println(state.value)
```

次に状態を `"ハマチ"` に書き換えて println で状態を出力すると結果は `"ハマチ"` になります。
ここまではいたって普通の挙動です。

---

```kotlin
snapshot.enter {
    println(state.value)
}
```

では先程取ったスナップショットの `enter` を呼び出し、そのスコープ内で状態の値を出力すると結果はどうなるでしょうか?
考えてみてください :clock: 

---

```kotlin
snapshot.enter {
    println(state.value)  // ツバス
}
```

正解は `"ツバス"` になります。わかりましたか?
そうです、あたかもスナップショットを取った時点の状態に戻ったかのような挙動をするのです。
不思議ですよね。

---

## Snapshot システムがどのように作られているか

---

## 状態管理に必要なこと

状態は複数のスレッドで同時に読み込みが行われたり、書き込みが行われたりすることがあります。
その場合でも状態の整合性を確保するための仕組みが必要になります。

いくつかのアプローチ方法がありますが、Compose では アクターモデル に似た方法を採用しています。

---

## 並行性制御システム

どのように状態の変化が起きたとしても確実に同調することが必要です。
しかしそのためにはいくらかパフォーマンスを犠牲にする必要があります。そのため可能な限り効率の良い方法を取ることが重要です。

並行性制御システムの例として RDBMS のトランザクションがあります。
Compose の場合は RDBMS ほど複雑でなく、ACID 原則のうちDの部分は不要なので持っていません。

---

### ACID 原則

* Atomicity (不可分性)
* Consistency (整合性)
* Isolation (独立性)
* Durability (永続性)


※ Durability は Compose にはない

---

## MVCC

「MVCC=Multiversion concurrency control、多版型同時実行制御」
Compose で使用されている並行性制御システムです。
全ての処理がどの順番で行われたかを記録しており、すべての変更は ID で管理されています。
独立性があるので書き込み中の読み取りや反対に読み取り中の書き込みを行うことができます。

MVCCに は独立性のためにデータが書き込まれるたびにオリジナルの値を上書きする代わりに新しいデータのコピーを作成します。Compose の実装では `StateRecord` クラスのLinked listで実現されています。

---

## MVCC

MVCCのもう1つの特徴としてスナップショットがあります。それぞれのスナップショットにはIDがあり Compose の場合は単調増加します。(1,2,3...)

スレッドごとにある瞬間の状態のスナップショットを持っており、そのスナップショット上で操作が行われます(マルチバージョン)。各スレッドで行われた変更はそれらが完了したら各スレッドのスナップショットに伝播されます。

---

## Snapshot のツリー

Snapshot はツリー構造を持っています。任意の数だけ Snapshot をネストすることができます。ルートは GlobalSnapshot と呼ばれ、子ノードはNested[Readonly|Mutable]Snapshot と呼ばれます。

```mermaid
graph TD
    A[GlobalSnapshot] --> B[Snapshot]
    A --> C[Snapshot]
    B --> D[NestedSnapshot]
    B --> E[NestedSnapshot]
    C --> F[NestedSnapshot]
    C --> G[NestedSnapshot]
```

---


# GlobalSnapshot

GlobalSnapshot は Snapshot のツリーのルートで書き込みが可能なスナップショットです。
Compose の初期化時に GlobalSnapshot が作成されます。

---

## スナップショットのネスト

ネストされたスナップショットは親と独立しているので、親をアクティブに保ちながら dispose することができます。
Compose では Subcomposition で使用されていて、具体的には `BoxWithConstraints` `LazyColumn` `SubcomposeLayout` などで使用されています。ネストされたスナップショットに変更が加えられた場合は親に伝播されます。

---

## 状態の読み込みと書き込みの購読

Compose では状態の変化に反応して Recompose をする必要があります。これを実現するためにスナップショットでは状態の読み込みと書き込みを購読できる仕組みが用意されています。

身近な例として Recomposer の他に [`snapshotFlow`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFlow.kt;l=110?q=snapshotflow&sq=) がこの仕組みを使用しています。

## 読み込みの購読

```kotlin
val snapshot = Snapshot.takeSnapshot(
    readObserver = {
        println("readObserver: $it")
    }
)
```

`readObserver` を使用します。

## 書き込みの購読

```kotlin
val snapshot = Snapshot.takeMutableSnapshot(
    writeObserver = {
        println("writeObserver: $it")
    }
)
```

`writeObserver` を使用します。`readObserver` と違い `MutableSnapshot` でしか使えないので注意してください。

---

## MutableSnapshot

`Snapshot.takeSnapshot` は `ReadonlySnapshot` を返すので書き込みを行うことができません。
`Snapshot.takeMutableSnapshot` で書き込み可能なスナップショットを取ることができます。(MutableSnapshot)

書き込みを行ったあと、`MutableSnapshot#apply()` を呼びだすことで変更が反映されます。

---

## StateStateRecord

`StateRecord` はある状態のある時点での値を保持しているクラスです。

```kotlin
abstract class StateRecord {
    internal var snapshotId: Int = currentSnapshot().id

    internal var next: StateRecord? = null

    abstract fun assign(value: StateRecord)

    abstract fun create(): StateRecord
}
```

### snapshotId

StateRecord は 現在の Snapshotに対して作成されます。
`snapshotId` には StateRecord が作成された時点のスナップショットIDがセットされています。

### next

StateRecord は Linked list で接続されています。next は次の要素を指します。

---

# StateObject

`StateObject` は StateRecord の Linked list を管理しています。
mutableStateOf で返される `MutableState` の実体である `SnapshotMutableStateImpl`　は `StateObject` を実装しています。

状態が読み込まれるたびに、StateRecord の Linked list をトラバースして最新の有効な `StateRecord` を見つけて返します。

StateRecord の linked list の先頭に追加することができます。


```mermaid
graph LR
  A[StateObject.firstStateRecord] --> B[StateRecord.next]
  B --> C[StateRecord.next]
  C --> D[...]
```

---

## MutableState の値の読み込みと書き込み

`MutableState` の実体は `SnapshotMutableStateImpl` というクラスです。
Getter と setter は [Computed property](https://kotlinlang.org/docs/properties.html#getters-and-setters) になっています。
つまり単にフィールドの値が参照されているわけではなく、ここに秘密が隠されています。

```kotlin
internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
        get() = next.readable(this).value
        set(value) = next.withCurrent {
            if (!policy.equivalent(it.value, value)) {
                next.overwritable(this, it) { this.value = value }
            }
        }
}
```

---

## `MutableState.value` の Getter

```kotlin
override var value: T
    get() = next.readable(this).value
```

next は StateRecord の Linked list の先頭を表しています。

## `readable`

validかつ最新の StateRecord を返しつつ、ReadObserverの呼び出しを行っています。

---

## `MutableState.value` の Setter

```kotlin
set(value) = next.withCurrent {
    if (!policy.equivalent(it.value, value)) {
        next.overwritable(this, it) { this.value = value }
    }
}
```

`withCurrent` で通知をすることなくvalidかつ最新の `StateRecord` を取得します。
policy に基づいて　StateRecord　の値とセットしようとしている値が異なる場合には `overwritable` で新しい StateRecrod を作成し、Linked list の先頭に追加して最新の値を書き込みます。

---

## さいごに

---

## スナップショットはとてもよくできた面白いシステムです。実際にコードでいろいろ試しながら挙動を確認してみることをおすすめします :thumbsup: