mirror of
https://github.com/InsanusMokrassar/TelegramBotApiLibraries.git
synced 2025-10-26 17:50:20 +00:00
Compare commits
19 Commits
0.0.4
...
feature/fs
| Author | SHA1 | Date | |
|---|---|---|---|
| b45bd3192a | |||
| c4e18ad25f | |||
| 6f17a53146 | |||
| f04f065ac5 | |||
| 7ac8effb5f | |||
| 4dc009b222 | |||
| 0ace1c760d | |||
| c4e7e05ff2 | |||
| 681848a908 | |||
| 1e2cf00ffe | |||
| d940a266ac | |||
| cdbcf29409 | |||
| a88b6d17a2 | |||
| e0cf102ec8 | |||
| 3dc2515e57 | |||
| ee21a44270 | |||
| eee918cd9f | |||
| 459d4dc5e2 | |||
| b974d4dfdc |
@@ -3,5 +3,5 @@
|
||||
This project was created due to neccessity of additional libraries over [tgbotapi](https://github.com/InsanusMokrassar/TelegramBotAPI).
|
||||
Currently there are plans to create several libraries at the start of this project:
|
||||
|
||||
* Cache library for media (saving and autorefreshing of `fileId`)
|
||||
* Cache library for admins (saving chat admins, autoupdate and refreshing by command (maybe))
|
||||
* Cache library for media (saving and autorefreshing of `fileId`) (**currently in TBD state**)
|
||||
* Cache library for admins (saving chat admins, autoupdate and refreshing by command (maybe)) (you may retrieve it using github packages for now)
|
||||
|
||||
@@ -8,7 +8,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||
|
||||
17
fsm/core/build.gradle
Normal file
17
fsm/core/build.gradle
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,15 @@
|
||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class StateHandlerHolder<I : State>(
|
||||
private val inputKlass: KClass<I>,
|
||||
private val strict: Boolean = false,
|
||||
private val delegateTo: StatesHandler<I>
|
||||
) : StatesHandler<State> {
|
||||
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
||||
|
||||
override suspend fun StatesMachine.handleState(state: State): State? {
|
||||
return delegateTo.run { handleState(state as I) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
||||
|
||||
fun interface StatesHandler<I : State> {
|
||||
suspend fun StatesMachine.handleState(state: I): State?
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
|
||||
private suspend fun <I : State> StatesMachine.launchStateHandling(
|
||||
state: State,
|
||||
handlers: List<StateHandlerHolder<out I>>
|
||||
): State? {
|
||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||
handleState(state)
|
||||
}
|
||||
}
|
||||
|
||||
class StatesMachine (
|
||||
private val statesManager: StatesManager,
|
||||
private val handlers: List<StateHandlerHolder<*>>
|
||||
) : StatesHandler<State> {
|
||||
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
||||
|
||||
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||
val statePerformer: suspend (State) -> Unit = { state: State ->
|
||||
val newState = launchStateHandling(state, handlers)
|
||||
if (newState != null) {
|
||||
statesManager.update(state, newState)
|
||||
} else {
|
||||
statesManager.endChain(state)
|
||||
}
|
||||
}
|
||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { statePerformer(it) }
|
||||
}
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { statePerformer(it.second) }
|
||||
}
|
||||
|
||||
statesManager.getActiveStates().forEach {
|
||||
launch { statePerformer(it) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startChain(state: State) {
|
||||
statesManager.startChain(state)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
interface StatesManager {
|
||||
val onChainStateUpdated: Flow<Pair<State, State>>
|
||||
val onStartChain: Flow<State>
|
||||
val onEndChain: Flow<State>
|
||||
|
||||
|
||||
/**
|
||||
* Must set current set using [State.context]
|
||||
*/
|
||||
suspend fun update(old: State, new: State)
|
||||
|
||||
/**
|
||||
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
||||
* busy by the other [State]
|
||||
*/
|
||||
suspend fun startChain(state: State)
|
||||
|
||||
/**
|
||||
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
||||
* ignored
|
||||
*/
|
||||
suspend fun endChain(state: State)
|
||||
|
||||
suspend fun getActiveStates(): List<State>
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<Pair<State, State>>(0)
|
||||
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()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package dev.inmo.tgbotapi.libraries.fsm.core.dsl
|
||||
|
||||
import dev.inmo.tgbotapi.libraries.fsm.core.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FSMBuilder(
|
||||
var statesManager: StatesManager = InMemoryStatesManager()
|
||||
) {
|
||||
private var states = mutableListOf<StateHandlerHolder<*>>()
|
||||
|
||||
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
|
||||
states.add(StateHandlerHolder(kClass, false, handler))
|
||||
}
|
||||
|
||||
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
|
||||
states.add(StateHandlerHolder(kClass, true, handler))
|
||||
}
|
||||
|
||||
fun build() = StatesMachine(
|
||||
statesManager,
|
||||
states.toList()
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
|
||||
add(I::class, handler)
|
||||
}
|
||||
|
||||
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
|
||||
addStrict(I::class, handler)
|
||||
}
|
||||
|
||||
fun buildFSM(
|
||||
block: FSMBuilder.() -> Unit
|
||||
): StatesMachine = FSMBuilder().apply(block).build()
|
||||
54
fsm/core/src/jvmTest/kotlin/PlayableMain.kt
Normal file
54
fsm/core/src/jvmTest/kotlin/PlayableMain.kt
Normal file
@@ -0,0 +1,54 @@
|
||||
import dev.inmo.tgbotapi.libraries.fsm.core.*
|
||||
import dev.inmo.tgbotapi.libraries.fsm.core.dsl.buildFSM
|
||||
import dev.inmo.tgbotapi.libraries.fsm.core.dsl.strictlyOn
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
|
||||
sealed interface TrafficLightState : ImmediateOrNeverState {
|
||||
val trafficLightNumber: Int
|
||||
override val context: Int
|
||||
get() = trafficLightNumber
|
||||
}
|
||||
data class GreenCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||
data class YellowCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||
data class RedCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||
|
||||
class PlayableMain {
|
||||
fun test() {
|
||||
runBlocking {
|
||||
val countOfTrafficLights = 10
|
||||
val initialStates = (0 until countOfTrafficLights).map {
|
||||
when (0/*Random.nextInt(3)*/) {
|
||||
0 -> GreenCommon(it)
|
||||
1 -> YellowCommon(it)
|
||||
else -> RedCommon(it)
|
||||
}
|
||||
}
|
||||
|
||||
val statesManager = InMemoryStatesManager()
|
||||
|
||||
val machine = buildFSM {
|
||||
strictlyOn<GreenCommon> {
|
||||
delay(1000L)
|
||||
YellowCommon(it.context).also(::println)
|
||||
}
|
||||
strictlyOn<YellowCommon> {
|
||||
delay(1000L)
|
||||
RedCommon(it.context).also(::println)
|
||||
}
|
||||
strictlyOn<RedCommon> {
|
||||
delay(1000L)
|
||||
GreenCommon(it.context).also(::println)
|
||||
}
|
||||
this.statesManager = statesManager
|
||||
}
|
||||
|
||||
initialStates.forEach { machine.startChain(it) }
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
machine.start(scope).join()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
1
fsm/core/src/main/AndroidManifest.xml
Normal file
1
fsm/core/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.tgbotapi.libraries.fsm.core"/>
|
||||
17
fsm/tgbotapi/build.gradle
Normal file
17
fsm/tgbotapi/build.gradle
Normal file
@@ -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 project(":tgbotapi.libraries.fsm.core")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
fsm/tgbotapi/src/main/AndroidManifest.xml
Normal file
1
fsm/tgbotapi/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.tgbotapi.libraries.fsm.tgbotapi"/>
|
||||
@@ -6,21 +6,22 @@ kotlin.incremental.js=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
kotlin_version=1.4.32
|
||||
kotlin_serialisation_core_version=1.1.0
|
||||
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
|
||||
|
||||
tgbotapi_version=0.33.3
|
||||
micro_utils_version=0.4.33
|
||||
exposed_version=0.30.1
|
||||
plagubot_version=0.1.8
|
||||
tgbotapi_version=0.35.0
|
||||
micro_utils_version=0.5.12
|
||||
exposed_version=0.32.1
|
||||
plagubot_version=0.3.0
|
||||
|
||||
# ANDROID
|
||||
|
||||
android_minSdkVersion=21
|
||||
android_compileSdkVersion=30
|
||||
android_buildToolsVersion=30.0.2
|
||||
android_buildToolsVersion=30.0.3
|
||||
dexcount_version=2.0.0
|
||||
junit_version=4.12
|
||||
test_ext_junit_version=1.1.2
|
||||
@@ -28,10 +29,10 @@ espresso_core=3.3.0
|
||||
|
||||
# Dokka
|
||||
|
||||
dokka_version=1.4.20
|
||||
dokka_version=1.4.32
|
||||
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.0.4
|
||||
android_code_version=4
|
||||
version=0.0.10
|
||||
android_code_version=9
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project.version = "$version" + System.getenv("additional_version")
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project.version = "$version" + System.getenv("additional_version")
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
@@ -4,10 +4,8 @@ project.group = "$group"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.main.kotlinOptions.useIR = true
|
||||
}
|
||||
js (BOTH) {
|
||||
jvm()
|
||||
js (IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ String[] includes = [
|
||||
":cache:admins:common",
|
||||
":cache:admins:micro_utils",
|
||||
":cache:admins:plagubot",
|
||||
":cache:media"
|
||||
":cache:media",
|
||||
|
||||
":fsm:core",
|
||||
":fsm:tgbotapi"
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user