diff --git a/fsm/core/build.gradle b/fsm/core/build.gradle new file mode 100644 index 0000000..7a1b559 --- /dev/null +++ b/fsm/core/build.gradle @@ -0,0 +1,17 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppProjectWithSerializationPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api "dev.inmo:micro_utils.coroutines:$micro_utils_version" + } + } + } +} diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/State.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/State.kt new file mode 100644 index 0000000..7b83733 --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/State.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.libraries.fsm.core + +sealed interface State { + val context: Any +} + +/** + * Use this state as parent of your state in case you want to avoid saving of this state in queue for [context] if this + * queue is not empty + */ +interface ImmediateOrNeverState : State + +/** + * Use this state as parent of your state in case you want to keep saving of this state in queue for [context] if this + * queue is not empty + */ +interface QueueableState : State diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt new file mode 100644 index 0000000..269563b --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt @@ -0,0 +1,15 @@ +package dev.inmo.tgbotapi.libraries.fsm.core + +import kotlin.reflect.KClass + +class StateHandlerHolder( + private val inputKlass: KClass, + private val delegateTo: StatesHandler, + private val strict: Boolean = false +) : StatesHandler { + fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) + + override suspend fun handleState(state: State): O? { + return delegateTo.handleState(state as I) + } +} diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt new file mode 100644 index 0000000..4996e5e --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt @@ -0,0 +1,5 @@ +package dev.inmo.tgbotapi.libraries.fsm.core + +fun interface StatesHandler { + suspend fun handleState(state: I): O? +} diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt new file mode 100644 index 0000000..2b22ec0 --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt @@ -0,0 +1,37 @@ +package dev.inmo.tgbotapi.libraries.fsm.core + +import dev.inmo.micro_utils.coroutines.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.asFlow + +private suspend fun launchStateHandling( + state: State, + handlers: List> +): O? { + return handlers.firstOrNull { it.checkHandleable(state) } ?.handleState( + state + ) +} + +class StatesMachine( + private val statesRepo: StatesRepo, + private val statesQuotaManager: StatesQuotaManager, + private val handlers: List> +) : StatesHandler { + override suspend fun handleState(state: T): O? { + return statesQuotaManager.doOnQuota( + state + ) { + launchStateHandling(state, handlers) + } + } + + fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { + val statesFlow = statesRepo.loadStates().asFlow() + statesRepo.onNewState + statesFlow.subscribeSafelyWithoutExceptions(this) { + val newState = handleState(it) + newState ?.also { statesRepo.saveState(newState) } + statesRepo.removeState(it) + } + } +} diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesQuotaManager.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesQuotaManager.kt new file mode 100644 index 0000000..b182d1a --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesQuotaManager.kt @@ -0,0 +1,5 @@ +package dev.inmo.tgbotapi.libraries.fsm.core + +interface StatesQuotaManager { + suspend fun doOnQuota(state: T, block: suspend (T) -> O?): O? +} diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesRepo.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesRepo.kt new file mode 100644 index 0000000..4c8d568 --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesRepo.kt @@ -0,0 +1,12 @@ +package dev.inmo.tgbotapi.libraries.fsm.core + +import kotlinx.coroutines.flow.Flow + +interface StatesRepo { + val onNewState: Flow + val onStateRemoved: Flow + + suspend fun saveState(state: T) + suspend fun removeState(state: T) + suspend fun loadStates(): List +} diff --git a/fsm/core/src/main/AndroidManifest.xml b/fsm/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8f9ccb8 --- /dev/null +++ b/fsm/core/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/gradle.properties b/gradle.properties index fca372b..6108810 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,7 @@ android.enableJetifier=true kotlin_version=1.5.10 kotlin_serialisation_core_version=1.2.1 +kotlin_coroutines_version=1.5.0 github_release_plugin_version=2.2.12 diff --git a/settings.gradle b/settings.gradle index 40732cb..b95bc89 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,9 @@ String[] includes = [ ":cache:admins:common", ":cache:admins:micro_utils", ":cache:admins:plagubot", - ":cache:media" + ":cache:media", + + ":fsm:core" ]