1 /*
<lambda>null2  * Copyright 2020 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:OptIn(InternalComposeApi::class)
18 
19 package androidx.compose.runtime.internal
20 
21 import androidx.compose.runtime.Composable
22 import androidx.compose.runtime.ComposeCompilerApi
23 import androidx.compose.runtime.Composer
24 import androidx.compose.runtime.InternalComposeApi
25 import androidx.compose.runtime.RecomposeScope
26 import androidx.compose.runtime.Stable
27 import androidx.compose.runtime.remember
28 import androidx.compose.runtime.updateChangedFlags
29 import kotlin.jvm.functions.FunctionN
30 
31 @Stable
32 internal class ComposableLambdaNImpl(
33     val key: Int,
34     private val tracked: Boolean,
35     override val arity: Int
36 ) : ComposableLambdaN {
37     private var _block: Any? = null
38     private var scope: RecomposeScope? = null
39     private var scopes: MutableList<RecomposeScope>? = null
40 
41     private fun trackWrite() {
42         if (tracked) {
43             val scope = this.scope
44             if (scope != null) {
45                 scope.invalidate()
46                 this.scope = null
47             }
48             val scopes = this.scopes
49             if (scopes != null) {
50                 for (index in 0 until scopes.size) {
51                     val item = scopes[index]
52                     item.invalidate()
53                 }
54                 scopes.clear()
55             }
56         }
57     }
58 
59     private fun trackRead(composer: Composer) {
60         if (tracked) {
61             val scope = composer.recomposeScope
62             if (scope != null) {
63                 // Find the first invalid scope and replace it or record it if no scopes are invalid
64                 composer.recordUsed(scope)
65                 val lastScope = this.scope
66                 if (lastScope.replacableWith(scope)) {
67                     this.scope = scope
68                 } else {
69                     val lastScopes = scopes
70                     if (lastScopes == null) {
71                         val newScopes = mutableListOf<RecomposeScope>()
72                         scopes = newScopes
73                         newScopes.add(scope)
74                     } else {
75                         for (index in 0 until lastScopes.size) {
76                             val scopeAtIndex = lastScopes[index]
77                             if (scopeAtIndex.replacableWith(scope)) {
78                                 lastScopes[index] = scope
79                                 return
80                             }
81                         }
82                         lastScopes.add(scope)
83                     }
84                 }
85             }
86         }
87     }
88 
89     fun update(block: Any) {
90         if (block != _block) {
91             val oldBlockNull = _block == null
92             _block = block as FunctionN<*>
93             if (!oldBlockNull) {
94                 trackWrite()
95             }
96         }
97     }
98 
99     private fun realParamCount(params: Int): Int {
100         var realParams = params
101         realParams-- // composer parameter
102         realParams-- // changed parameter
103         var changedParams = 1
104         while (changedParams * SLOTS_PER_INT < realParams) {
105             realParams--
106             changedParams++
107         }
108         return realParams
109     }
110 
111     override fun invoke(vararg args: Any?): Any? {
112         val realParams = realParamCount(args.size)
113         var c = args[realParams] as Composer
114         val allArgsButLast = args.slice(0 until args.size - 1).toTypedArray()
115         val lastChanged = args[args.size - 1] as Int
116         c = c.startRestartGroup(key)
117         trackRead(c)
118         val dirty =
119             lastChanged or if (c.changed(this)) differentBits(realParams) else sameBits(realParams)
120         @Suppress("UNCHECKED_CAST") val result = (_block as FunctionN<*>)(*allArgsButLast, dirty)
121         c.endRestartGroup()?.updateScope { nc, _ ->
122             val params = args.slice(0 until realParams).toTypedArray()
123             @Suppress("UNUSED_VARIABLE")
124             val changed = updateChangedFlags(args[realParams + 1] as Int)
125             val changedN =
126                 Array<Any?>(args.size - realParams - 2) { index ->
127                     updateChangedFlags(args[realParams + 2 + index] as Int)
128                 }
129             this(*params, nc, changed or 0b1, *changedN)
130         }
131         return result
132     }
133 }
134 
135 @Stable @ComposeCompilerApi interface ComposableLambdaN : FunctionN<Any?>
136 
137 @Suppress("unused")
138 @ComposeCompilerApi
composableLambdaNnull139 fun composableLambdaN(
140     composer: Composer,
141     key: Int,
142     tracked: Boolean,
143     arity: Int,
144     block: Any
145 ): ComposableLambdaN {
146     composer.startReplaceableGroup(key)
147     val slot = composer.rememberedValue()
148     val result =
149         if (slot === Composer.Empty) {
150             val value = ComposableLambdaNImpl(key, tracked, arity)
151             composer.updateRememberedValue(value)
152             value
153         } else {
154             @Suppress("UNCHECKED_CAST")
155             slot as ComposableLambdaNImpl
156         }
157     result.update(block)
158     composer.endReplaceableGroup()
159     return result
160 }
161 
162 @Suppress
163 @ComposeCompilerApi
164 @Composable
rememberComposableLambdaNnull165 fun rememberComposableLambdaN(
166     key: Int,
167     tracked: Boolean,
168     arity: Int,
169     block: Any
170 ): ComposableLambdaN =
171     remember { ComposableLambdaNImpl(key, tracked, arity) }.also { it.update(block) }
172 
173 @Suppress("unused")
174 @ComposeCompilerApi
composableLambdaNInstancenull175 fun composableLambdaNInstance(
176     key: Int,
177     tracked: Boolean,
178     arity: Int,
179     block: Any
180 ): ComposableLambdaN = ComposableLambdaNImpl(key, tracked, arity).apply { update(block) }
181