mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-11-04 06:00:22 +00:00 
			
		
		
		
	updates in FSM
This commit is contained in:
		@@ -5,6 +5,9 @@
 | 
				
			|||||||
* `Versions`:
 | 
					* `Versions`:
 | 
				
			||||||
    * `AppCompat`: `1.3.1` -> `1.4.0`
 | 
					    * `AppCompat`: `1.3.1` -> `1.4.0`
 | 
				
			||||||
    * Android Compile SDK: `31.0.0` -> `32.0.0`
 | 
					    * Android Compile SDK: `31.0.0` -> `32.0.0`
 | 
				
			||||||
 | 
					* `FSM`:
 | 
				
			||||||
 | 
					    * `DefaultStatesMachine` now is extendable
 | 
				
			||||||
 | 
					    * New type `UpdatableStatesMachine` with default realization`DefaultUpdatableStatesMachine`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 0.8.7
 | 
					## 0.8.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,8 +21,10 @@ import kotlinx.serialization.Serializable
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
@Serializable
 | 
					@Serializable
 | 
				
			||||||
data class Optional<T> internal constructor(
 | 
					data class Optional<T> internal constructor(
 | 
				
			||||||
    internal val data: T?,
 | 
					    @Warning("It is unsafe to use this data directly")
 | 
				
			||||||
    internal val dataPresented: Boolean
 | 
					    val data: T?,
 | 
				
			||||||
 | 
					    @Warning("It is unsafe to use this data directly")
 | 
				
			||||||
 | 
					    val dataPresented: Boolean
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
@@ -42,17 +44,31 @@ inline val <T> T.optional
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Will call [block] when data presented ([Optional.dataPresented] == true)
 | 
					 * Will call [block] when data presented ([Optional.dataPresented] == true)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
 | 
					inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
 | 
				
			||||||
    if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
 | 
					    if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will call [block] when data presented ([Optional.dataPresented] == true)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
 | 
				
			||||||
 | 
					    if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Will call [block] when data absent ([Optional.dataPresented] == false)
 | 
					 * Will call [block] when data absent ([Optional.dataPresented] == false)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
 | 
					inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
 | 
				
			||||||
    if (!dataPresented) { block() }
 | 
					    if (!dataPresented) { block() }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will call [block] when data presented ([Optional.dataPresented] == true)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
 | 
				
			||||||
 | 
					    if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
 | 
					 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -67,9 +83,10 @@ fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Supp
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
 | 
					 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
 | 
					inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
 | 
					 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
 | 
				
			||||||
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
 | 
					suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ kotlin {
 | 
				
			|||||||
    sourceSets {
 | 
					    sourceSets {
 | 
				
			||||||
        commonMain {
 | 
					        commonMain {
 | 
				
			||||||
            dependencies {
 | 
					            dependencies {
 | 
				
			||||||
 | 
					                api project(":micro_utils.common")
 | 
				
			||||||
                api project(":micro_utils.coroutines")
 | 
					                api project(":micro_utils.coroutines")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,11 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.fsm.common
 | 
					package dev.inmo.micro_utils.fsm.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
 | 
					import dev.inmo.micro_utils.common.Optional
 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
					import dev.inmo.micro_utils.common.onPresented
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.*
 | 
				
			||||||
import kotlinx.coroutines.*
 | 
					import kotlinx.coroutines.*
 | 
				
			||||||
 | 
					import kotlinx.coroutines.sync.Mutex
 | 
				
			||||||
 | 
					import kotlinx.coroutines.sync.withLock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
 | 
					 * Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
 | 
				
			||||||
@@ -42,17 +45,53 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
 | 
					 * Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
 | 
				
			||||||
 * resolving, and uses [launchStateHandling] for [State] handling
 | 
					 * resolving, and uses [launchStateHandling] for [State] handling.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class DefaultStatesMachine <T: State>(
 | 
					open class DefaultStatesMachine <T: State>(
 | 
				
			||||||
    private val statesManager: StatesManager<T>,
 | 
					    protected val statesManager: StatesManager<T>,
 | 
				
			||||||
    private val handlers: List<CheckableHandlerHolder<in T, T>>
 | 
					    protected val handlers: List<CheckableHandlerHolder<in T, T>>,
 | 
				
			||||||
) : StatesMachine<T> {
 | 
					) : StatesMachine<T> {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Will call [launchStateHandling] for state handling
 | 
					     * Will call [launchStateHandling] for state handling
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
 | 
					    override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * This
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected val statesJobs = mutableMapOf<T, Job>()
 | 
				
			||||||
 | 
					    protected val statesJobsMutex = Mutex()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected open suspend fun performUpdate(state: T) {
 | 
				
			||||||
 | 
					        val newState = launchStateHandling(state, handlers)
 | 
				
			||||||
 | 
					        if (newState != null) {
 | 
				
			||||||
 | 
					            statesManager.update(state, newState)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            statesManager.endChain(state)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
 | 
				
			||||||
 | 
					        statesJobsMutex.withLock {
 | 
				
			||||||
 | 
					            statesJobs[actualState] ?.cancel()
 | 
				
			||||||
 | 
					            statesJobs[actualState] = scope.launch {
 | 
				
			||||||
 | 
					                performUpdate(actualState)
 | 
				
			||||||
 | 
					            }.also { job ->
 | 
				
			||||||
 | 
					                job.invokeOnCompletion { _ ->
 | 
				
			||||||
 | 
					                    scope.launch {
 | 
				
			||||||
 | 
					                        statesJobsMutex.withLock {
 | 
				
			||||||
 | 
					                            if (statesJobs[actualState] == job) {
 | 
				
			||||||
 | 
					                                statesJobs.remove(actualState)
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Launch handling of states. On [statesManager] [StatesManager.onStartChain],
 | 
					     * Launch handling of states. On [statesManager] [StatesManager.onStartChain],
 | 
				
			||||||
     * [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
 | 
					     * [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
 | 
				
			||||||
@@ -60,23 +99,15 @@ class DefaultStatesMachine <T: State>(
 | 
				
			|||||||
     * [StatesManager.endChain].
 | 
					     * [StatesManager.endChain].
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
 | 
					    override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
 | 
				
			||||||
        val statePerformer: suspend (T) -> Unit = { state: T ->
 | 
					 | 
				
			||||||
            val newState = launchStateHandling(state, handlers)
 | 
					 | 
				
			||||||
            if (newState != null) {
 | 
					 | 
				
			||||||
                statesManager.update(state, newState)
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                statesManager.endChain(state)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
 | 
					        statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
 | 
				
			||||||
            launch { statePerformer(it) }
 | 
					            launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
 | 
					        statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
 | 
				
			||||||
            launch { statePerformer(it.second) }
 | 
					            launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        statesManager.getActiveStates().forEach {
 | 
					        statesManager.getActiveStates().forEach {
 | 
				
			||||||
            launch { statePerformer(it) }
 | 
					            launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.fsm.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.*
 | 
				
			||||||
 | 
					import kotlinx.coroutines.*
 | 
				
			||||||
 | 
					import kotlinx.coroutines.sync.withLock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
 | 
				
			||||||
 | 
					 * must be able to perform update of chain in internal [StatesManager]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
 | 
				
			||||||
 | 
					     * in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
 | 
				
			||||||
 | 
					     * [StatesManager.update] function
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    suspend fun updateChain(currentState: T, newState: T)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					open class DefaultUpdatableStatesMachine<T : State>(
 | 
				
			||||||
 | 
					    statesManager: StatesManager<T>,
 | 
				
			||||||
 | 
					    handlers: List<CheckableHandlerHolder<in T, T>>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					) : DefaultStatesMachine<T>(
 | 
				
			||||||
 | 
					    statesManager,
 | 
				
			||||||
 | 
					    handlers
 | 
				
			||||||
 | 
					), UpdatableStatesMachine<T> {
 | 
				
			||||||
 | 
					    protected val jobsStates = mutableMapOf<Job, T>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
 | 
				
			||||||
 | 
					        statesJobsMutex.withLock {
 | 
				
			||||||
 | 
					            statesJobs[actualState] ?.cancel()
 | 
				
			||||||
 | 
					            val job = previousState.mapOnPresented {
 | 
				
			||||||
 | 
					                statesJobs.remove(it)
 | 
				
			||||||
 | 
					            } ?: scope.launch {
 | 
				
			||||||
 | 
					                performUpdate(actualState)
 | 
				
			||||||
 | 
					            }.also { job ->
 | 
				
			||||||
 | 
					                job.invokeOnCompletion { _ ->
 | 
				
			||||||
 | 
					                    scope.launch {
 | 
				
			||||||
 | 
					                        statesJobsMutex.withLock {
 | 
				
			||||||
 | 
					                            statesJobs.remove(
 | 
				
			||||||
 | 
					                                jobsStates[job] ?: return@withLock
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            jobsStates.remove(job)
 | 
				
			||||||
 | 
					            statesJobs[actualState] = job
 | 
				
			||||||
 | 
					            jobsStates[job] = actualState
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun updateChain(currentState: T, newState: T) {
 | 
				
			||||||
 | 
					        statesManager.update(currentState, newState)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,6 +7,14 @@ import kotlin.reflect.KClass
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class FSMBuilder<T : State>(
 | 
					class FSMBuilder<T : State>(
 | 
				
			||||||
    var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
 | 
					    var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
 | 
				
			||||||
 | 
					    val fsmBuilder: (states: List<CheckableHandlerHolder<T, T>>, defaultHandler: StatesHandler<T, T>?) -> StatesMachine<T> = { states, defaultHandler ->
 | 
				
			||||||
 | 
					        StatesMachine(
 | 
				
			||||||
 | 
					            statesManager,
 | 
				
			||||||
 | 
					            states.let { list ->
 | 
				
			||||||
 | 
					                defaultHandler ?.let { list + it.holder { true } } ?: list
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
 | 
					    var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
 | 
					    private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
 | 
				
			||||||
@@ -42,12 +50,7 @@ class FSMBuilder<T : State>(
 | 
				
			|||||||
        add(filter, handler)
 | 
					        add(filter, handler)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun build() = StatesMachine(
 | 
					    fun build() = fsmBuilder(states.toList(), defaultStateHandler)
 | 
				
			||||||
        statesManager,
 | 
					 | 
				
			||||||
        states.toList().let { list ->
 | 
					 | 
				
			||||||
            defaultStateHandler ?.let { list + it.holder { true } } ?: list
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun <T : State> buildFSM(
 | 
					fun <T : State> buildFSM(
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user