All Downloads are FREE. Search and download functionalities are using the official Maven repository.

skikoMain.androidx.compose.ui.scene.ComposeSceneRecomposer.skiko.kt Maven / Gradle / Ivy

/*
 * Copyright 2023 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.ui.scene

import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.Recomposer
import androidx.compose.ui.platform.FlushCoroutineDispatcher
import androidx.compose.ui.util.trace
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.launch

/**
 * The scheduler for performing recomposition and applying updates to one or more [Composition]s.
 *
 * The main difference from [Recomposer] is separate dispatchers for LaunchEffect and other
 * recompositions that allows more precise status checking.
 *
 * @param coroutineContext The coroutine context to use for the compositor.
 * @param elements Additional coroutine context elements to include in context.
 */
internal class ComposeSceneRecomposer(
    coroutineContext: CoroutineContext,
    vararg elements: CoroutineContext.Element
) {
    private val job = Job()
    private val coroutineScope = CoroutineScope(coroutineContext + job)

    /**
     * We use [FlushCoroutineDispatcher] not only because we need [FlushCoroutineDispatcher.flush]
     * for LaunchEffect tasks, but also to know whether it is idle (has no scheduled tasks)
     */
    private val effectDispatcher = FlushCoroutineDispatcher(coroutineScope)
    private val recomposeDispatcher = FlushCoroutineDispatcher(coroutineScope)
    private val recomposer = Recomposer(coroutineContext + job + effectDispatcher)

    /**
     * `true` if there is any pending work scheduled, regardless of whether it is currently running.
     */
    val hasPendingWork: Boolean
        get() = recomposer.hasPendingWork ||
        effectDispatcher.hasTasks() ||
        recomposeDispatcher.hasTasks()

    val compositionContext: CompositionContext
        get() = recomposer

    init {
        var context: CoroutineContext = recomposeDispatcher
        for (element in elements) {
            context += element
        }
        coroutineScope.launch(context,
            start = CoroutineStart.UNDISPATCHED
        ) {
            recomposer.runRecomposeAndApplyChanges()
        }
    }

    /**
     * Perform all scheduled recomposer tasks and wait for the tasks which are already
     * performing in the recomposition scope.
     */
    fun performScheduledRecomposerTasks() =
        trace("ComposeSceneRecomposer:performScheduledRecomposerTasks") {
            recomposeDispatcher.flush()
        }

    /**
     * Perform all scheduled effects.
     */
    fun performScheduledEffects() = trace("ComposeSceneRecomposer:performScheduledEffects") {
        effectDispatcher.flush()
    }

    /**
     * Schedules the given [block] to run with the effects dispatcher.
     */
    fun scheduleAsEffect(block: () -> Unit) {
        effectDispatcher.dispatch(job, Runnable(block))
    }

    /**
     * Permanently shut down this [ComposeSceneRecomposer] for future use.
     *
     * @see Recomposer.cancel
     */
    fun cancel() {
        recomposer.cancel()
        job.cancel()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy