1 /*
<lambda>null2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 @file:JvmName("PrimitiveSnapshotStateKt")
18 @file:JvmMultifileClass
19
20 package androidx.compose.runtime
21
22 import androidx.compose.runtime.internal.JvmDefaultWithCompatibility
23 import androidx.compose.runtime.internal.equalsWithNanFix
24 import androidx.compose.runtime.snapshots.AutoboxingStateValueProperty
25 import androidx.compose.runtime.snapshots.GlobalSnapshot
26 import androidx.compose.runtime.snapshots.Snapshot
27 import androidx.compose.runtime.snapshots.SnapshotId
28 import androidx.compose.runtime.snapshots.SnapshotMutableState
29 import androidx.compose.runtime.snapshots.StateFactoryMarker
30 import androidx.compose.runtime.snapshots.StateObjectImpl
31 import androidx.compose.runtime.snapshots.StateRecord
32 import androidx.compose.runtime.snapshots.currentSnapshot
33 import androidx.compose.runtime.snapshots.overwritable
34 import androidx.compose.runtime.snapshots.readable
35 import androidx.compose.runtime.snapshots.toSnapshotId
36 import androidx.compose.runtime.snapshots.withCurrent
37 import kotlin.jvm.JvmMultifileClass
38 import kotlin.jvm.JvmName
39 import kotlin.reflect.KProperty
40
41 /**
42 * Return a new [MutableFloatState] initialized with the passed in [value]
43 *
44 * The MutableFloatState class is a single value holder whose reads and writes are observed by
45 * Compose. Additionally, writes to it are transacted as part of the [Snapshot] system. On the JVM,
46 * values are stored in memory as the primitive `float` type, avoiding the autoboxing that occurs
47 * when using `MutableState<Float>`.
48 *
49 * @param value the initial value for the [MutableFloatState]
50 * @see FloatState
51 * @see MutableFloatState
52 * @see mutableStateOf
53 * @see mutableIntStateOf
54 * @see mutableLongStateOf
55 * @see mutableDoubleStateOf
56 */
57 @StateFactoryMarker
58 fun mutableFloatStateOf(value: Float): MutableFloatState = createSnapshotMutableFloatState(value)
59
60 /**
61 * A value holder where reads to the [floatValue] property during the execution of a [Composable]
62 * function cause the current [RecomposeScope] to subscribe to changes of that value.
63 *
64 * @see MutableFloatState
65 * @see mutableDoubleStateOf
66 */
67 @Stable
68 @JvmDefaultWithCompatibility
69 interface FloatState : State<Float> {
70 @get:AutoboxingStateValueProperty("floatValue")
71 override val value: Float
72 @Suppress("AutoBoxing") get() = floatValue
73
74 val floatValue: Float
75 }
76
77 /** Permits property delegation of `val`s using `by` for [FloatState]. */
78 @Suppress("NOTHING_TO_INLINE")
getValuenull79 inline operator fun FloatState.getValue(thisObj: Any?, property: KProperty<*>): Float = floatValue
80
81 /**
82 * A value holder where reads to the [floatValue] property during the execution of a [Composable]
83 * function cause the current [RecomposeScope] to subscribe to changes of that value. When the
84 * [floatValue] property is written to and changed, a recomposition of any subscribed
85 * [RecomposeScope]s will be scheduled. If [floatValue] is written to with the same value, no
86 * recompositions will be scheduled.
87 *
88 * @see [FloatState]
89 * @see [mutableDoubleStateOf]
90 */
91 @Stable
92 @JvmDefaultWithCompatibility
93 interface MutableFloatState : FloatState, MutableState<Float> {
94 @get:AutoboxingStateValueProperty("floatValue")
95 @set:AutoboxingStateValueProperty("floatValue")
96 override var value: Float
97 @Suppress("AutoBoxing") get() = floatValue
98 set(value) {
99 floatValue = value
100 }
101
102 override var floatValue: Float
103 }
104
105 /** Permits property delegation of `var`s using `by` for [MutableFloatState]. */
106 @Suppress("NOTHING_TO_INLINE")
setValuenull107 inline operator fun MutableFloatState.setValue(
108 thisObj: Any?,
109 property: KProperty<*>,
110 value: Float
111 ) {
112 this.floatValue = value
113 }
114
createSnapshotMutableFloatStatenull115 internal expect fun createSnapshotMutableFloatState(value: Float): MutableFloatState
116
117 /**
118 * A single value holder whose reads and writes are observed by Compose.
119 *
120 * Additionally, writes to it are transacted as part of the [Snapshot] system.
121 *
122 * @param value the wrapped value
123 * @see [mutableDoubleStateOf]
124 */
125 internal open class SnapshotMutableFloatStateImpl(value: Float) :
126 StateObjectImpl(), MutableFloatState, SnapshotMutableState<Float> {
127
128 private var next =
129 currentSnapshot().let { snapshot ->
130 FloatStateStateRecord(snapshot.snapshotId, value).also {
131 if (snapshot !is GlobalSnapshot) {
132 it.next =
133 FloatStateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
134 }
135 }
136 }
137
138 override val firstStateRecord: StateRecord
139 get() = next
140
141 override var floatValue: Float
142 get() = next.readable(this).value
143 set(value) =
144 next.withCurrent {
145 if (!it.value.equalsWithNanFix(value)) {
146 next.overwritable(this, it) { this.value = value }
147 }
148 }
149
150 // Arbitrary policies are not allowed. The underlying `==` implementation
151 // for primitive types corresponds to structural equality
152 override val policy: SnapshotMutationPolicy<Float>
153 get() = structuralEqualityPolicy()
154
155 override fun component1(): Float = floatValue
156
157 override fun component2(): (Float) -> Unit = { floatValue = it }
158
159 override fun prependStateRecord(value: StateRecord) {
160 next = value as FloatStateStateRecord
161 }
162
163 override fun mergeRecords(
164 previous: StateRecord,
165 current: StateRecord,
166 applied: StateRecord
167 ): StateRecord? {
168 val currentRecord = current as FloatStateStateRecord
169 val appliedRecord = applied as FloatStateStateRecord
170 return if (currentRecord.value.equalsWithNanFix(appliedRecord.value)) {
171 current
172 } else {
173 null
174 }
175 }
176
177 override fun toString(): String =
178 next.withCurrent { "MutableFloatState(value=${it.value})@${hashCode()}" }
179
180 private class FloatStateStateRecord(snapshotId: SnapshotId, var value: Float) :
181 StateRecord(snapshotId) {
182 override fun assign(value: StateRecord) {
183 this.value = (value as FloatStateStateRecord).value
184 }
185
186 override fun create(): StateRecord = create(currentSnapshot().snapshotId)
187
188 override fun create(snapshotId: SnapshotId): StateRecord =
189 FloatStateStateRecord(snapshotId, value)
190 }
191 }
192