MicroUtils/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt

119 lines
4.7 KiB
Kotlin
Raw Normal View History

2021-10-06 06:14:02 +00:00
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
2021-10-13 09:06:58 +00:00
/**
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
*/
2021-11-04 09:31:10 +00:00
interface DefaultStatesManagerRepo<T : State> {
2021-10-06 06:14:02 +00:00
/**
* Must save [state] as current state of chain with [State.context] of [state]
*/
2021-11-04 09:31:10 +00:00
suspend fun set(state: T)
2021-10-06 06:14:02 +00:00
/**
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
* NOT be removed
*/
2021-11-04 09:31:10 +00:00
suspend fun removeState(state: T)
2021-10-06 06:14:02 +00:00
/**
2021-10-06 07:30:25 +00:00
* @return Current list of available and saved states
2021-10-06 06:14:02 +00:00
*/
2021-11-04 09:31:10 +00:00
suspend fun getStates(): List<T>
2021-10-06 07:30:25 +00:00
/**
* @return Current state by [context]
*/
2021-11-04 09:31:10 +00:00
suspend fun getContextState(context: Any): T?
2021-10-06 07:30:25 +00:00
/**
* @return Current state by [context]
*/
2021-10-13 09:06:58 +00:00
suspend fun contains(context: Any): Boolean = getContextState(context) != null
2021-10-06 06:14:02 +00:00
}
/**
2021-10-13 09:06:58 +00:00
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
2022-03-16 12:40:21 +00:00
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default, will be used
2021-10-13 09:06:58 +00:00
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
2022-03-27 11:43:01 +00:00
* @param onStartContextsConflictResolver Receive current [State] and the state passed with [startChain]. In case when
* this callback will return true, currently placed on the [State.context] [State] will be replaced by new state
* with [endChain] with current state
* @param onUpdateContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
2021-10-06 06:14:02 +00:00
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
2022-03-16 12:40:21 +00:00
open class DefaultStatesManager<T : State>(
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
2023-03-02 15:35:59 +00:00
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false },
2023-03-02 15:32:58 +00:00
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false }
2021-11-04 09:31:10 +00:00
) : StatesManager<T> {
2022-03-16 12:40:21 +00:00
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
2021-11-04 09:31:10 +00:00
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
2022-03-16 12:40:21 +00:00
protected val _onStartChain = MutableSharedFlow<T>(0)
2021-11-04 09:31:10 +00:00
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
2022-03-16 12:40:21 +00:00
protected val _onEndChain = MutableSharedFlow<T>(0)
2021-11-04 09:31:10 +00:00
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
2021-10-06 06:14:02 +00:00
2022-03-16 12:40:21 +00:00
protected val mapMutex = Mutex()
2021-10-06 06:14:02 +00:00
constructor(
repo: DefaultStatesManagerRepo<T>,
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean
) : this (
repo,
onUpdateContextsConflictResolver = onContextsConflictResolver
)
2021-11-04 09:31:10 +00:00
override suspend fun update(old: T, new: T) = mapMutex.withLock {
val stateByOldContext: T? = repo.getContextState(old.context)
2021-10-06 06:14:02 +00:00
when {
2021-10-06 07:30:25 +00:00
stateByOldContext != old -> return@withLock
stateByOldContext == null || old.context == new.context -> {
repo.removeState(old)
2021-10-06 07:30:25 +00:00
repo.set(new)
2021-10-06 06:14:02 +00:00
_onChainStateUpdated.emit(old to new)
}
else -> {
2021-10-06 07:30:25 +00:00
val stateOnNewOneContext = repo.getContextState(new.context)
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) {
2021-10-06 07:30:25 +00:00
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
repo.removeState(old)
repo.set(new)
2021-10-06 06:14:02 +00:00
_onChainStateUpdated.emit(old to new)
}
}
}
}
2021-11-04 09:31:10 +00:00
override suspend fun startChain(state: T) = mapMutex.withLock {
val stateOnContext = repo.getContextState(state.context)
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
stateOnContext ?.let {
endChainWithoutLock(it)
}
2021-10-06 07:30:25 +00:00
repo.set(state)
2021-10-06 06:14:02 +00:00
_onStartChain.emit(state)
}
}
2022-03-16 12:40:21 +00:00
protected open suspend fun endChainWithoutLock(state: T) {
2021-10-06 07:30:25 +00:00
if (repo.getContextState(state.context) == state) {
repo.removeState(state)
2021-10-06 06:14:02 +00:00
_onEndChain.emit(state)
}
}
2021-11-04 09:31:10 +00:00
override suspend fun endChain(state: T) {
2021-10-06 06:14:02 +00:00
mapMutex.withLock {
endChainWithoutLock(state)
}
}
2021-11-04 09:31:10 +00:00
override suspend fun getActiveStates(): List<T> = repo.getStates()
2021-10-06 06:14:02 +00:00
}