Compare commits

...

19 Commits

Author SHA1 Message Date
b45bd3192a add dsl preview 2021-06-19 13:57:34 +06:00
c4e18ad25f fixes in architecture and adding playable main 2021-06-19 02:20:47 +06:00
6f17a53146 state to revert 2021-06-19 01:16:39 +06:00
f04f065ac5 start working with fsm 2021-06-18 14:09:02 +06:00
7ac8effb5f update up to 0.0.10 with versions updates 2021-06-18 12:23:57 +06:00
4dc009b222 Merge pull request #5 from InsanusMokrassar/0.0.9
0.0.9
2021-06-12 12:17:09 +06:00
0ace1c760d Update mppProjectWithSerialization.gradle 2021-06-07 19:57:06 +06:00
c4e7e05ff2 Update gradle.properties 2021-06-07 19:53:50 +06:00
681848a908 Update README.md 2021-05-06 12:02:23 +06:00
1e2cf00ffe Merge pull request #4 from InsanusMokrassar/0.0.8
0.0.8
2021-05-06 11:23:15 +06:00
d940a266ac update dependencies 2021-05-05 19:53:47 +06:00
cdbcf29409 start 0.0.8 2021-05-05 19:52:30 +06:00
a88b6d17a2 Merge pull request #3 from InsanusMokrassar/0.0.7
0.0.7
2021-05-01 20:29:25 +06:00
e0cf102ec8 Update gradle.properties 2021-05-01 13:58:47 +06:00
3dc2515e57 Update gradle.properties 2021-05-01 13:57:40 +06:00
ee21a44270 start 0.0.7 2021-05-01 13:56:41 +06:00
eee918cd9f Update gradle.properties 2021-04-25 15:41:40 +06:00
459d4dc5e2 0.0.5 2021-04-18 17:22:48 +06:00
b974d4dfdc Merge pull request #2 from InsanusMokrassar/0.0.4
0.0.4
2021-04-05 21:56:34 +06:00
18 changed files with 322 additions and 20 deletions

View File

@@ -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)

View File

@@ -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
View 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"
}
}
}
}

View File

@@ -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

View File

@@ -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) }
}
}

View File

@@ -0,0 +1,5 @@
package dev.inmo.tgbotapi.libraries.fsm.core
fun interface StatesHandler<I : State> {
suspend fun StatesMachine.handleState(state: I): State?
}

View File

@@ -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)
}
}

View File

@@ -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()
}

View File

@@ -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()

View 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()
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.tgbotapi.libraries.fsm.core"/>

17
fsm/tgbotapi/build.gradle Normal file
View 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")
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.tgbotapi.libraries.fsm.tgbotapi"/>

View File

@@ -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

View File

@@ -1,4 +1,4 @@
project.version = "$version" + System.getenv("additional_version")
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"

View File

@@ -1,4 +1,4 @@
project.version = "$version" + System.getenv("additional_version")
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"

View File

@@ -4,10 +4,8 @@ project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main.kotlinOptions.useIR = true
}
js (BOTH) {
jvm()
js (IR) {
browser()
nodejs()
}

View File

@@ -4,7 +4,10 @@ String[] includes = [
":cache:admins:common",
":cache:admins:micro_utils",
":cache:admins:plagubot",
":cache:media"
":cache:media",
":fsm:core",
":fsm:tgbotapi"
]