1 /*
2 * Copyright 2021 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.internal.JvmDefaultWithCompatibility
20 import kotlin.coroutines.CoroutineContext
21 import kotlin.coroutines.coroutineContext
22
23 /**
24 * Provides a time source for display frames and the ability to perform an action on the next frame.
25 * This may be used for matching timing with the refresh rate of a display or otherwise
26 * synchronizing work with a desired frame rate.
27 */
28 @JvmDefaultWithCompatibility
29 interface MonotonicFrameClock : CoroutineContext.Element {
30 /**
31 * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time in
32 * nanoseconds in the calling context of frame dispatch, then resumes with the result from
33 * [onFrame].
34 *
35 * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame as
36 * it may be normalized to the target time for the frame, not necessarily a direct, "now" value.
37 *
38 * The time base of the value provided by [withFrameNanos] is implementation defined. Time
39 * values provided are strictly monotonically increasing; after a call to [withFrameNanos]
40 * completes it must not provide the same value again for a subsequent call.
41 */
withFrameNanosnull42 suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R
43
44 override val key: CoroutineContext.Key<*>
45 get() = Key
46
47 companion object Key : CoroutineContext.Key<MonotonicFrameClock>
48 }
49
50 /**
51 * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time in
52 * milliseconds in the calling context of frame dispatch, then resumes with the result from
53 * [onFrame].
54 *
55 * `frameTimeMillis` should be used when calculating animation time deltas from frame to frame as it
56 * may be normalized to the target time for the frame, not necessarily a direct, "now" value.
57 *
58 * The time base of the value provided by [MonotonicFrameClock.withFrameMillis] is implementation
59 * defined. Time values provided are monotonically increasing; after a call to [withFrameMillis]
60 * completes it must not provide a smaller value for a subsequent call.
61 */
62 @Suppress("UnnecessaryLambdaCreation")
63 suspend inline fun <R> MonotonicFrameClock.withFrameMillis(
64 crossinline onFrame: (frameTimeMillis: Long) -> R
65 ): R = withFrameNanos { onFrame(it / 1_000_000L) }
66
67 /**
68 * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time in
69 * nanoseconds in the calling context of frame dispatch, then resumes with the result from
70 * [onFrame].
71 *
72 * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame as it
73 * may be normalized to the target time for the frame, not necessarily a direct, "now" value.
74 *
75 * The time base of the value provided by [withFrameNanos] is implementation defined. Time values
76 * provided are strictly monotonically increasing; after a call to [withFrameNanos] completes it
77 * must not provide the same value again for a subsequent call.
78 *
79 * This function will invoke [MonotonicFrameClock.withFrameNanos] using the calling
80 * [CoroutineContext]'s [MonotonicFrameClock] and will throw an [IllegalStateException] if one is
81 * not present in the [CoroutineContext].
82 */
83 @OptIn(ExperimentalComposeApi::class)
withFrameNanosnull84 suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R =
85 coroutineContext.monotonicFrameClock.withFrameNanos(onFrame)
86
87 /**
88 * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time in
89 * milliseconds in the calling context of frame dispatch, then resumes with the result from
90 * [onFrame].
91 *
92 * `frameTimeMillis` should be used when calculating animation time deltas from frame to frame as it
93 * may be normalized to the target time for the frame, not necessarily a direct, "now" value.
94 *
95 * The time base of the value provided by [MonotonicFrameClock.withFrameMillis] is implementation
96 * defined. Time values provided are monotonically increasing; after a call to [withFrameMillis]
97 * completes it must not provide a smaller value for a subsequent call.
98 *
99 * This function will invoke [MonotonicFrameClock.withFrameNanos] using the calling
100 * [CoroutineContext]'s [MonotonicFrameClock] and will throw an [IllegalStateException] if one is
101 * not present in the [CoroutineContext].
102 */
103 @OptIn(ExperimentalComposeApi::class)
104 suspend fun <R> withFrameMillis(onFrame: (frameTimeMillis: Long) -> R): R =
105 coroutineContext.monotonicFrameClock.withFrameMillis(onFrame)
106
107 /**
108 * Returns the [MonotonicFrameClock] for this [CoroutineContext] or throws [IllegalStateException]
109 * if one is not present.
110 */
111 @ExperimentalComposeApi
112 val CoroutineContext.monotonicFrameClock: MonotonicFrameClock
113 get() =
114 this[MonotonicFrameClock]
115 ?: error(
116 "A MonotonicFrameClock is not available in this CoroutineContext. Callers should supply " +
117 "an appropriate MonotonicFrameClock using withContext."
118 )
119
120 /**
121 * The [MonotonicFrameClock] used by [withFrameNanos] and [withFrameMillis] if one is not present in
122 * the calling [kotlin.coroutines.CoroutineContext].
123 *
124 * This value is no longer used by compose runtime.
125 */
126 @Deprecated(
127 "MonotonicFrameClocks are not globally applicable across platforms. " +
128 "Use an appropriate local clock."
129 )
130 expect val DefaultMonotonicFrameClock: MonotonicFrameClock
131