From fab789d9c0b31f1a30276a03f86ae679653cef2a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Oct 2021 12:14:02 +0600 Subject: [PATCH] start rework of FSM states manager --- .../fsm/common/InMemoryStatesManager.kt | 6 ++ .../micro_utils/fsm/common/StatesManager.kt | 62 ------------- .../common/managers/DefaultStatesManager.kt | 90 +++++++++++++++++++ .../common/managers/InMemoryStatesManager.kt | 68 ++++++++++++++ ...alueBasedDefaultStatesManagerStatesRepo.kt | 29 ++++++ .../common/KeyValueBasedStatesManager.kt | 1 + 6 files changed, 194 insertions(+), 62 deletions(-) create mode 100644 fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/InMemoryStatesManager.kt create mode 100644 fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt create mode 100644 fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt create mode 100644 fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerStatesRepo.kt diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/InMemoryStatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/InMemoryStatesManager.kt new file mode 100644 index 00000000000..d00bbbc42fe --- /dev/null +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/InMemoryStatesManager.kt @@ -0,0 +1,6 @@ +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 diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt index ac52d84b6e8..6b341aafa60 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt @@ -1,8 +1,6 @@ package dev.inmo.micro_utils.fsm.common import kotlinx.coroutines.flow.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock interface StatesManager { val onChainStateUpdated: Flow> @@ -30,63 +28,3 @@ interface StatesManager { suspend fun getActiveStates(): List } -/** - * @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 - * new state by using [endChain] with that state - */ -class InMemoryStatesManager( - private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } -) : StatesManager { - private val _onChainStateUpdated = MutableSharedFlow>(0) - override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() - private val _onStartChain = MutableSharedFlow(0) - override val onStartChain: Flow = _onStartChain.asSharedFlow() - private val _onEndChain = MutableSharedFlow(0) - override val onEndChain: Flow = _onEndChain.asSharedFlow() - - private val contextsToStates = mutableMapOf() - 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 = contextsToStates.values.toList() - -} diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt new file mode 100644 index 00000000000..221d9c48ef9 --- /dev/null +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt @@ -0,0 +1,90 @@ +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 + +interface DefaultStatesManagerStatesRepo { + /** + * Must save [state] as current state of chain with [State.context] of [state] + */ + suspend fun newState(state: State) + /** + * Must save [to] as instead of [from]. In case when they have different [State.context] - [from] must be just + * removed like with [removeState] + */ + suspend fun updateState(from: State, to: State) + /** + * Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should + * NOT be removed + */ + suspend fun removeState(state: State) + /** + * @return current list of available and saved states + */ + suspend fun getStates(): List +} + +/** + * @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 + * new state by using [endChain] with that state + */ +class DefaultStatesManager( + private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }, + private val repo: DefaultStatesManagerStatesRepo +) : StatesManager { + private val _onChainStateUpdated = MutableSharedFlow>(0) + override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() + private val _onStartChain = MutableSharedFlow(0) + override val onStartChain: Flow = _onStartChain.asSharedFlow() + private val _onEndChain = MutableSharedFlow(0) + override val onEndChain: Flow = _onEndChain.asSharedFlow() + + private val contextsToStates = mutableMapOf() + 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 = contextsToStates.values.toList() + +} diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt new file mode 100644 index 00000000000..4d8094bd48b --- /dev/null +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt @@ -0,0 +1,68 @@ +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 + +/** + * @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 + * new state by using [endChain] with that state + */ +class InMemoryStatesManager( + private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } +) : StatesManager { + private val _onChainStateUpdated = MutableSharedFlow>(0) + override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() + private val _onStartChain = MutableSharedFlow(0) + override val onStartChain: Flow = _onStartChain.asSharedFlow() + private val _onEndChain = MutableSharedFlow(0) + override val onEndChain: Flow = _onEndChain.asSharedFlow() + + private val contextsToStates = mutableMapOf() + 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 = contextsToStates.values.toList() + +} diff --git a/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerStatesRepo.kt b/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerStatesRepo.kt new file mode 100644 index 00000000000..73ad2929041 --- /dev/null +++ b/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerStatesRepo.kt @@ -0,0 +1,29 @@ +package dev.inmo.micro_utils.fsm.repos.common + +import dev.inmo.micro_utils.fsm.common.State +import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerStatesRepo +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.pagination.getAll + +class KeyValueBasedDefaultStatesManagerStatesRepo( + private val keyValueRepo: KeyValueRepo +) : DefaultStatesManagerStatesRepo { + override suspend fun newState(state: State) { + keyValueRepo.set(state.context, state) + } + + override suspend fun updateState(from: State, to: State) { + if (from.context != to.context && keyValueRepo.get(from.context) == from) { + keyValueRepo.unset(from.context) + } + keyValueRepo.set(to.context, to) + } + + override suspend fun removeState(state: State) { + if (keyValueRepo.get(state.context) == state) { + keyValueRepo.unset(state.context) + } + } + + override suspend fun getStates(): List = keyValueRepo.getAll { keys(it) }.map { it.second } +} diff --git a/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedStatesManager.kt b/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedStatesManager.kt index 4f6f98d536c..bffe06b4180 100644 --- a/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedStatesManager.kt +++ b/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedStatesManager.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +@Deprecated class KeyValueBasedStatesManager( private val keyValueRepo: KeyValueRepo, private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }