PausableMonotonicFrameClock.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.runtime

/**
 * A [MonotonicFrameClock] wrapper that can be [pause]d and [resume]d.
 *
 * A paused clock will not dispatch [withFrameNanos] events until it is resumed.
 * Pausing a clock does **not** stop or change the frame times reported to [withFrameNanos] calls;
 * the clock times reported will always remain consistent with [frameClock].
 *
 * [PausableMonotonicFrameClock] should be used in cases where frames should not be produced
 * under some conditions, such as when a window hosting a UI is not currently visible.
 * As clock times are not altered from the source [frameClock], animations in progress may
 * be fully complete by the time the clock is resumed and a new frame is produced.
 */
class PausableMonotonicFrameClock(
    private val frameClock: MonotonicFrameClock
) : MonotonicFrameClock {
    private val latch = Latch()

    /**
     * `true` if this clock is currently [paused][pause] or `false` if this clock is currently
     * [resumed][resume]. A PausableMonotonicFrameClock is not paused at construction time.
     */
    val isPaused: Boolean
        get() = !latch.isOpen

    /**
     * Pause the generation of frames. Pausing a clock that is already paused has no effect.
     * While the clock is paused any calls to [withFrameNanos] will suspend until the clock is
     * resumed before delegating to the wrapped [frameClock]'s [withFrameNanos] method.
     * Call [resume] to resume generating frames.
     */
    fun pause() {
        latch.closeLatch()
    }

    /**
     * Resume the generation of frames. Any queued calls to [withFrameNanos] will resume and
     * delegate to the wrapped [frameClock]'s [withFrameNanos] method.
     */
    fun resume() {
        latch.openLatch()
    }

    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        latch.await()
        return frameClock.withFrameNanos(onFrame)
    }
}