1 /* <lambda>null2 * Copyright (C) 2024 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 com.android.mechanics 18 19 import androidx.compose.runtime.FloatState 20 import androidx.compose.runtime.derivedStateOf 21 import androidx.compose.runtime.getValue 22 import androidx.compose.runtime.mutableFloatStateOf 23 import androidx.compose.runtime.mutableLongStateOf 24 import androidx.compose.runtime.mutableStateOf 25 import androidx.compose.runtime.referentialEqualityPolicy 26 import androidx.compose.runtime.setValue 27 import androidx.compose.runtime.snapshotFlow 28 import androidx.compose.runtime.withFrameNanos 29 import com.android.mechanics.debug.DebugInspector 30 import com.android.mechanics.debug.FrameData 31 import com.android.mechanics.impl.Computations 32 import com.android.mechanics.impl.DiscontinuityAnimation 33 import com.android.mechanics.impl.GuaranteeState 34 import com.android.mechanics.spec.Breakpoint 35 import com.android.mechanics.spec.Guarantee 36 import com.android.mechanics.spec.InputDirection 37 import com.android.mechanics.spec.Mapping 38 import com.android.mechanics.spec.MotionSpec 39 import com.android.mechanics.spec.SegmentData 40 import com.android.mechanics.spring.SpringState 41 import java.util.concurrent.atomic.AtomicInteger 42 import kotlinx.coroutines.CoroutineName 43 import kotlinx.coroutines.flow.first 44 import kotlinx.coroutines.withContext 45 46 /** 47 * Computes an animated [output] value, by mapping the [currentInput] according to the [spec]. 48 * 49 * A [MotionValue] represents a single animated value within a larger animation. It takes a 50 * numerical [currentInput] value, typically a spatial value like width, height, or gesture length, 51 * and transforms it into an [output] value using a [MotionSpec]. 52 * 53 * ## Mapping Input to Output 54 * 55 * The [MotionSpec] defines the relationship between the input and output values. It does this by 56 * specifying a series of [Mapping] functions and [Breakpoint]s. Breakpoints divide the input domain 57 * into segments. Each segment has an associated [Mapping] function, which determines how input 58 * values within that segment are transformed into output values. 59 * 60 * These [Mapping] functions can be arbitrary, as long as they are 61 * 1. deterministic: When invoked repeatedly for the same input, they must produce the same output. 62 * 2. continuous: meaning infinitesimally small changes in input result in infinitesimally small 63 * changes in output 64 * 65 * A valid [Mapping] function is one whose graph could be drawn without lifting your pen from the 66 * paper, meaning there are no abrupt jumps or breaks. 67 * 68 * ## Animating Discontinuities 69 * 70 * When the input value crosses a breakpoint, there might be a discontinuity in the output value due 71 * to the switch between mapping functions. `MotionValue` automatically animates these 72 * discontinuities using a spring animation. The spring parameters are defined for each 73 * [Breakpoint]. 74 * 75 * ## Guarantees for Choreography 76 * 77 * Breakpoints can also define [Guarantee]s. These guarantees can make the spring animation finish 78 * faster, in response to quick input value changes. Thus, [Guarantee]s allows to maintain a 79 * predictable choreography, even as the input is unpredictably changed by a user's gesture. 80 * 81 * ## Updating the MotionSpec 82 * 83 * The [spec] property can be changed at any time. If the new spec produces a different output for 84 * the current input, the difference will be animated using the spring parameters defined in 85 * [MotionSpec.resetSpring]. 86 * 87 * ## Gesture Context 88 * 89 * The [GestureContext] augments the [currentInput] value with the user's intent. The 90 * [GestureContext] is created wherever gesture input is handled. If the motion value is not driven 91 * by a gesture, it is OK for the [GestureContext] to return static values. 92 * 93 * ## Usage 94 * 95 * The [MotionValue] does animate the [output] implicitly, whenever a change in [currentInput], 96 * [spec], or [gestureContext] requires it. The animated value is computed whenever the [output] 97 * property is read, or the latest once the animation frame is complete. 98 * 1. Create an instance, providing the input value, gesture context, and an initial spec. 99 * 2. Call [keepRunning] in a coroutine scope, and keep the coroutine running while the 100 * `MotionValue` is in use. 101 * 3. Access the animated output value through the [output] property. 102 * 103 * Internally, the [keepRunning] coroutine is automatically suspended if there is nothing to 104 * animate. 105 * 106 * @param currentInput Provides the current input value. 107 * @param gestureContext The [GestureContext] augmenting the [currentInput]. 108 * @param label An optional label to aid in debugging. 109 * @param stableThreshold A threshold value (in output units) that determines when the 110 * [MotionValue]'s internal spring animation is considered stable. 111 */ 112 class MotionValue( 113 currentInput: () -> Float, 114 gestureContext: GestureContext, 115 initialSpec: MotionSpec = MotionSpec.Empty, 116 label: String? = null, 117 stableThreshold: Float = StableThresholdEffect, 118 ) : FloatState { 119 private val impl = 120 ObservableComputations(currentInput, gestureContext, initialSpec, stableThreshold, label) 121 122 /** The [MotionSpec] describing the mapping of this [MotionValue]'s input to the output. */ 123 var spec: MotionSpec by impl::spec 124 125 /** Animated [output] value. */ 126 val output: Float by impl::output 127 128 /** 129 * [output] value, but without animations. 130 * 131 * This value always reports the target value, even before a animation is finished. 132 * 133 * While [isStable], [outputTarget] and [output] are the same value. 134 */ 135 val outputTarget: Float by impl::outputTarget 136 137 /** The [output] exposed as [FloatState]. */ 138 override val floatValue: Float by impl::output 139 140 /** Whether an animation is currently running. */ 141 val isStable: Boolean by impl::isStable 142 143 /** 144 * Keeps the [MotionValue]'s animated output running. 145 * 146 * Clients must call [keepRunning], and keep the coroutine running while the [MotionValue] is in 147 * use. When disposing this [MotionValue], cancel the coroutine. 148 * 149 * Internally, this method does suspend, unless there are animations ongoing. 150 */ 151 suspend fun keepRunning(): Nothing { 152 withContext(CoroutineName("MotionValue($label)")) { impl.keepRunning { true } } 153 154 // `keepRunning` above will never finish, 155 throw AssertionError("Unreachable code") 156 } 157 158 /** 159 * Keeps the [MotionValue]'s animated output running while [continueRunning] returns `true`. 160 * 161 * When [continueRunning] returns `false`, the coroutine will end by the next frame. 162 * 163 * To keep the [MotionValue] running until the current animations are complete, check for 164 * `isStable` as well. 165 * 166 * ```kotlin 167 * motionValue.keepRunningWhile { !shouldEnd() || !isStable } 168 * ``` 169 */ 170 suspend fun keepRunningWhile(continueRunning: MotionValue.() -> Boolean) = 171 withContext(CoroutineName("MotionValue($label)")) { 172 impl.keepRunning { continueRunning.invoke(this@MotionValue) } 173 } 174 175 val label: String? by impl::label 176 177 companion object { 178 /** Creates a [MotionValue] whose [currentInput] is the animated [output] of [source]. */ 179 fun createDerived( 180 source: MotionValue, 181 initialSpec: MotionSpec = MotionSpec.Empty, 182 label: String? = null, 183 stableThreshold: Float = 0.01f, 184 ): MotionValue { 185 return MotionValue( 186 currentInput = source::output, 187 gestureContext = source.impl.gestureContext, 188 initialSpec = initialSpec, 189 label = label, 190 stableThreshold = stableThreshold, 191 ) 192 } 193 194 const val StableThresholdEffect = 0.01f 195 const val StableThresholdSpatial = 1f 196 197 internal const val TAG = "MotionValue" 198 } 199 200 private var debugInspectorRefCount = AtomicInteger(0) 201 202 private fun onDisposeDebugInspector() { 203 if (debugInspectorRefCount.decrementAndGet() == 0) { 204 impl.debugInspector = null 205 } 206 } 207 208 /** 209 * Provides access to internal state for debug tooling and tests. 210 * 211 * The returned [DebugInspector] must be [DebugInspector.dispose]d when no longer needed. 212 */ 213 fun debugInspector(): DebugInspector { 214 if (debugInspectorRefCount.getAndIncrement() == 0) { 215 impl.debugInspector = 216 DebugInspector( 217 FrameData( 218 impl.lastInput, 219 impl.lastSegment.direction, 220 impl.lastGestureDragOffset, 221 impl.lastFrameTimeNanos, 222 impl.lastSpringState, 223 impl.lastSegment, 224 impl.lastAnimation, 225 ), 226 impl.isActive, 227 impl.debugIsAnimating, 228 ::onDisposeDebugInspector, 229 ) 230 } 231 232 return checkNotNull(impl.debugInspector) 233 } 234 } 235 236 private class ObservableComputations( 237 val input: () -> Float, 238 val gestureContext: GestureContext, 239 initialSpec: MotionSpec = MotionSpec.Empty, 240 override val stableThreshold: Float, 241 override val label: String?, 242 ) : Computations { 243 244 // ---- CurrentFrameInput --------------------------------------------------------------------- 245 246 override var spec by mutableStateOf(initialSpec) 247 override val currentInput: Float 248 get() = input.invoke() 249 250 override val currentDirection: InputDirection 251 get() = gestureContext.direction 252 253 override val currentGestureDragOffset: Float 254 get() = gestureContext.dragOffset 255 256 override var currentAnimationTimeNanos by mutableLongStateOf(-1L) 257 258 // ---- LastFrameState --------------------------------------------------------------------- 259 260 override var lastSegment: SegmentData by 261 mutableStateOf( 262 spec.segmentAtInput(currentInput, currentDirection), 263 referentialEqualityPolicy(), 264 ) 265 266 override var lastGuaranteeState: GuaranteeState 267 get() = GuaranteeState(_lastGuaranteeStatePacked) 268 set(value) { 269 _lastGuaranteeStatePacked = value.packedValue 270 } 271 272 private var _lastGuaranteeStatePacked: Long by 273 mutableLongStateOf(GuaranteeState.Inactive.packedValue) 274 275 override var lastAnimation: DiscontinuityAnimation by 276 mutableStateOf(DiscontinuityAnimation.None, referentialEqualityPolicy()) 277 278 override var directMappedVelocity: Float = 0f 279 280 override var lastSpringState: SpringState 281 get() = SpringState(_lastSpringStatePacked) 282 set(value) { 283 _lastSpringStatePacked = value.packedValue 284 } 285 286 private var _lastSpringStatePacked: Long by 287 mutableLongStateOf(lastAnimation.springStartState.packedValue) 288 289 override var lastFrameTimeNanos by mutableLongStateOf(-1L) 290 291 override var lastInput by mutableFloatStateOf(currentInput) 292 293 override var lastGestureDragOffset by mutableFloatStateOf(currentGestureDragOffset) 294 295 // ---- Computations --------------------------------------------------------------------------- 296 <lambda>null297 override val currentSegment by derivedStateOf { computeCurrentSegment() } <lambda>null298 override val currentGuaranteeState by derivedStateOf { computeCurrentGuaranteeState() } <lambda>null299 override val currentAnimation by derivedStateOf { computeCurrentAnimation() } <lambda>null300 override val currentSpringState by derivedStateOf { computeCurrentSpringState() } 301 keepRunningnull302 suspend fun keepRunning(continueRunning: () -> Boolean) { 303 check(!isActive) { "MotionValue($label) is already running" } 304 isActive = true 305 306 // These `captured*` values will be applied to the `last*` values, at the beginning 307 // of the each new frame. 308 // TODO(b/397837971): Encapsulate the state in a StateRecord. 309 var capturedSegment = currentSegment 310 var capturedGuaranteeState = currentGuaranteeState 311 var capturedAnimation = currentAnimation 312 var capturedSpringState = currentSpringState 313 var capturedFrameTimeNanos = currentAnimationTimeNanos 314 var capturedInput = currentInput 315 var capturedGestureDragOffset = currentGestureDragOffset 316 var capturedDirection = currentDirection 317 318 try { 319 debugIsAnimating = true 320 321 // indicates whether withFrameNanos is called continuously (as opposed to being 322 // suspended for an undetermined amount of time in between withFrameNanos). 323 // This is essential after `withFrameNanos` returned: if true at this point, 324 // currentAnimationTimeNanos - lastFrameNanos is the duration of the last frame. 325 var isAnimatingUninterrupted = false 326 327 while (continueRunning()) { 328 329 withFrameNanos { frameTimeNanos -> 330 currentAnimationTimeNanos = frameTimeNanos 331 332 // With the new frame started, copy 333 334 lastSegment = capturedSegment 335 lastGuaranteeState = capturedGuaranteeState 336 lastAnimation = capturedAnimation 337 lastSpringState = capturedSpringState 338 lastFrameTimeNanos = capturedFrameTimeNanos 339 lastInput = capturedInput 340 lastGestureDragOffset = capturedGestureDragOffset 341 } 342 343 // At this point, the complete frame is done (including layout, drawing and 344 // everything else), and this MotionValue has been updated. 345 346 // Capture the `current*` MotionValue state, so that it can be applied as the 347 // `last*` state when the next frame starts. Its imperative to capture at this point 348 // already (since the input could change before the next frame starts), while at the 349 // same time not already applying the `last*` state (as this would cause a 350 // re-computation if the current state is being read before the next frame). 351 if (isAnimatingUninterrupted) { 352 val currentDirectMapped = currentDirectMapped 353 val lastDirectMapped = 354 lastSegment.mapping.map(lastInput) - lastAnimation.targetValue 355 356 val frameDuration = 357 (currentAnimationTimeNanos - lastFrameTimeNanos) / 1_000_000_000.0 358 val staticDelta = (currentDirectMapped - lastDirectMapped) 359 directMappedVelocity = (staticDelta / frameDuration).toFloat() 360 } else { 361 directMappedVelocity = 0f 362 } 363 364 var scheduleNextFrame = !isStable 365 if (capturedSegment != currentSegment) { 366 capturedSegment = currentSegment 367 scheduleNextFrame = true 368 } 369 370 if (capturedGuaranteeState != currentGuaranteeState) { 371 capturedGuaranteeState = currentGuaranteeState 372 scheduleNextFrame = true 373 } 374 375 if (capturedAnimation != currentAnimation) { 376 capturedAnimation = currentAnimation 377 scheduleNextFrame = true 378 } 379 380 if (capturedSpringState != currentSpringState) { 381 capturedSpringState = currentSpringState 382 scheduleNextFrame = true 383 } 384 385 if (capturedInput != currentInput) { 386 capturedInput = currentInput 387 scheduleNextFrame = true 388 } 389 390 if (capturedGestureDragOffset != currentGestureDragOffset) { 391 capturedGestureDragOffset = currentGestureDragOffset 392 scheduleNextFrame = true 393 } 394 395 if (capturedDirection != currentDirection) { 396 capturedDirection = currentDirection 397 scheduleNextFrame = true 398 } 399 400 capturedFrameTimeNanos = currentAnimationTimeNanos 401 402 debugInspector?.run { 403 frame = 404 FrameData( 405 capturedInput, 406 capturedDirection, 407 capturedGestureDragOffset, 408 capturedFrameTimeNanos, 409 capturedSpringState, 410 capturedSegment, 411 capturedAnimation, 412 ) 413 } 414 415 isAnimatingUninterrupted = scheduleNextFrame 416 if (scheduleNextFrame) { 417 continue 418 } 419 420 debugIsAnimating = false 421 snapshotFlow { 422 val wakeup = 423 !continueRunning() || 424 spec != capturedSegment.spec || 425 currentInput != capturedInput || 426 currentDirection != capturedDirection || 427 currentGestureDragOffset != capturedGestureDragOffset 428 wakeup 429 } 430 .first { it } 431 debugIsAnimating = true 432 } 433 } finally { 434 isActive = false 435 debugIsAnimating = false 436 } 437 } 438 439 /** Whether a [keepRunning] coroutine is active currently. */ 440 var isActive = false 441 set(value) { 442 field = value 443 debugInspector?.isActive = value 444 } 445 446 /** 447 * `false` whenever the [keepRunning] coroutine is suspended while no animation is running and 448 * the input is not changing. 449 */ 450 var debugIsAnimating = false 451 set(value) { 452 field = value 453 debugInspector?.isAnimating = value 454 } 455 456 var debugInspector: DebugInspector? = null 457 } 458