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

commonMain.space.kscience.dataforge.data.Goal.kt Maven / Gradle / Ivy

There is a newer version: 0.7.0
Show newest version
package space.kscience.dataforge.data

import kotlinx.coroutines.*
import space.kscience.dataforge.misc.DFExperimental
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

/**
 * Lazy computation result with its dependencies to allowing to stat computing dependencies ahead of time
 */
public interface Goal {
    public val dependencies: Collection>

    /**
     * Returns current running coroutine if the goal is started. Null if the computation is not started.
     */
    public val deferred: Deferred?

    /**
     * Get ongoing computation or start a new one.
     * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
     *
     * If the computation is already running, the scope is not used.
     */
    public fun async(coroutineScope: CoroutineScope): Deferred

    /**
     * Reset the computation
     */
    public fun reset()

    public companion object
}

public fun Goal<*>.launch(coroutineScope: CoroutineScope): Job = async(coroutineScope)

public suspend fun  Goal.await(): T = coroutineScope { async(this).await() }

public val Goal<*>.isComplete: Boolean get() = deferred?.isCompleted ?: false

public open class StaticGoal(public val value: T) : Goal {
    override val dependencies: Collection> get() = emptyList()
    override val deferred: Deferred = CompletableDeferred(value)

    override fun async(coroutineScope: CoroutineScope): Deferred = deferred

    override fun reset() {
        //doNothing
    }
}

/**
 * @param coroutineContext additional context information
 */
public open class LazyGoal(
    private val coroutineContext: CoroutineContext = EmptyCoroutineContext,
    override val dependencies: Collection> = emptyList(),
    public val block: suspend () -> T,
) : Goal {

    final override var deferred: Deferred? = null
        private set

    /**
     * Get ongoing computation or start a new one.
     * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
     * If [GoalExecutionRestriction] is present in the [coroutineScope] context, the call could produce a error a warning
     * depending on the settings.
     */
    @DFExperimental
    override fun async(coroutineScope: CoroutineScope): Deferred {
        val log = coroutineScope.coroutineContext[GoalLogger]
        // Check if context restricts goal computation
        coroutineScope.coroutineContext[GoalExecutionRestriction]?.let { restriction ->
            when (restriction.policy) {
                GoalExecutionRestrictionPolicy.WARNING -> log?.emit(GoalLogger.WARNING_TAG) { "Goal eager execution is prohibited by the coroutine scope policy" }
                GoalExecutionRestrictionPolicy.ERROR -> error("Goal eager execution is prohibited by the coroutine scope policy")
                else -> {
                    /*do nothing*/
                }
            }
        }

        log?.emit { "Starting dependencies computation for ${this@LazyGoal}" }
        val startedDependencies = this.dependencies.map { goal ->
            goal.run { async(coroutineScope) }
        }
        return deferred ?: coroutineScope.async(
            coroutineContext
                    + CoroutineMonitor()
                    + Dependencies(startedDependencies)
                    + GoalExecutionRestriction(GoalExecutionRestrictionPolicy.NONE) // Remove restrictions on goal execution
        ) {
            //cancel execution if error encountered in one of dependencies
            startedDependencies.forEach { deferred ->
                deferred.invokeOnCompletion { error ->
                    if (error != null) this.cancel(CancellationException("Dependency $deferred failed with error: ${error.message}"))
                }
            }
            coroutineContext[GoalLogger]?.emit { "Starting computation of ${this@LazyGoal}" }
            block()
        }.also { deferred = it }
    }

    /**
     * Reset the computation
     */
    override fun reset() {
        deferred?.cancel()
        deferred = null
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy