Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Opening the shutter on snapshots (dcNYC 22)

Opening the shutter on snapshots (dcNYC 22)

Video: https://www.droidcon.com/2022/09/29/opening-the-shutter-on-snapshots/

Jetpack Compose shows the power of a custom compiler plugin. But not all the magic happens during compilation. A lot of Compose features are based on a runtime library that doesn't require any compiler support: the snapshot system. It might seem like magic at first, but it's just built on top of things you might already know: ThreadLocals, linked lists, and, yes, even regular old callbacks. Once you understand how Compose thinks about state, you might find ways to use its tools in your own code – even outside of Compose.

Zach Klippenstein

September 02, 2022
Tweet

More Decks by Zach Klippenstein

Other Decks in Programming

Transcript

  1. @zachklipp Pre-watch: A Hitchhiker’s Guide to Compose Post-watch: Using Compose

    Runtime to create a client library not this talk this talk snapshots
  2. @zachklipp Pre-watch: A Hitchhiker’s Guide to Compose Post-watch: Using Compose

    Runtime to create a client library these slides my twitter not this talk this talk snapshots
  3. @zachklipp What for? What do they do? How do they

    work? What can we do with them?
  4. @zachklipp Git Snapshots branches snapshots main branch global snapshot commits

    state object writes merge apply snapshot resolve conflicts merge records hooks read, write, apply listeners
  5. @zachklipp (but shipped with it) Snapshots ≠ Compose snapshots the

    rest of Compose ThreadLocal integers linked lists
  6. @zachklipp Giant state container? (i.e. Redux) snapA “a” “foo” Range(0,

    100) snapB “a” “bar” Range(0, 50) snapC “b” “foo” Range(25, 75) all state in app
  7. @zachklipp reads: 96 snapshots records 128 167 181 64 75

    111 209 52 53 87 many : state “foo”
  8. @zachklipp writes: : “bar” 210 96 128 167 181 64

    75 111 209 52 53 87 state “foo”
  9. @zachklipp Snapshots concepts State Objects low-level API concrete ID previous

    IDs invalid set primary API abstract map of IDs to values written by snapshot with ID i.e. “records”
  10. @zachklipp Snapshots API State Objects takeNestedSnapshot takeNestedMutableSnapshot withMutableSnapshot registerApplyObserver val

    state = mutableStateOf(“”) 
 mutableStateListOf mutableStateMapOf …whatever!
  11. @zachklipp Snapshots operations State Objects Create: choose new ID Enter:

    set ThreadLocal restore ThreadLocal Apply: commit ID notify listeners Read: find record read from record Write: find or copy record write to record
  12. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  13. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  14. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  15. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  16. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  17. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  18. @zachklipp Selecting record for read message id value “he” “hello

    asdf” tombstoned “hello ” written by 72 “hello world” color id value Red Blue written by 80 snapshot ID invalid set
  19. newest valid record @zachklipp “mutableState.value” get() = findRecord(SnapshotThreadLocal.get()) highest ID

    not higher than snapshot ID AND not in snapshot invalid set AND not tombstoned
  20. @zachklipp Walkthrough: create child, apply val message = mutableStateOf("") val

    snapshot = Snapshot.takeMutableSnapshot() snapshot.enter { message.value = "hello" } snapshot.apply() message
  21. @zachklipp Snapshot rules: Create child: 1. Create new snapshot 2.

    Copy parent’s invalid list 3. Advance parent – child’s ID will be added to invalid set (see Advance rule 3 below) Apply: 1. Advance parent 2. Remove child ID from invalid set – parent sees child’s writes 3. Add child’s current and previous IDs to parent’s list of previous IDs – parent “absorbs” child Advance: 1. New ID is next highest ID 2. Add old ID to list of previous IDs 3. Add IDs between old and new (exclusive) to invalid set This is a superset of all open snapshots i.e. Unrelated snapshots created since old ID should be ignored
  22. @zachklipp Walkthrough: create 2 children, apply val message = mutableStateOf("")

    val color = mutableStateOf(Red) val snapshotA = Snapshot.takeMutableSnapshot() val snapshotB = Snapshot.takeMutableSnapshot() snapshotA.enter { message.value = "hello" } snapshotB.enter { color.value = Blue } snapshotA.apply() snapshotB.apply()
  23. @zachklipp Walkthrough: create 2 children, apply val message = mutableStateOf("")

    val color = mutableStateOf(Red) message color global snapshot
  24. @zachklipp Walkthrough: create 2 children, apply val message = mutableStateOf("")

    val color = mutableStateOf(Red) message “” color Red global snapshot
  25. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot val snapshotB = Snapshot.takeMutableSnapshot() snapshotB
  26. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot val snapshotB = Snapshot.takeMutableSnapshot() snapshotB
  27. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot val snapshotB = Snapshot.takeMutableSnapshot() snapshotB
  28. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot val snapshotB = Snapshot.takeMutableSnapshot() snapshotB
  29. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot val snapshotB = Snapshot.takeMutableSnapshot() snapshotB
  30. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot val snapshotB = Snapshot.takeMutableSnapshot() snapshotB
  31. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB snapshotA.enter { message.value = "hello" }
  32. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB snapshotA.enter { message.value = "hello" }
  33. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB “hello” snapshotA.enter { message.value = "hello" }
  34. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB “hello” snapshotB.enter { color.value = Blue }
  35. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB “hello” snapshotB.enter { color.value = Blue }
  36. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB “hello” Blue snapshotB.enter { color.value = Blue }
  37. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB “hello” Blue snapshotA.apply()
  38. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    snapshotA global snapshot snapshotB “hello” Blue snapshotA.apply()
  39. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB “hello” Blue snapshotA.apply()
  40. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB “hello” Blue snapshotA.apply()
  41. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB “hello” Blue snapshotA.apply()
  42. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotA.apply() snapshotB “hello” Blue
  43. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotA.apply() snapshotB “hello” Blue
  44. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() snapshotB “hello” Blue
  45. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() snapshotB “hello” Blue
  46. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() “hello” Blue
  47. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() “hello” Blue
  48. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() “hello” Blue
  49. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() “hello” Blue
  50. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot snapshotB.apply() “hello” Blue
  51. @zachklipp Walkthrough: create 2 children, apply message “” color Red

    global snapshot “hello” Blue snapshotB.apply()
  52. @zachklipp Walkthrough: created nested child, apply val message = mutableStateOf("")

    val color = mutableStateOf(Red) val snapshotA = Snapshot.takeMutableSnapshot() snapshotA.enter { message.value = "hello" val snapshotB = Snapshot.takeMutableSnapshot() snapshotB.enter { color.value = Blue } snapshotB.apply() } snapshotA.apply()
  53. @zachklipp val message = mutableStateOf("") val color = mutableStateOf(Red) message

    color global snapshot Walkthrough: created nested child, apply
  54. @zachklipp val message = mutableStateOf("") val color = mutableStateOf(Red) message

    “” color Red global snapshot Walkthrough: created nested child, apply
  55. @zachklipp snapshotA.enter { message.value = "hello" message “” color Red

    snapshotA global snapshot Walkthrough: created nested child, apply
  56. @zachklipp snapshotA.enter { message.value = "hello" message “” color Red

    snapshotA global snapshot Walkthrough: created nested child, apply
  57. @zachklipp snapshotA.enter { message.value = "hello" message “” color Red

    snapshotA global snapshot “hello” Walkthrough: created nested child, apply
  58. @zachklipp snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot() message “” color

    Red snapshotA global snapshot snapshotB “hello” Walkthrough: created nested child, apply
  59. @zachklipp snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot() message “” color

    Red snapshotA global snapshot snapshotB “hello” Walkthrough: created nested child, apply
  60. @zachklipp snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot() message “” color

    Red snapshotA global snapshot snapshotB “hello” Walkthrough: created nested child, apply
  61. @zachklipp snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot() message “” color

    Red snapshotA global snapshot snapshotB “hello” Walkthrough: created nested child, apply
  62. @zachklipp snapshotA.enter { snapshotB.enter { color.value = Blue } message

    “” color Red snapshotA global snapshot snapshotB “hello” Walkthrough: created nested child, apply
  63. @zachklipp snapshotA.enter { snapshotB.enter { color.value = Blue } message

    “” color Red snapshotA global snapshot snapshotB “hello” Walkthrough: created nested child, apply
  64. @zachklipp snapshotA.enter { snapshotB.enter { color.value = Blue } message

    “” color Red snapshotA global snapshot snapshotB “hello” Blue Walkthrough: created nested child, apply
  65. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot snapshotB “hello” Blue Walkthrough: created nested child, apply
  66. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot snapshotB “hello” Blue Walkthrough: created nested child, apply
  67. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot “hello” Blue Walkthrough: created nested child, apply
  68. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot “hello” Blue Walkthrough: created nested child, apply
  69. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot “hello” Blue Walkthrough: created nested child, apply
  70. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot “hello” Blue Walkthrough: created nested child, apply
  71. @zachklipp snapshotA.enter { snapshotB.apply() } message “” color Red snapshotA

    global snapshot “hello” Blue Walkthrough: created nested child, apply
  72. @zachklipp message “” color Red snapshotA global snapshot “hello” Blue

    snapshotA.apply() Walkthrough: created nested child, apply
  73. @zachklipp MutableState message “” “hello” interface MutableState<T> : State<T> {

    override var value: T operator fun component1(): T operator fun component2(): (T) -> Unit }
  74. @zachklipp SnapshotMutableState MutableState message “” “hello” interface SnapshotMutableState<T> : MutableState<T>

    { val policy: SnapshotMutationPolicy<T> } interface MutableState<T> : State<T> {
  75. @zachklipp SnapshotMutableStateImpl SnapshotMutableState message “” “hello” internal class SnapshotMutableStateImpl<T>( value:

    T, override val policy: SnapshotMutationPolicy<T> ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } } interface SnapshotMutableState<T> : MutableState<T> {
  76. @zachklipp SnapshotMutableStateImpl StateObject message “” “hello” internal class SnapshotMutableStateImpl<T>( val

    firstStateRecord: StateRecord fun prependStateRecord(value: StateRecord) fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? = null } interface SnapshotMutableState<T> : MutableState<T> {
  77. @zachklipp message “” “hello” interface StateObject { value: T, override

    val policy: SnapshotMutationPolicy<T> ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } } SnapshotMutableStateImpl StateObject :
  78. @zachklipp message “” “hello” interface StateObject { value: T, override

    val policy: SnapshotMutationPolicy<T> ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = TODO("???") override fun prependStateRecord(value: StateRecord) { TODO("???") } } SnapshotMutableStateImpl StateObject :
  79. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = TODO("???") override fun prependStateRecord(value: StateRecord) { TODO("???") } }
  80. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = TODO("???") override fun prependStateRecord(value: StateRecord) { TODO("???") } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = TODO("???") override fun assign(value: StateRecord) { TODO("???") } } }
  81. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = TODO("???") override fun prependStateRecord(value: StateRecord) { TODO("???") } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { TODO("???") } } }
  82. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = TODO("???") override fun prependStateRecord(value: StateRecord) { TODO("???") } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  83. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { private var next: StateStateRecord<T> = StateStateRecord(value) override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = TODO(“???") override fun prependStateRecord(value: StateRecord) { TODO("???") } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  84. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { private var next: StateStateRecord<T> = StateStateRecord(value) override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { TODO("???") } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  85. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { private var next: StateStateRecord<T> = StateStateRecord(value) override var value: T get() = TODO("???") set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as StateStateRecord<T> } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  86. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { private var next: StateStateRecord<T> = StateStateRecord(value) override var value: T get() = next.value set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as StateStateRecord<T> } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  87. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { private var next: StateStateRecord<T> = StateStateRecord(value) override var value: T get() = next.readable(this).value set(value) { TODO("???") } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as StateStateRecord<T> } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  88. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { private var next: StateStateRecord<T> = StateStateRecord(value) override var value: T get() = next.readable(this).value set(value) { next.writable(this) { this.value = value } } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as StateStateRecord<T> } private class StateStateRecord<T>(myValue: T) : StateRecord() { var value: T = myValue override fun create(): StateRecord = StateStateRecord(value) override fun assign(value: StateRecord) { this.value = (value as StateStateRecord<T>).value } } }
  89. @zachklipp internal class SnapshotMutableStateImpl<T>( val firstStateRecord: StateRecord fun prependStateRecord(value: StateRecord)

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? = null }
  90. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { override fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { } }
  91. @zachklipp internal class SnapshotMutableStateImpl<T>( value: T, override val policy: SnapshotMutationPolicy<T>

    ) : StateObject, SnapshotMutableState<T> { override fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { val previousRecord = previous as StateStateRecord<T> val currentRecord = current as StateStateRecord<T> val appliedRecord = applied as StateStateRecord<T> return if (policy.equivalent(currentRecord.value, appliedRecord.value)) current else { val merged = policy.merge( previousRecord.value, currentRecord.value, appliedRecord.value ) if (merged != null) { appliedRecord.create().also { (it as StateStateRecord<T>).value = merged } } else { null } } } }
  92. @zachklipp Custom state objects class Range { private var next

    = Record() var start: Int = 0 var end: Int = 0 private class Record : StateRecord() { var start: Int = 0 var end: Int = 0 override fun create(): StateRecord = Record() override fun assign(value: StateRecord) { value as Record start = value.start end = value.end } } }
  93. @zachklipp Custom state objects class Range { private var next

    = Record() var start: Int get() = next.readable(this).start set(value) { next.writable(this) { start = value } } var end: Int get() = next.readable(this).end set(value) { next.writable(this) { end = value } } private class Record : StateRecord() { var start: Int = 0 var end: Int = 0 override fun create(): StateRecord = Record() override fun assign(value: StateRecord) { value as Record start = value.start end = value.end } } }
  94. @zachklipp Custom state objects class Range : StateObject { private

    var next = Record() var start: Int get() = next.readable(this).start set(value) { next.writable(this) { start = value } } var end: Int get() = next.readable(this).end set(value) { next.writable(this) { end = value } } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as Record } private class Record : StateRecord() { var start: Int = 0 var end: Int = 0 override fun create(): StateRecord = Record() override fun assign(value: StateRecord) { value as Record start = value.start end = value.end } } }
  95. @zachklipp Custom state objects class Range : StateObject { private

    var next = Record() var start: Int get() = next.readable(this).start set(value) { next.writable(this) { start = value } } var end: Int get() = next.readable(this).end set(value) { next.writable(this) { end = value } } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as Record } private class Record : StateRecord() { var start: Int = 0 var end: Int = 0 override fun create(): StateRecord = Record() override fun assign(value: StateRecord) { value as Record start = value.start end = value.end } } }
  96. @zachklipp Custom state objects class Range : StateObject { private

    var next = Record() var start: Int get() = next.readable(this).start set(value) { require(value <= next.readable(this).end) next.writable(this) { start = value } } var end: Int get() = next.readable(this).end set(value) { require(value >= next.readable(this).start) next.writable(this) { end = value } } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as Record } private class Record : StateRecord() { var start: Int = 0 var end: Int = 0 override fun create(): StateRecord = Record() override fun assign(value: StateRecord) { value as Record start = value.start end = value.end } } }
  97. @zachklipp Custom state objects class Range : StateObject { private

    var next = Record() var start: Int get() = next.readable(this).start set(value) { next.withCurrent { require(value <= it.end) } next.writable(this) { start = value } } var end: Int get() = next.readable(this).end set(value) { next.withCurrent { require(value >= it.start) } next.writable(this) { end = value } } override val firstStateRecord: StateRecord get() = next override fun prependStateRecord(value: StateRecord) { next = value as Record } private class Record : StateRecord() { var start: Int = 0 var end: Int = 0 override fun create(): StateRecord = Record() override fun assign(value: StateRecord) { value as Record start = value.start end = value.end } } }
  98. @zachklipp Custom state objects class Range : StateObject { override

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { } }
  99. @zachklipp Custom state objects class Range : StateObject { override

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { val previousRecord = previous as Record val currentRecord = current as Record val appliedRecord = applied as Record if (currentRecord.start == appliedRecord.start && currentRecord.end == appliedRecord.end ) { return current } } }
  100. @zachklipp Custom state objects class Range : StateObject { override

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { val previousRecord = previous as Record val currentRecord = current as Record val appliedRecord = applied as Record if (currentRecord.start == appliedRecord.start && currentRecord.end == appliedRecord.end ) { return current } val currentChangedStart = previousRecord.start != currentRecord.start val currentChangedEnd = previousRecord.end != currentRecord.end val appliedChangedStart = previousRecord.start != appliedRecord.start val appliedChangedEnd = previousRecord.end != appliedRecord.end if ((currentChangedStart && appliedChangedStart) || (currentChangedEnd && appliedChangedEnd) ) { return null } } }
  101. @zachklipp Custom state objects class Range : StateObject { override

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { val previousRecord = previous as Record val currentRecord = current as Record val appliedRecord = applied as Record if (currentRecord.start == appliedRecord.start && currentRecord.end == appliedRecord.end ) { return current } val currentChangedStart = previousRecord.start != currentRecord.start val currentChangedEnd = previousRecord.end != currentRecord.end val appliedChangedStart = previousRecord.start != appliedRecord.start val appliedChangedEnd = previousRecord.end != appliedRecord.end if ((currentChangedStart && appliedChangedStart) || (currentChangedEnd && appliedChangedEnd) ) { return null } return Record().apply { start = if (currentChangedStart) currentRecord.start else appliedRecord.start end = if (currentChangedEnd) currentRecord.end else appliedRecord.end } } }
  102. @zachklipp State diffing suspend fun withAnimation(changes: () -> Unit) {

    val statesToAnimate = mutableSetOf<Any>() val targetValues = mutableMapOf<Any, Any>() val snapshot = Snapshot.takeMutableSnapshot( writeObserver = { stateObject -> statesToAnimate += stateObject } ) snapshot.enter { changes() statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value } } snapshot.dispose() coroutineScope { statesToAnimate.forEach { stateObject -> launch { animate(stateObject.value, targetValues[stateObject]) } } } }
  103. @zachklipp State diffing suspend fun withAnimation(changes: () -> Unit) {

    val statesToAnimate = mutableSetOf<Any>() val targetValues = mutableMapOf<Any, Any>() val snapshot = Snapshot.takeMutableSnapshot( writeObserver = { stateObject -> statesToAnimate += stateObject } ) snapshot.enter { changes() statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value } } snapshot.dispose() coroutineScope { statesToAnimate.forEach { stateObject -> launch { animate(stateObject.value, targetValues[stateObject]) } } } }
  104. @zachklipp State diffing suspend fun withAnimation(changes: () -> Unit) {

    val statesToAnimate = mutableSetOf<Any>() val targetValues = mutableMapOf<Any, Any>() val snapshot = Snapshot.takeMutableSnapshot( writeObserver = { stateObject -> statesToAnimate += stateObject } ) snapshot.enter { changes() statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value } } snapshot.dispose() coroutineScope { statesToAnimate.forEach { stateObject -> launch { animate(stateObject.value, targetValues[stateObject]) } } } }
  105. @zachklipp State diffing suspend fun withAnimation(changes: () -> Unit) {

    val statesToAnimate = mutableSetOf<Any>() val targetValues = mutableMapOf<Any, Any>() val snapshot = Snapshot.takeMutableSnapshot( writeObserver = { stateObject -> statesToAnimate += stateObject } ) snapshot.enter { changes() statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value } } snapshot.dispose() coroutineScope { statesToAnimate.forEach { stateObject -> launch { animate(stateObject.value, targetValues[stateObject]) } } } }
  106. @zachklipp State diffing suspend fun withAnimation(changes: () -> Unit) {

    val statesToAnimate = mutableSetOf<Any>() val targetValues = mutableMapOf<Any, Any>() val snapshot = Snapshot.takeMutableSnapshot( writeObserver = { stateObject -> statesToAnimate += stateObject } ) snapshot.enter { changes() statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value } } snapshot.dispose() coroutineScope { statesToAnimate.forEach { stateObject -> launch { animate(stateObject.value, targetValues[stateObject]) } } } }
  107. @zachklipp Persistence fun observeUserAsState(name: String): State<User?> { val cached =

    cachedStates.getOrPut(name) { val flow = users.findByName(name) val state = mutableStateOf<User?>(null) val job = scope.launch { flow.collect { state.value = it } } return@getOrPut UserState(state, job) } return cached.state } Read
  108. @zachklipp Persistence Read fun observeUserAsState(name: String): State<User?> { val cached

    = cachedStates.getOrPut(name) { val flow = users.findByName(name) val state = mutableStateOf<User?>(null) val job = scope.launch { flow.collect { state.value = it } } return@getOrPut UserState(state, job) } return cached.state }
  109. @zachklipp Persistence Read fun observeUserAsState(name: String): State<User?> { val cached

    = cachedStates.getOrPut(name) { val flow = users.findByName(name) val state = mutableStateOf<User?>(null) val job = scope.launch { flow.collect { state.value = it } } return@getOrPut UserState(state, job) } return cached.state }
  110. @zachklipp Persistence Read fun observeUserAsState(name: String): State<User?> { val cached

    = cachedStates.getOrPut(name) { val flow = users.findByName(name) val state = mutableStateOf<User?>(null) val job = scope.launch { flow.collect { state.value = it } } return@getOrPut UserState(state, job) } return cached.state }
  111. @zachklipp Persistence Read fun observeUserAsState(name: String): State<User?> { val cached

    = cachedStates.getOrPut(name) { val flow = users.findByName(name) val state = mutableStateOf<User?>(null) val job = scope.launch { flow.collect { state.value = it } } return@getOrPut UserState(state, job) } return cached.state }
  112. @zachklipp Persistence Update Snapshot.registerApplyObserver { changedStateObjects, snapshot -> val changedDbStates

    = changedStateObjects.mapNotNull { if (it in cachedStates) it as MutableState<User?> else null } if (changedDbStates.isNotEmpty()) { scope.launch(Dispatchers.IO) { val readSnapshot = snapshot.takeNestedSnapshot() try { readSnapshot.enter { changedDbStates.forEach { (user, _) -> user?.let { users.updateUser(user) } } } } finally { readSnapshot.dispose() } } } }
  113. @zachklipp Persistence Update Snapshot.registerApplyObserver { changedStateObjects, snapshot -> val changedDbStates

    = changedStateObjects.mapNotNull { if (it in cachedStates) it as MutableState<User?> else null } if (changedDbStates.isNotEmpty()) { scope.launch(Dispatchers.IO) { val readSnapshot = snapshot.takeNestedSnapshot() try { readSnapshot.enter { changedDbStates.forEach { (user, _) -> user?.let { users.updateUser(user) } } } } finally { readSnapshot.dispose() } } } }
  114. @zachklipp Persistence Update Snapshot.registerApplyObserver { changedStateObjects, snapshot -> val changedDbStates

    = changedStateObjects.mapNotNull { if (it in cachedStates) it as MutableState<User?> else null } if (changedDbStates.isNotEmpty()) { scope.launch(Dispatchers.IO) { val readSnapshot = snapshot.takeNestedSnapshot() try { readSnapshot.enter { changedDbStates.forEach { (user, _) -> user?.let { users.updateUser(user) } } } } finally { readSnapshot.dispose() } } } }
  115. @zachklipp Persistence Update Snapshot.registerApplyObserver { changedStateObjects, snapshot -> val changedDbStates

    = changedStateObjects.mapNotNull { if (it in cachedStates) it as MutableState<User?> else null } if (changedDbStates.isNotEmpty()) { scope.launch(Dispatchers.IO) { val readSnapshot = snapshot.takeNestedSnapshot() try { readSnapshot.enter { changedDbStates.forEach { (user, _) -> user?.let { users.updateUser(user) } } } } finally { readSnapshot.dispose() } } } }
  116. @zachklipp Persistence Update Snapshot.registerApplyObserver { changedStateObjects, snapshot -> val changedDbStates

    = changedStateObjects.mapNotNull { if (it in cachedStates) it as MutableState<User?> else null } if (changedDbStates.isNotEmpty()) { scope.launch(Dispatchers.IO) { val readSnapshot = snapshot.takeNestedSnapshot() try { readSnapshot.enter { changedDbStates.forEach { (user, _) -> user?.let { users.updateUser(user) } } } } finally { readSnapshot.dispose() } } } }
  117. @zachklipp Persistence Update Snapshot.registerApplyObserver { changedStateObjects, snapshot -> val changedDbStates

    = changedStateObjects.mapNotNull { if (it in cachedStates) it as MutableState<User?> else null } if (changedDbStates.isNotEmpty()) { scope.launch(Dispatchers.IO) { val readSnapshot = snapshot.takeNestedSnapshot() try { readSnapshot.enter { changedDbStates.forEach { (user, _) -> user?.let { users.updateUser(user) } } } } finally { readSnapshot.dispose() } } } }
  118. @zachklipp Persistence Usage @Composable fun UserNameEditor(database: SnapshotDatabase) { val userState

    = remember(database) { database.observeUserAsState("Rhaenyra") } userState.value?.let { user -> BasicTextField( value = user.name, onValueChange = { userState.value = user.copy(name = it) } ) } }
  119. @zachklipp Persistence Usage @Composable fun UserNameEditor(database: SnapshotDatabase) { val userState

    = remember(database) { database.observeUserAsState("Rhaenyra") } userState.value?.let { user -> BasicTextField( value = user.name, onValueChange = { userState.value = user.copy(name = it) } ) } }