1 /*
2 * 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 package androidx.compose.runtime
18
19 import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentMap
20 import androidx.compose.runtime.internal.persistentCompositionLocalHashMapOf
21
22 /**
23 * A read-only, immutable snapshot of the [CompositionLocals][CompositionLocal] that are set at a
24 * specific position in the composition hierarchy.
25 */
26 sealed interface CompositionLocalMap {
27 /**
28 * Returns the value of the provided [composition local][key] at the position in the composition
29 * hierarchy represented by this [CompositionLocalMap] instance. If the provided [key] is not
30 * set at this point in the hierarchy, its default value will be used.
31 *
32 * For [non-static CompositionLocals][compositionLocalOf], this function will return the latest
33 * value of the CompositionLocal, which might change over time across the same instance of the
34 * CompositionLocalMap. Reads done in this way are not tracked in the snapshot system.
35 *
36 * For [static CompositionLocals][staticCompositionLocalOf], this function returns the value at
37 * the time of creation of the CompositionLocalMap. When a static CompositionLocal is
38 * reassigned, the entire composition hierarchy is recomposed and a new CompositionLocalMap is
39 * created with the updated value of the static CompositionLocal.
40 */
getnull41 operator fun <T> get(key: CompositionLocal<T>): T
42
43 companion object {
44 /** An empty [CompositionLocalMap] instance which contains no keys or values. */
45 val Empty: CompositionLocalMap = persistentCompositionLocalHashMapOf()
46 }
47 }
48
49 /**
50 * A [CompositionLocal] map is is an immutable map that maps [CompositionLocal] keys to a provider
51 * of their current value. It is used to represent the combined scope of all provided
52 * [CompositionLocal]s.
53 */
54 internal interface PersistentCompositionLocalMap :
55 PersistentMap<CompositionLocal<Any?>, ValueHolder<Any?>>,
56 CompositionLocalMap,
57 CompositionLocalAccessorScope {
58
putValuenull59 fun putValue(
60 key: CompositionLocal<Any?>,
61 value: ValueHolder<Any?>
62 ): PersistentCompositionLocalMap
63
64 override val <T> CompositionLocal<T>.currentValue: T
65 get() = read(this)
66
67 // Override the builder APIs so that we can create new PersistentMaps that retain the type
68 // information of PersistentCompositionLocalMap. If we use the built-in implementation, we'll
69 // get back a PersistentMap<CompositionLocal<Any?>, State<Any?>> instead of a
70 // PersistentCompositionLocalMap
71 override fun builder(): Builder
72
73 interface Builder : PersistentMap.Builder<CompositionLocal<Any?>, ValueHolder<Any?>> {
74 override fun build(): PersistentCompositionLocalMap
75 }
76 }
77
mutatenull78 internal inline fun PersistentCompositionLocalMap.mutate(
79 mutator: (MutableMap<CompositionLocal<Any?>, ValueHolder<Any?>>) -> Unit
80 ): PersistentCompositionLocalMap = builder().apply(mutator).build()
81
82 @Suppress("UNCHECKED_CAST")
83 internal fun <T> PersistentCompositionLocalMap.contains(key: CompositionLocal<T>) =
84 this.containsKey(key as CompositionLocal<Any?>)
85
86 @Suppress("UNCHECKED_CAST")
87 internal fun <T> PersistentCompositionLocalMap.read(key: CompositionLocal<T>): T =
88 getOrElse(key as CompositionLocal<Any?>) { key.defaultValueHolder }.readValue(this) as T
89
updateCompositionMapnull90 internal fun updateCompositionMap(
91 values: Array<out ProvidedValue<*>>,
92 parentScope: PersistentCompositionLocalMap,
93 previous: PersistentCompositionLocalMap = persistentCompositionLocalHashMapOf(),
94 ): PersistentCompositionLocalMap {
95 val builder: PersistentCompositionLocalMap.Builder =
96 persistentCompositionLocalHashMapOf().builder()
97 val map: PersistentMap<CompositionLocal<Any?>, ValueHolder<Any?>> = previous
98 @Suppress("UNCHECKED_CAST")
99 for (index in values.indices) {
100 val provided = values[index]
101 val local = provided.compositionLocal as ProvidableCompositionLocal<Any?>
102 if (provided.canOverride || !parentScope.contains(local)) {
103 val previousState = map[local]
104 val newState = local.updatedStateOf(provided as ProvidedValue<Any?>, previousState)
105 builder[local] = newState
106 }
107 }
108 return builder.build()
109 }
110