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