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

commonMain.pro.respawn.flowmvi.plugins.TimeTravelPlugin.kt Maven / Gradle / Ivy

Go to download

A Kotlin Multiplatform MVI library based on plugins that is simple, fast, powerful & flexible

There is a newer version: 3.0.0
Show newest version
package pro.respawn.flowmvi.plugins

import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.PipelineContext
import pro.respawn.flowmvi.dsl.StoreBuilder
import pro.respawn.flowmvi.util.CappedMutableList

/**
 * A plugin that keeps track of changes in the store.
 * It keeps references to last `maxStates` (inclusive) states and so on for other properties.
 * Keep a reference to this plugin and use it to enable custom time travel support or validate the store's behavior
 * in tests.
 */
public class TimeTravelPlugin internal constructor(
    name: String,
    maxStates: Int,
    maxIntents: Int,
    maxActions: Int,
    maxExceptions: Int,
) : AbstractStorePlugin(name) {

    private val _states by atomic(CappedMutableList(maxStates))

    /**
     * States emitted by the store, capped at [maxStates]
     * The last value is the most recent.
     */
    public val states: Collection get() = _states
    private val _intents by atomic(CappedMutableList(maxIntents))

    /**
     * Intents processed by the store, capped at [maxIntents].
     * The last value is the most recent.
     */
    public val intents: Collection get() = _intents
    private val _actions by atomic(CappedMutableList(maxActions))

    /**
     *  Actions sent by the store, capped at [maxActions].
     * The last value is the most recent.
     */
    public val actions: Collection get() = _actions
    private val _exceptions by atomic(CappedMutableList(maxExceptions))

    /**
     * Last exceptions caught by store, capped at [maxExceptions].
     * The last value is the most recent.
     */
    public val exceptions: Collection get() = _exceptions

    /**
     * Number of subscription events of the store. Never decreases.
     * The last value is the most recent.
     */
    public var subscriptions: Int by atomic(0)
        internal set

    /**
     * Number of times the store was launched. Never decreases.
     */
    public var starts: Int by atomic(0)
        internal set

    /**
     * Number of the times the store was stopped. Never decreases.
     */
    public var stops: Int by atomic(0)
        internal set

    /**
     * Number of times the store has been unsubscribed from. Never decreases.
     */
    public var unsubscriptions: Int by atomic(0)
        private set

    /**
     * Reset all values of this plugin and start from scratch.
     */
    public fun reset() {
        _states.clear()
        _intents.clear()
        _actions.clear()
        _exceptions.clear()
        subscriptions = 0
        unsubscriptions = 0
        starts = 0
        stops = 0
    }

    override suspend fun PipelineContext.onState(old: S, new: S): S = new.also { _states.add(it) }

    override suspend fun PipelineContext.onIntent(intent: I): I = intent.also { _intents.add(it) }

    override suspend fun PipelineContext.onAction(action: A): A = action.also { _actions.add(it) }

    override suspend fun PipelineContext.onException(e: Exception): Exception = e.also { _exceptions.add(it) }

    override suspend fun PipelineContext.onStart() {
        starts += 1
    }

    override fun PipelineContext.onSubscribe(subscriberScope: CoroutineScope, subscriberCount: Int) {
        subscriptions += 1
    }

    override fun PipelineContext.onUnsubscribe(subscriberCount: Int) {
        unsubscriptions += 1
    }

    override fun onStop(e: Exception?) {
        stops += 1
    }

    public companion object {

        /**
         * The default max size for time travel holders
         */
        public const val DefaultHistorySize: Int = 64

        /**
         * Default time travel plugin name. Hardcoded to prevent multiple plugins from being installed.
         */
        public const val Name: String = "TimeTravelPlugin"
    }
}

/**
 * Create a new [TimeTravelPlugin]. Keep a reference to the plugin to use its properties.
 * @return the plugin.
 */
public fun  timeTravelPlugin(
    name: String = TimeTravelPlugin.Name,
    maxStates: Int = TimeTravelPlugin.DefaultHistorySize,
    maxIntents: Int = TimeTravelPlugin.DefaultHistorySize,
    maxActions: Int = TimeTravelPlugin.DefaultHistorySize,
    maxExceptions: Int = TimeTravelPlugin.DefaultHistorySize,
): TimeTravelPlugin = TimeTravelPlugin(name, maxStates, maxIntents, maxActions, maxExceptions)

/**
 * Create a new [TimeTravelPlugin] and installs it. Keep a reference to the plugin to use its properties.
 * @return the plugin.
 */
public fun  StoreBuilder.timeTravel(
    name: String = TimeTravelPlugin.Name,
    maxStates: Int = TimeTravelPlugin.DefaultHistorySize,
    maxIntents: Int = TimeTravelPlugin.DefaultHistorySize,
    maxActions: Int = TimeTravelPlugin.DefaultHistorySize,
    maxExceptions: Int = TimeTravelPlugin.DefaultHistorySize,
): TimeTravelPlugin = timeTravelPlugin(
    name = name,
    maxStates = maxStates,
    maxIntents = maxIntents,
    maxActions = maxActions,
    maxExceptions = maxExceptions,
).also { install(it) }