From ceba81c08f93f3ffea387d712414620a1aec731f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Oct 2021 11:51:55 +0600 Subject: [PATCH 01/10] start 0.7.1 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1b60f6fa0..f9fef295aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.7.1 + ## 0.7.0 **THIS VERSION HAS MIGRATED FROM KOTLINX DATETIME TO KORLIBS KLOCK. CAREFUL** diff --git a/gradle.properties b/gradle.properties index 1278710500a..1735f5ea4b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -45,5 +45,5 @@ dokka_version=1.5.30 # Project data group=dev.inmo -version=0.7.0 -android_code_version=74 +version=0.7.1 +android_code_version=75 From fab789d9c0b31f1a30276a03f86ae679653cef2a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Oct 2021 12:14:02 +0600 Subject: [PATCH 02/10] 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 } From 119a0588cc7bd8e8cda6b3665f083447ceb7ef4d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Oct 2021 13:30:25 +0600 Subject: [PATCH 03/10] DefaultStatesManager --- CHANGELOG.md | 5 ++ .../common/managers/DefaultStatesManager.kt | 53 +++++++++++-------- ... KeyValueBasedDefaultStatesManagerRepo.kt} | 18 +++---- .../common/KeyValueBasedStatesManager.kt | 2 +- 4 files changed, 43 insertions(+), 35 deletions(-) rename fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/{KeyValueBasedDefaultStatesManagerStatesRepo.kt => KeyValueBasedDefaultStatesManagerRepo.kt} (61%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9fef295aa3..58b1ffb5b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 0.7.1 +* `FSM`: + * `Common` + * New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of + data info + ## 0.7.0 **THIS VERSION HAS MIGRATED FROM KOTLINX DATETIME TO KORLIBS KLOCK. CAREFUL** 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 index 221d9c48ef9..dda9ae1729e 100644 --- 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 @@ -6,35 +6,42 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -interface DefaultStatesManagerStatesRepo { +interface DefaultStatesManagerRepo { /** * 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) + suspend fun set(state: 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 + * @return Current list of available and saved states */ suspend fun getStates(): List + + /** + * @return Current state by [context] + */ + suspend fun getContextState(context: Any): State? + + /** + * @return Current state by [context] + */ + suspend fun contains(context: Any): Boolean } /** * @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 + * @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE + * any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. */ class DefaultStatesManager( private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }, - private val repo: DefaultStatesManagerStatesRepo + private val repo: DefaultStatesManagerRepo ) : StatesManager { private val _onChainStateUpdated = MutableSharedFlow>(0) override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() @@ -43,22 +50,22 @@ class DefaultStatesManager( 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 { + val stateByOldContext: State? = repo.getContextState(old.context) when { - contextsToStates[old.context] != old -> return@withLock - old.context == new.context || !contextsToStates.containsKey(new.context) -> { - contextsToStates[old.context] = new + stateByOldContext != old -> return@withLock + stateByOldContext == null || old.context == new.context -> { + repo.set(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 + val stateOnNewOneContext = repo.getContextState(new.context) + if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) { + stateOnNewOneContext ?.let { endChainWithoutLock(it) } + repo.removeState(old) + repo.set(new) _onChainStateUpdated.emit(old to new) } } @@ -66,15 +73,15 @@ class DefaultStatesManager( } override suspend fun startChain(state: State) = mapMutex.withLock { - if (!contextsToStates.containsKey(state.context)) { - contextsToStates[state.context] = state + if (!repo.contains(state.context)) { + repo.set(state) _onStartChain.emit(state) } } private suspend fun endChainWithoutLock(state: State) { - if (contextsToStates[state.context] == state) { - contextsToStates.remove(state.context) + if (repo.getContextState(state.context) == state) { + repo.removeState(state) _onEndChain.emit(state) } } @@ -85,6 +92,6 @@ class DefaultStatesManager( } } - override suspend fun getActiveStates(): List = contextsToStates.values.toList() + override suspend fun getActiveStates(): List = repo.getStates() } 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/KeyValueBasedDefaultStatesManagerRepo.kt similarity index 61% rename from fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerStatesRepo.kt rename to fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerRepo.kt index 73ad2929041..68b3b687db6 100644 --- 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/KeyValueBasedDefaultStatesManagerRepo.kt @@ -1,24 +1,17 @@ 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.fsm.common.managers.DefaultStatesManagerRepo import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.pagination.getAll -class KeyValueBasedDefaultStatesManagerStatesRepo( +class KeyValueBasedDefaultStatesManagerRepo( private val keyValueRepo: KeyValueRepo -) : DefaultStatesManagerStatesRepo { - override suspend fun newState(state: State) { +) : DefaultStatesManagerRepo { + override suspend fun set(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) @@ -26,4 +19,7 @@ class KeyValueBasedDefaultStatesManagerStatesRepo( } override suspend fun getStates(): List = keyValueRepo.getAll { keys(it) }.map { it.second } + override suspend fun getContextState(context: Any): State? = keyValueRepo.get(context) + + override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) } 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 bffe06b4180..136bfd45c9c 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,7 +9,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -@Deprecated +@Deprecated("Replace with DefaultStatesManager and KeyValueBasedDefaultStatesManagerRepo") class KeyValueBasedStatesManager( private val keyValueRepo: KeyValueRepo, private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } From 7d3b1f8e7570c17471aa52e5a037ed9d4973d7ac Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Oct 2021 13:56:58 +0600 Subject: [PATCH 04/10] StatesMachine is interface --- .../micro_utils/fsm/common/StatesMachine.kt | 23 +++++++++++++++---- .../micro_utils/fsm/common/dsl/FSMBuilder.kt | 4 +++- .../common/managers/DefaultStatesManager.kt | 4 ++-- .../InMemoryDefaultStatesManagerRepo.kt | 21 +++++++++++++++++ 4 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt index 5c2ea9592d9..dde2cdc11b2 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt @@ -13,13 +13,28 @@ private suspend fun StatesMachine.launchStateHandling( } } -class StatesMachine ( +interface StatesMachine : StatesHandler { + fun start(scope: CoroutineScope): Job + suspend fun startChain(state: State) + + companion object { + /** + * Creates [DefaultStatesMachine] + */ + operator fun invoke( + statesManager: StatesManager, + handlers: List> + ) = DefaultStatesMachine(statesManager, handlers) + } +} + +class DefaultStatesMachine ( private val statesManager: StatesManager, private val handlers: List> -) : StatesHandler { +) : StatesMachine { override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers) - fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { + override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { val statePerformer: suspend (State) -> Unit = { state: State -> val newState = launchStateHandling(state, handlers) if (newState != null) { @@ -40,7 +55,7 @@ class StatesMachine ( } } - suspend fun startChain(state: State) { + override suspend fun startChain(state: State) { statesManager.startChain(state) } } diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/dsl/FSMBuilder.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/dsl/FSMBuilder.kt index f0694f360ef..a5fd74a5ddb 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/dsl/FSMBuilder.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/dsl/FSMBuilder.kt @@ -1,10 +1,12 @@ package dev.inmo.micro_utils.fsm.common.dsl import dev.inmo.micro_utils.fsm.common.* +import dev.inmo.micro_utils.fsm.common.managers.* +import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager import kotlin.reflect.KClass class FSMBuilder( - var statesManager: StatesManager = InMemoryStatesManager() + var statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()) ) { private var states = mutableListOf>() 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 index dda9ae1729e..3479ff53637 100644 --- 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 @@ -40,8 +40,8 @@ interface DefaultStatesManagerRepo { * any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. */ class DefaultStatesManager( - private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }, - private val repo: DefaultStatesManagerRepo + private val repo: DefaultStatesManagerRepo, + private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } ) : StatesManager { private val _onChainStateUpdated = MutableSharedFlow>(0) override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt new file mode 100644 index 00000000000..211d3f5370d --- /dev/null +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt @@ -0,0 +1,21 @@ +package dev.inmo.micro_utils.fsm.common.managers + +import dev.inmo.micro_utils.fsm.common.State + +class InMemoryDefaultStatesManagerRepo( + private val map: MutableMap = mutableMapOf() +) : DefaultStatesManagerRepo { + override suspend fun set(state: State) { + map[state.context] = state + } + + override suspend fun removeState(state: State) { + map.remove(state.context) + } + + override suspend fun getStates(): List = map.values.toList() + + override suspend fun getContextState(context: Any): State? = map[context] + + override suspend fun contains(context: Any): Boolean = map.contains(context) +} From bcf67f7e5996c9db6838b718b8781d86c82638a0 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 12 Oct 2021 20:56:00 +0600 Subject: [PATCH 05/10] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1735f5ea4b1..4ab20f7620c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx2g kotlin_version=1.5.31 kotlin_coroutines_version=1.5.2 kotlin_serialisation_core_version=1.3.0 -kotlin_exposed_version=0.35.1 +kotlin_exposed_version=0.35.2 ktor_version=1.6.4 From 5d1cab075d12c69bfaad37289af8f51bbb3c3e80 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 12 Oct 2021 21:03:47 +0600 Subject: [PATCH 06/10] Update gradle.properties --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4ab20f7620c..35c0ea75379 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ kotlin_exposed_version=0.35.2 ktor_version=1.6.4 -klockVersion=2.4.3 +klockVersion=2.4.5 github_release_plugin_version=2.2.12 @@ -40,7 +40,7 @@ crypto_js_version=4.1.1 # Dokka -dokka_version=1.5.30 +dokka_version=1.5.31 # Project data From 4082f65afa0ae8d5bc6dc3dcc9305aefa4bff202 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 13 Oct 2021 13:26:39 +0600 Subject: [PATCH 07/10] AccumulatorFlow --- CHANGELOG.md | 5 +- .../micro_utils/coroutines/AccumulatorFlow.kt | 94 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AccumulatorFlow.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b1ffb5b8f..35fa6ec5b96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ## 0.7.1 +* `Coroutines`: + * `Common`: + * New `Flow` - `AccumulatorFlow` * `FSM`: - * `Common` + * `Common`: * New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of data info diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AccumulatorFlow.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AccumulatorFlow.kt new file mode 100644 index 00000000000..eaee88190aa --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AccumulatorFlow.kt @@ -0,0 +1,94 @@ +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +private sealed interface AccumulatorFlowStep +private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep +private data class SubscribeAccumulatorFlowStep(val channel: Channel) : AccumulatorFlowStep +private data class UnsubscribeAccumulatorFlowStep(val channel: Channel) : AccumulatorFlowStep + +/** + * This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences: + * + * * All unhandled by [FlowCollector] data will not be removed from [AccumulatorFlow] and will be sent to new + * [FlowCollector]s until anybody will handle it + * * Here there are an [activeData] where data [T] will be stored until somebody will handle it + */ +class AccumulatorFlow( + sourceDataFlow: Flow, + scope: CoroutineScope +) : AbstractFlow() { + private val subscope = scope.LinkedSupervisorScope() + private val activeData = ArrayDeque() + private val dataMutex = Mutex() + private val channelsForBroadcast = mutableListOf>() + private val channelsMutex = Mutex() + private val steps = subscope.actor { step -> + when (step) { + is DataRetrievedAccumulatorFlowStep -> { + if (activeData.first() === step.data) { + dataMutex.withLock { + activeData.removeFirst() + } + } + } + is SubscribeAccumulatorFlowStep -> channelsMutex.withLock { + channelsForBroadcast.add(step.channel) + dataMutex.withLock { + val dataToSend = activeData.toList() + safelyWithoutExceptions { + dataToSend.forEach { step.channel.send(it as Any) } + } + } + } + is UnsubscribeAccumulatorFlowStep -> channelsMutex.withLock { + channelsForBroadcast.remove(step.channel) + } + } + } + private val subscriptionJob = sourceDataFlow.subscribeSafelyWithoutExceptions(subscope) { + dataMutex.withLock { + activeData.addLast(it) + } + channelsMutex.withLock { + channelsForBroadcast.forEach { channel -> + safelyWithResult { + channel.send(it as Any) + } + } + } + } + + override suspend fun collectSafely(collector: FlowCollector) { + val channel = Channel(Channel.UNLIMITED, BufferOverflow.SUSPEND) + steps.send(SubscribeAccumulatorFlowStep(channel)) + for (data in channel) { + try { + collector.emit(data as T) + steps.send(DataRetrievedAccumulatorFlowStep(data)) + } finally { + channel.cancel() + steps.send(UnsubscribeAccumulatorFlowStep(channel)) + } + } + } +} + +/** + * Creates [AccumulatorFlow] using [this] as base [Flow] + */ +fun Flow.accumulatorFlow(scope: CoroutineScope): Flow { + return AccumulatorFlow(this, scope) +} + +/** + * Creates [AccumulatorFlow] using [this] with [receiveAsFlow] to get + */ +fun Channel.accumulatorFlow(scope: CoroutineScope): Flow { + return receiveAsFlow().accumulatorFlow(scope) +} From e3da761249eee9ef801e1ce6d0cff36f4a1a1477 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 13 Oct 2021 14:38:50 +0600 Subject: [PATCH 08/10] Fill changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35fa6ec5b96..36c3afd2074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,16 @@ ## 0.7.1 +* `Versions`: + * `Klock`: `2.4.3` -> `2.4.5` + * `Exposed`: `0.35.1` -> `0.35.2` * `Coroutines`: * `Common`: * New `Flow` - `AccumulatorFlow` * `FSM`: * `Common`: + * `InMemoryStatesManager` has been replaced + * `StatesMachine` became an interface * New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of data info From bc7789ad2c38d603d82947890d417f6418599002 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 13 Oct 2021 15:06:58 +0600 Subject: [PATCH 09/10] add kdocs --- .../fsm/common/StateHandlerHolder.kt | 12 ++++++++ .../micro_utils/fsm/common/StatesHandler.kt | 6 ++++ .../micro_utils/fsm/common/StatesMachine.kt | 28 +++++++++++++++++++ .../common/managers/DefaultStatesManager.kt | 12 +++++--- .../InMemoryDefaultStatesManagerRepo.kt | 4 +++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StateHandlerHolder.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StateHandlerHolder.kt index 5b06f317c16..a92f9b96602 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StateHandlerHolder.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StateHandlerHolder.kt @@ -2,13 +2,25 @@ package dev.inmo.micro_utils.fsm.common import kotlin.reflect.KClass +/** + * Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class + * casting in [handleState] + */ class StateHandlerHolder( private val inputKlass: KClass, private val strict: Boolean = false, private val delegateTo: StatesHandler ) : StatesHandler { + /** + * 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 + */ fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) + /** + * Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable] + * to be sure that this [StateHandlerHolder] will be able to handle [state] + */ override suspend fun StatesMachine.handleState(state: State): State? { return delegateTo.run { handleState(state as I) } } diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt index b152b38dea6..0dd89843a27 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt @@ -1,5 +1,11 @@ package dev.inmo.micro_utils.fsm.common +/** + * Default realization of states handler + */ fun interface StatesHandler { + /** + * + */ suspend fun StatesMachine.handleState(state: I): State? } diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt index dde2cdc11b2..11170c5b030 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt @@ -13,8 +13,20 @@ private suspend fun StatesMachine.launchStateHandling( } } +/** + * 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 + * handling until [start] method will be called + */ interface StatesMachine : StatesHandler { + /** + * Starts handling of [State]s + */ fun start(scope: CoroutineScope): Job + + /** + * Start chain of [State]s witn [state] + */ suspend fun startChain(state: State) companion object { @@ -28,12 +40,25 @@ interface StatesMachine : StatesHandler { } } +/** + * Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts + * resolving, and uses [launchStateHandling] for [State] handling + */ class DefaultStatesMachine ( private val statesManager: StatesManager, private val handlers: List> ) : StatesMachine { + /** + * Will call [launchStateHandling] for state handling + */ override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers) + /** + * Launch handling of states. On [statesManager] [StatesManager.onStartChain], + * [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If + * [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise + * [StatesManager.endChain]. + */ override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { val statePerformer: suspend (State) -> Unit = { state: State -> val newState = launchStateHandling(state, handlers) @@ -55,6 +80,9 @@ class DefaultStatesMachine ( } } + /** + * Just calls [StatesManager.startChain] of [statesManager] + */ override suspend fun startChain(state: State) { statesManager.startChain(state) } 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 index 3479ff53637..3488e0ac7d3 100644 --- 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 @@ -6,6 +6,9 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +/** + * Implement this repo if you want to use some custom repo for [DefaultStatesManager] + */ interface DefaultStatesManagerRepo { /** * Must save [state] as current state of chain with [State.context] of [state] @@ -29,18 +32,19 @@ interface DefaultStatesManagerRepo { /** * @return Current state by [context] */ - suspend fun contains(context: Any): Boolean + suspend fun contains(context: Any): Boolean = getContextState(context) != null } /** + * @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE + * any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default will be used + * [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter * @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 - * @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE - * any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. */ class DefaultStatesManager( - private val repo: DefaultStatesManagerRepo, + private val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(), private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } ) : StatesManager { private val _onChainStateUpdated = MutableSharedFlow>(0) diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt index 211d3f5370d..70051e14d07 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryDefaultStatesManagerRepo.kt @@ -2,6 +2,10 @@ package dev.inmo.micro_utils.fsm.common.managers import dev.inmo.micro_utils.fsm.common.State +/** + * Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive + * functionality + */ class InMemoryDefaultStatesManagerRepo( private val map: MutableMap = mutableMapOf() ) : DefaultStatesManagerRepo { From e9c5df4c131b2469d011b2cfbfecb7c7e996d0a6 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 13 Oct 2021 15:09:05 +0600 Subject: [PATCH 10/10] upfill kdocs --- .../kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt index 0dd89843a27..a64f5133d96 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesHandler.kt @@ -5,7 +5,8 @@ package dev.inmo.micro_utils.fsm.common */ fun interface StatesHandler { /** - * + * 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. */ suspend fun StatesMachine.handleState(state: I): State? }