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

Opening the shutter on snapshots

Opening the shutter on snapshots

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. Zach Klippenstein (he/him) Google Opening the shutter on snapshots

  2. @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
  3. @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
  4. @zachklipp What for? What do they do? How do they

    work? What can we do with them?
  5. @zachklipp

  6. @zachklipp

  7. @zachklipp

  8. Snapshots are Git for your variables @zachklipp

  9. Composition is the release manager @zachklipp

  10. @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
  11. @zachklipp ACID “In-memory”

  12. @zachklipp ACID tomicity On-disk “a”, Red “b”, Red “b”, Blue

    “b”, Blue
  13. @zachklipp A CID onsistency On-disk “a” “b” “c” “c” “c”

    tomicity
  14. @zachklipp AC ID solation On-disk “a”, Red “a”, Blue “b”,

    Red “b”, Blue onsistency urability
  15. @zachklipp ACI D urability On-disk solation

  16. @zachklipp ACI D urability On-disk In-memory solation

  17. @zachklipp ACID “In-memory” tomicity onsistency solation urability

  18. @zachklipp Database Snapshots ACID ACID transactional snapshots commit/rollback apply/dispose views

    derived state
  19. @zachklipp How do they work?

  20. @zachklipp

  21. @zachklipp

  22. No magic @zachklipp

  23. Compiler plugin? @zachklipp from A Hitchhiker’s Guide to Jetpack Compose

  24. No compiler plugin @zachklipp

  25. @zachklipp (but shipped with it) Snapshots ≠ Compose

  26. @zachklipp (but shipped with it) Snapshots ≠ Compose snapshots the

    rest of Compose ThreadLocal integers linked lists
  27. @zachklipp snapshots ThreadLocal integers linked lists

  28. @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
  29. @zachklipp mutableStateOf mutableStateOf Range “a” “b” “foo” “bar” snapA snapB

    snapC
  30. @zachklipp Snapshots State Objects mutableStateOf mutableStateOf Range “a” “b” “foo”

    “bar” snapA snapB snapC
  31. @zachklipp Snapshots State Objects records mutableStateOf mutableStateOf Range “a” “b”

    “foo” “bar” snapA snapB snapC
  32. @zachklipp reads: 96 snapshots records 128 167 181 64 75

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

    75 111 209 52 53 87 state “foo”
  34. @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”
  35. @zachklipp Snapshots API State Objects takeNestedSnapshot takeNestedMutableSnapshot withMutableSnapshot registerApplyObserver val

    state = mutableStateOf(“”) 
 mutableStateListOf mutableStateMapOf …whatever!
  36. @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
  37. @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
  38. @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
  39. @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
  40. @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
  41. @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
  42. @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
  43. @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
  44. 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
  45. @zachklipp Git: commit SHAs

  46. @zachklipp Snapshot IDs

  47. @zachklipp Walkthrough: create child, apply val message = mutableStateOf("") val

    snapshot = Snapshot.takeMutableSnapshot() snapshot.enter { message.value = "hello" } snapshot.apply() message
  48. @zachklipp Walkthrough: create child, apply message val message = mutableStateOf("")

    global snapshot
  49. @zachklipp Walkthrough: create child, apply message val message = mutableStateOf("")

    global snapshot
  50. @zachklipp Walkthrough: create child, apply message val message = mutableStateOf("")

    “” global snapshot
  51. @zachklipp Walkthrough: create child, apply message val snapshot = Snapshot.takeMutableSnapshot()

    “” global snapshot snapshot
  52. @zachklipp Walkthrough: create child, apply message val snapshot = Snapshot.takeMutableSnapshot()

    “” global snapshot snapshot
  53. @zachklipp Walkthrough: create child, apply message val snapshot = Snapshot.takeMutableSnapshot()

    “” global snapshot snapshot
  54. @zachklipp Walkthrough: create child, apply message val snapshot = Snapshot.takeMutableSnapshot()

    “” global snapshot snapshot
  55. @zachklipp Walkthrough: create child, apply message val snapshot = Snapshot.takeMutableSnapshot()

    “” global snapshot snapshot
  56. @zachklipp Walkthrough: create child, apply message message.value = "hello" “”

    global snapshot snapshot
  57. @zachklipp Walkthrough: create child, apply message message.value = "hello" “hello”

    “” global snapshot snapshot
  58. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot snapshot
  59. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot snapshot
  60. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot
  61. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot
  62. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot
  63. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot
  64. @zachklipp Walkthrough: create child, apply message snapshot.apply() “” “hello” global

    snapshot
  65. @zachklipp Walkthrough: create child, apply message snapshot.apply() “hello” “” global

    snapshot
  66. @zachklipp Walkthrough: create child, apply message “hello” “” merged! global

    snapshot
  67. @zachklipp Snapshot rules: Create child Apply Advance

  68. @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
  69. @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()
  70. @zachklipp Walkthrough: create 2 children, apply val message = mutableStateOf("")

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

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

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

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

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

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

    message “” color Red snapshotA global snapshot
  77. @zachklipp Walkthrough: create 2 children, apply message “” color Red

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    global snapshot “hello” Blue merged!
  105. @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()
  106. @zachklipp val message = mutableStateOf("") val color = mutableStateOf(Red) message

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    snapshotA.apply() Walkthrough: created nested child, apply
  131. @zachklipp snapshotA.apply() message “” color Red snapshotA global snapshot “hello”

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

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

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

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

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

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

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

    Walkthrough: created nested child, apply
  139. @zachklipp message “” color Red global snapshot “hello” Blue merged!

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

  141. @zachklipp MutableState message “” “hello” interface MutableState<T> : State<T> {

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

    { val policy: SnapshotMutationPolicy<T> } interface MutableState<T> : State<T> {
  143. @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> {
  144. @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> {
  145. @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 :
  146. @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 :
  147. @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("???") } }
  148. @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("???") } } }
  149. @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("???") } } }
  150. @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 } } }
  151. @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 } } }
  152. @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 } } }
  153. @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 } } }
  154. @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 } } }
  155. @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 } } }
  156. @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 } } }
  157. @zachklipp internal class SnapshotMutableStateImpl<T>( val firstStateRecord: StateRecord fun prependStateRecord(value: StateRecord)

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? = null }
  158. @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? { } }
  159. @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 } } } }
  160. @zachklipp What can we do with them?

  161. @zachklipp Custom state objects class Range { var start: Int

    = 0 var end: Int = 0 }
  162. @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 } } }
  163. @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 } } }
  164. @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 } } }
  165. @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 } } }
  166. @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 } } }
  167. @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 } } }
  168. @zachklipp Custom state objects class Range : StateObject { }

  169. @zachklipp Custom state objects class Range : StateObject { override

    fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { } }
  170. @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 } } }
  171. @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 } } }
  172. @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 } } }
  173. @zachklipp State diffing github.com/zach-klippenstein/compose-autotransition

  174. @zachklipp State diffing github.com/zach-klippenstein/compose-autotransition

  175. @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]) } } } }
  176. @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]) } } } }
  177. @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]) } } } }
  178. @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]) } } } }
  179. @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]) } } } }
  180. @zachklipp State diffing github.com/zach-klippenstein/compose-autotransition

  181. @zachklipp State diffing github.com/zach-klippenstein/compose-autotransition

  182. @zachklipp Persistence “In-memory” tomicity onsistency solation urability ACID

  183. @zachklipp Persistence “In-memory” tomicity onsistency solation urability ACID

  184. @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
  185. @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 }
  186. @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 }
  187. @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 }
  188. @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 }
  189. @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() } } } }
  190. @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() } } } }
  191. @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() } } } }
  192. @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() } } } }
  193. @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() } } } }
  194. @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() } } } }
  195. @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) } ) } }
  196. @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) } ) } }
  197. Snapshots are Git for your variables @zachklipp

  198. Further reading: More info about implementing custom StateObjects: Slides: Whitepaper

    on multiversion concurrency control: @zachklipp
  199. Questions? twitter.com/ @zachklipp More info about implementing custom StateObjects: Slides:

    Whitepaper on multiversion concurrency control: