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