rework of FSM + 0.8.0

This commit is contained in:
InsanusMokrassar 2021-11-04 15:31:10 +06:00
parent 00acb9fddd
commit bbe5320312
14 changed files with 156 additions and 250 deletions

View File

@ -1,6 +1,6 @@
# Changelog # Changelog
## 0.7.5 ## 0.8.0
* `Versions`: * `Versions`:
* `Klock`: `2.4.6` -> `2.4.7` * `Klock`: `2.4.6` -> `2.4.7`
@ -9,6 +9,9 @@
* Type `Either` got its own serializer * Type `Either` got its own serializer
* `FSM`: * `FSM`:
* `Common`: * `Common`:
* Full rework of FSM:
* Now it is more flexible for checking of handler opportunity to handle state
* Now machine and states managers are type-oriented
* Add opportunity for comfortable adding default state handler * Add opportunity for comfortable adding default state handler
## 0.7.4 ## 0.7.4

View File

@ -1,6 +0,0 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
@Deprecated("Replaced", ReplaceWith("InMemoryStatesManager", "dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager"))
typealias InMemoryStatesManager = InMemoryStatesManager

View File

@ -3,10 +3,10 @@ package dev.inmo.micro_utils.fsm.common
/** /**
* Default realization of states handler * Default realization of states handler
*/ */
fun interface StatesHandler<I : State> { fun interface StatesHandler<I : State, O: State> {
/** /**
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null * Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
* [State] it is assumed that chain is not completed. * [State] it is assumed that chain is not completed.
*/ */
suspend fun StatesMachine.handleState(state: I): State? suspend fun StatesMachine<in O>.handleState(state: I): O?
} }

View File

@ -2,37 +2,67 @@ package dev.inmo.micro_utils.fsm.common
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* Define checkable holder which can be used to precheck that this handler may handle incoming [State]
*/
interface CheckableHandlerHolder<I : State, O : State> : StatesHandler<I, O> {
suspend fun checkHandleable(state: O): Boolean
}
/** /**
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class * Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
* casting in [handleState] * casting in [handleState]
*/ */
class StatesHandlerHolder<I : State>( class CustomizableHandlerHolder<I : O, O : State>(
private val inputKlass: KClass<I>, private val delegateTo: StatesHandler<I, O>,
private val strict: Boolean = false, private val filter: suspend (state: O) -> Boolean
private val delegateTo: StatesHandler<I> ) : CheckableHandlerHolder<I, O> {
) : StatesHandler<State> {
/** /**
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state] * Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false * [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
*/ */
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) override suspend fun checkHandleable(state: O) = filter(state)
/** /**
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable] * Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
* to be sure that this [StatesHandlerHolder] will be able to handle [state] * to be sure that this [StatesHandlerHolder] will be able to handle [state]
*/ */
override suspend fun StatesMachine.handleState(state: State): State? { override suspend fun StatesMachine<in O>.handleState(state: I): O? {
return delegateTo.run { handleState(state as I) } return delegateTo.run { handleState(state) }
} }
} }
@Deprecated("Renamed", ReplaceWith("StatesHandlerHolder")) fun <I : O, O : State> StateHandlerHolder(
typealias StateHandlerHolder<T> = StatesHandlerHolder<T> inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CustomizableHandlerHolder(
StatesHandler<O, O> {
delegateTo.run { handleState(it as I) }
},
if (strict) {
{ it::class == inputKlass }
} else {
{ inputKlass.isInstance(it) }
}
)
inline fun <reified T : State> StatesHandler<T>.holder( inline fun <reified I : O, O : State> StateHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = StateHandlerHolder(I::class, strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true strict: Boolean = true
) = StatesHandlerHolder( ) = StateHandlerHolder<I, O>(
T::class, I::class,
strict, strict,
this this
) )
inline fun <I : O, O: State> StatesHandler<I, O>.holder(
noinline filter: suspend (state: State) -> Boolean
) = CustomizableHandlerHolder<O, O>(
{ this@holder.run { handleState(it as I) } },
filter
)

View File

@ -3,21 +3,21 @@ package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.coroutines.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
private suspend fun <I : State> StatesMachine.launchStateHandling(
state: State,
handlers: List<StatesHandlerHolder<out I>>
): State? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/** /**
* 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
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start * [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
* handling until [start] method will be called * handling until [start] method will be called
*/ */
interface StatesMachine : StatesHandler<State> { interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CustomizableHandlerHolder<in T, T>>
): T? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/** /**
* Starts handling of [State]s * Starts handling of [State]s
*/ */
@ -26,15 +26,15 @@ interface StatesMachine : StatesHandler<State> {
/** /**
* Start chain of [State]s witn [state] * Start chain of [State]s witn [state]
*/ */
suspend fun startChain(state: State) suspend fun startChain(state: T)
companion object { companion object {
/** /**
* Creates [DefaultStatesMachine] * Creates [DefaultStatesMachine]
*/ */
operator fun invoke( operator fun <T: State> invoke(
statesManager: StatesManager, statesManager: StatesManager<T>,
handlers: List<StatesHandlerHolder<*>> handlers: List<CustomizableHandlerHolder<in T, T>>
) = DefaultStatesMachine(statesManager, handlers) ) = DefaultStatesMachine(statesManager, handlers)
} }
} }
@ -43,14 +43,14 @@ interface StatesMachine : StatesHandler<State> {
* 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
*/ */
class DefaultStatesMachine ( class DefaultStatesMachine <T: State>(
private val statesManager: StatesManager, private val statesManager: StatesManager<T>,
private val handlers: List<StatesHandlerHolder<*>> private val handlers: List<CustomizableHandlerHolder<in T, T>>
) : StatesMachine { ) : StatesMachine<T> {
/** /**
* Will call [launchStateHandling] for state handling * Will call [launchStateHandling] for state handling
*/ */
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers) override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
/** /**
* Launch handling of states. On [statesManager] [StatesManager.onStartChain], * Launch handling of states. On [statesManager] [StatesManager.onStartChain],
@ -59,7 +59,7 @@ class DefaultStatesMachine (
* [StatesManager.endChain]. * [StatesManager.endChain].
*/ */
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (State) -> Unit = { state: State -> val statePerformer: suspend (T) -> Unit = { state: T ->
val newState = launchStateHandling(state, handlers) val newState = launchStateHandling(state, handlers)
if (newState != null) { if (newState != null) {
statesManager.update(state, newState) statesManager.update(state, newState)
@ -82,7 +82,7 @@ class DefaultStatesMachine (
/** /**
* Just calls [StatesManager.startChain] of [statesManager] * Just calls [StatesManager.startChain] of [statesManager]
*/ */
override suspend fun startChain(state: State) { override suspend fun startChain(state: T) {
statesManager.startChain(state) statesManager.startChain(state)
} }
} }

View File

@ -2,29 +2,29 @@ package dev.inmo.micro_utils.fsm.common
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
interface StatesManager { interface StatesManager<T : State> {
val onChainStateUpdated: Flow<Pair<State, State>> val onChainStateUpdated: Flow<Pair<T, T>>
val onStartChain: Flow<State> val onStartChain: Flow<T>
val onEndChain: Flow<State> val onEndChain: Flow<T>
/** /**
* Must set current set using [State.context] * Must set current set using [State.context]
*/ */
suspend fun update(old: State, new: State) suspend fun update(old: T, new: T)
/** /**
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already * Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
* busy by the other [State] * busy by the other [State]
*/ */
suspend fun startChain(state: State) suspend fun startChain(state: T)
/** /**
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just * Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
* ignored * ignored
*/ */
suspend fun endChain(state: State) suspend fun endChain(state: T)
suspend fun getActiveStates(): List<State> suspend fun getActiveStates(): List<T>
} }

View File

@ -4,36 +4,47 @@ import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.managers.* import dev.inmo.micro_utils.fsm.common.managers.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FSMBuilder( class FSMBuilder<T : State>(
var statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
var defaultStateHandler: StatesHandler<State>? = StatesHandler { null } var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
) { ) {
private var states = mutableListOf<StatesHandlerHolder<*>>() private var states = mutableListOf<CustomizableHandlerHolder<T, T>>()
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) { fun <I : T> add(kClass: KClass<I>, handler: StatesHandler<I, T>) {
states.add(StatesHandlerHolder(kClass, false, handler)) states.add(StateHandlerHolder(kClass, false, handler))
} }
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) { fun <I : T> add(filter: suspend (state: State) -> Boolean, handler: StatesHandler<I, T>) {
states.add(StatesHandlerHolder(kClass, true, handler)) states.add(handler.holder(filter))
}
fun <I : T> addStrict(kClass: KClass<I>, handler: StatesHandler<I, T>) {
states.add(StateHandlerHolder(kClass, true, handler))
}
inline fun <reified I : T> onStateOrSubstate(handler: StatesHandler<I, T>) {
add(I::class, handler)
}
inline fun <reified I : T> strictlyOn(handler: StatesHandler<I, T>) {
addStrict(I::class, handler)
}
inline fun <reified I : T> doWhen(
noinline filter: suspend (state: State) -> Boolean,
handler: StatesHandler<I, T>
) {
add(filter, handler)
} }
fun build() = StatesMachine( fun build() = StatesMachine(
statesManager, statesManager,
states.toList().let { list -> states.toList().let { list ->
defaultStateHandler ?.let { list + it.holder(false) } ?: list defaultStateHandler ?.let { list + it.holder { true } } ?: list
} }
) )
} }
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) { fun <T : State> buildFSM(
add(I::class, handler) block: FSMBuilder<T>.() -> Unit
} ): StatesMachine<T> = FSMBuilder<T>().apply(block).build()
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
addStrict(I::class, handler)
}
fun buildFSM(
block: FSMBuilder.() -> Unit
): StatesMachine = FSMBuilder().apply(block).build()

View File

@ -9,25 +9,25 @@ import kotlinx.coroutines.sync.withLock
/** /**
* Implement this repo if you want to use some custom repo for [DefaultStatesManager] * Implement this repo if you want to use some custom repo for [DefaultStatesManager]
*/ */
interface DefaultStatesManagerRepo { interface DefaultStatesManagerRepo<T : State> {
/** /**
* Must save [state] as current state of chain with [State.context] of [state] * Must save [state] as current state of chain with [State.context] of [state]
*/ */
suspend fun set(state: State) suspend fun set(state: T)
/** /**
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should * Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
* NOT be removed * NOT be removed
*/ */
suspend fun removeState(state: State) suspend fun removeState(state: T)
/** /**
* @return Current list of available and saved states * @return Current list of available and saved states
*/ */
suspend fun getStates(): List<State> suspend fun getStates(): List<T>
/** /**
* @return Current state by [context] * @return Current state by [context]
*/ */
suspend fun getContextState(context: Any): State? suspend fun getContextState(context: Any): T?
/** /**
* @return Current state by [context] * @return Current state by [context]
@ -43,21 +43,21 @@ interface DefaultStatesManagerRepo {
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by * 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 * new state by using [endChain] with that state
*/ */
class DefaultStatesManager( class DefaultStatesManager<T : State>(
private val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(), private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) : StatesManager { ) : StatesManager<T> {
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0) private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow() override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<State>(0) private val _onStartChain = MutableSharedFlow<T>(0)
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow() override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0) private val _onEndChain = MutableSharedFlow<T>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow() override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
private val mapMutex = Mutex() private val mapMutex = Mutex()
override suspend fun update(old: State, new: State) = mapMutex.withLock { override suspend fun update(old: T, new: T) = mapMutex.withLock {
val stateByOldContext: State? = repo.getContextState(old.context) val stateByOldContext: T? = repo.getContextState(old.context)
when { when {
stateByOldContext != old -> return@withLock stateByOldContext != old -> return@withLock
stateByOldContext == null || old.context == new.context -> { stateByOldContext == null || old.context == new.context -> {
@ -76,26 +76,26 @@ class DefaultStatesManager(
} }
} }
override suspend fun startChain(state: State) = mapMutex.withLock { override suspend fun startChain(state: T) = mapMutex.withLock {
if (!repo.contains(state.context)) { if (!repo.contains(state.context)) {
repo.set(state) repo.set(state)
_onStartChain.emit(state) _onStartChain.emit(state)
} }
} }
private suspend fun endChainWithoutLock(state: State) { private suspend fun endChainWithoutLock(state: T) {
if (repo.getContextState(state.context) == state) { if (repo.getContextState(state.context) == state) {
repo.removeState(state) repo.removeState(state)
_onEndChain.emit(state) _onEndChain.emit(state)
} }
} }
override suspend fun endChain(state: State) { override suspend fun endChain(state: T) {
mapMutex.withLock { mapMutex.withLock {
endChainWithoutLock(state) endChainWithoutLock(state)
} }
} }
override suspend fun getActiveStates(): List<State> = repo.getStates() override suspend fun getActiveStates(): List<T> = repo.getStates()
} }

View File

@ -6,20 +6,20 @@ import dev.inmo.micro_utils.fsm.common.State
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive * Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
* functionality * functionality
*/ */
class InMemoryDefaultStatesManagerRepo( class InMemoryDefaultStatesManagerRepo<T : State>(
private val map: MutableMap<Any, State> = mutableMapOf() private val map: MutableMap<Any, T> = mutableMapOf()
) : DefaultStatesManagerRepo { ) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: State) { override suspend fun set(state: T) {
map[state.context] = state map[state.context] = state
} }
override suspend fun removeState(state: State) { override suspend fun removeState(state: T) {
map.remove(state.context) map.remove(state.context)
} }
override suspend fun getStates(): List<State> = map.values.toList() override suspend fun getStates(): List<T> = map.values.toList()
override suspend fun getContextState(context: Any): State? = map[context] override suspend fun getContextState(context: Any): T? = map[context]
override suspend fun contains(context: Any): Boolean = map.contains(context) override suspend fun contains(context: Any): Boolean = map.contains(context)
} }

View File

@ -7,62 +7,13 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
/** /**
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
*
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context] * @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by * 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 * new state by using [endChain] with that state
*/ */
class InMemoryStatesManager( @Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } fun <T: State> InMemoryStatesManager(
) : StatesManager { onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0) ) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<State>(0)
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
private val contextsToStates = mutableMapOf<Any, State>()
private val mapMutex = Mutex()
override suspend fun update(old: State, new: State) = mapMutex.withLock {
when {
contextsToStates[old.context] != old -> return@withLock
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
contextsToStates[old.context] = new
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = contextsToStates.getValue(new.context)
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
endChainWithoutLock(stateOnNewOneContext)
contextsToStates.remove(old.context)
contextsToStates[new.context] = new
_onChainStateUpdated.emit(old to new)
}
}
}
}
override suspend fun startChain(state: State) = mapMutex.withLock {
if (!contextsToStates.containsKey(state.context)) {
contextsToStates[state.context] = state
_onStartChain.emit(state)
}
}
private suspend fun endChainWithoutLock(state: State) {
if (contextsToStates[state.context] == state) {
contextsToStates.remove(state.context)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: State) {
mapMutex.withLock {
endChainWithoutLock(state)
}
}
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
}

View File

@ -1,6 +1,7 @@
import dev.inmo.micro_utils.fsm.common.* import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.dsl.strictlyOn import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
import kotlinx.coroutines.* import kotlinx.coroutines.*
sealed interface TrafficLightState : State { sealed interface TrafficLightState : State {
@ -25,9 +26,9 @@ class PlayableMain {
} }
} }
val statesManager = InMemoryStatesManager() val statesManager = DefaultStatesManager<TrafficLightState>()
val machine = buildFSM { val machine = buildFSM<TrafficLightState> {
strictlyOn<GreenCommon> { strictlyOn<GreenCommon> {
delay(1000L) delay(1000L)
YellowCommon(it.context).also(::println) YellowCommon(it.context).also(::println)

View File

@ -5,21 +5,21 @@ import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.pagination.getAll import dev.inmo.micro_utils.repos.pagination.getAll
class KeyValueBasedDefaultStatesManagerRepo( class KeyValueBasedDefaultStatesManagerRepo<T : State>(
private val keyValueRepo: KeyValueRepo<Any, State> private val keyValueRepo: KeyValueRepo<Any, T>
) : DefaultStatesManagerRepo { ) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: State) { override suspend fun set(state: T) {
keyValueRepo.set(state.context, state) keyValueRepo.set(state.context, state)
} }
override suspend fun removeState(state: State) { override suspend fun removeState(state: T) {
if (keyValueRepo.get(state.context) == state) { if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context) keyValueRepo.unset(state.context)
} }
} }
override suspend fun getStates(): List<State> = keyValueRepo.getAll { keys(it) }.map { it.second } override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
override suspend fun getContextState(context: Any): State? = keyValueRepo.get(context) override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
} }

View File

@ -1,84 +0,0 @@
package dev.inmo.micro_utils.fsm.repos.common
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@Deprecated("Replace with DefaultStatesManager and KeyValueBasedDefaultStatesManagerRepo")
class KeyValueBasedStatesManager(
private val keyValueRepo: KeyValueRepo<Any, State>,
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
) : StatesManager {
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
override val onStartChain: Flow<State> = keyValueRepo.onNewValue.map { it.second }
private val mutex = Mutex()
override suspend fun update(old: State, new: State) {
mutex.withLock {
when {
keyValueRepo.get(old.context) != old -> return@withLock
old.context == new.context || !keyValueRepo.contains(new.context) -> {
keyValueRepo.set(old.context, new)
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = keyValueRepo.get(new.context)!!
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
endChainWithoutLock(stateOnNewOneContext)
keyValueRepo.unset(old.context)
keyValueRepo.set(new.context, new)
_onChainStateUpdated.emit(old to new)
}
}
}
}
}
override suspend fun startChain(state: State) {
if (!keyValueRepo.contains(state.context)) {
keyValueRepo.set(state.context, state)
}
}
private suspend fun endChainWithoutLock(state: State) {
if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: State) {
mutex.withLock { endChainWithoutLock(state) }
}
override suspend fun getActiveStates(): List<State> {
return keyValueRepo.getAll { keys(it) }.map { it.second }
}
}
inline fun <reified TargetContextType, reified TargetStateType> createStatesManager(
targetKeyValueRepo: KeyValueRepo<TargetContextType, TargetStateType>,
noinline contextToOutTransformer: suspend Any.() -> TargetContextType,
noinline stateToOutTransformer: suspend State.() -> TargetStateType,
noinline outToContextTransformer: suspend TargetContextType.() -> Any,
noinline outToStateTransformer: suspend TargetStateType.() -> State,
) = KeyValueBasedStatesManager(
targetKeyValueRepo.withMapper<Any, State, TargetContextType, TargetStateType>(
contextToOutTransformer,
stateToOutTransformer,
outToContextTransformer,
outToStateTransformer
)
)

View File

@ -45,5 +45,5 @@ dokka_version=1.5.31
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.7.5 version=0.8.0
android_code_version=79 android_code_version=80