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