mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
5d0bdb9bcf | |||
31fdcf74a5 | |||
afca09cc1d | |||
531d89d9db | |||
6bbbea0bc3 | |||
e337cd98c8 | |||
bcbab3b380 | |||
fb63de7568 | |||
aa45a4ab13 | |||
2af7e2f681 | |||
34fd9edce0 | |||
2a4cb8c5f9 | |||
50ea40bc3a | |||
a77654052d | |||
88aafce552 | |||
4e95d6bfff | |||
38d0e34fb5 | |||
8fbc6b9041 | |||
e8219d6cf4 | |||
6c20fc4ca6 | |||
85cd975492 | |||
1171a717fe | |||
bbe5320312 | |||
00acb9fddd | |||
de3d14dc41 | |||
67ff9cc9b3 | |||
af132103a0 | |||
3b1124a804 | |||
f226c2dfd6 |
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,5 +1,51 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.8.4
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Server`:
|
||||||
|
* Several new `createKtorServer`
|
||||||
|
|
||||||
|
## 0.8.3
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* Ranges intersection functionality
|
||||||
|
* New type `Optional`
|
||||||
|
* `Pagination`:
|
||||||
|
* `Pagination` now extends `ClosedRange<Int>`
|
||||||
|
* `Pagination` intersection functionality
|
||||||
|
|
||||||
|
## 0.8.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.7` -> `2.4.8`
|
||||||
|
* `Serialization`: `1.3.0` -> `1.3.1`
|
||||||
|
* `FSM`:
|
||||||
|
* Now it is possible to pass any `CheckableHandlerHolder` in `FSMBuilder`
|
||||||
|
* Now `StatesMachine` works with `CheckableHandlerHolder` instead of `CustomizableHandlerHolder`
|
||||||
|
|
||||||
|
## 0.8.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.36.1` -> `0.36.2`
|
||||||
|
* `Core KTX`: `1.6.0` -> `1.7.0`
|
||||||
|
|
||||||
|
## 0.8.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.6` -> `2.4.7`
|
||||||
|
* `Ktor`: `1.6.4` -> `1.6.5`
|
||||||
|
* `Exposed`: `0.35.3` -> `0.36.1`
|
||||||
|
* `Common`:
|
||||||
|
* Type `Either` got its own serializer
|
||||||
|
* `FSM`:
|
||||||
|
* `Common`:
|
||||||
|
* Full rework of FSM:
|
||||||
|
* Now it is more flexible for checking of handler opportunity to handle state
|
||||||
|
* Now machine and states managers are type-oriented
|
||||||
|
* `StateHandlerHolder` has been renamed to `CheckableHandlerHolder`
|
||||||
|
* Add opportunity for comfortable adding default state handler
|
||||||
|
|
||||||
## 0.7.4
|
## 0.7.4
|
||||||
|
|
||||||
* `Common`:
|
* `Common`:
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
|
|
||||||
private inline fun <T> getObject(
|
private inline fun <T> getObject(
|
||||||
additional: MutableList<T>,
|
additional: MutableList<T>,
|
||||||
iterator: Iterator<T>
|
iterator: Iterator<T>
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.*
|
||||||
|
import kotlinx.serialization.encoding.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Realization of this interface will contains at least one not null - [t1] or [t2]
|
* Realization of this interface will contains at least one not null - [t1] or [t2]
|
||||||
*
|
*
|
||||||
@@ -10,16 +15,90 @@ package dev.inmo.micro_utils.common
|
|||||||
* @see Either.onFirst
|
* @see Either.onFirst
|
||||||
* @see Either.onSecond
|
* @see Either.onSecond
|
||||||
*/
|
*/
|
||||||
|
@Serializable(EitherSerializer::class)
|
||||||
sealed interface Either<T1, T2> {
|
sealed interface Either<T1, T2> {
|
||||||
val t1: T1?
|
val t1: T1?
|
||||||
val t2: T2?
|
val t2: T2?
|
||||||
|
|
||||||
companion object
|
companion object {
|
||||||
|
fun <T1, T2> serializer(
|
||||||
|
t1Serializer: KSerializer<T1>,
|
||||||
|
t2Serializer: KSerializer<T2>,
|
||||||
|
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EitherSerializer<T1, T2>(
|
||||||
|
t1Serializer: KSerializer<T1>,
|
||||||
|
t2Serializer: KSerializer<T2>,
|
||||||
|
) : KSerializer<Either<T1, T2>> {
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
@InternalSerializationApi
|
||||||
|
override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||||
|
"TypedSerializer",
|
||||||
|
SerialKind.CONTEXTUAL
|
||||||
|
) {
|
||||||
|
element("type", String.serializer().descriptor)
|
||||||
|
element("value", ContextualSerializer(Either::class).descriptor)
|
||||||
|
}
|
||||||
|
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
|
||||||
|
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
@InternalSerializationApi
|
||||||
|
override fun deserialize(decoder: Decoder): Either<T1, T2> {
|
||||||
|
return decoder.decodeStructure(descriptor) {
|
||||||
|
var type: String? = null
|
||||||
|
lateinit var result: Either<T1, T2>
|
||||||
|
while (true) {
|
||||||
|
when (val index = decodeElementIndex(descriptor)) {
|
||||||
|
0 -> type = decodeStringElement(descriptor, 0)
|
||||||
|
1 -> {
|
||||||
|
result = when (type) {
|
||||||
|
"t1" -> decodeSerializableElement(
|
||||||
|
descriptor,
|
||||||
|
1,
|
||||||
|
t1EitherSerializer
|
||||||
|
)
|
||||||
|
"t2" -> decodeSerializableElement(
|
||||||
|
descriptor,
|
||||||
|
1,
|
||||||
|
t2EitherSerializer
|
||||||
|
)
|
||||||
|
else -> error("Unknown type of either: $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompositeDecoder.DECODE_DONE -> break
|
||||||
|
else -> error("Unexpected index: $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
@InternalSerializationApi
|
||||||
|
override fun serialize(encoder: Encoder, value: Either<T1, T2>) {
|
||||||
|
encoder.encodeStructure(descriptor) {
|
||||||
|
when (value) {
|
||||||
|
is EitherFirst -> {
|
||||||
|
encodeStringElement(descriptor, 0, "t1")
|
||||||
|
encodeSerializableElement(descriptor, 1, t1EitherSerializer, value)
|
||||||
|
}
|
||||||
|
is EitherSecond -> {
|
||||||
|
encodeStringElement(descriptor, 0, "t2")
|
||||||
|
encodeSerializableElement(descriptor, 1, t2EitherSerializer, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This type [Either] will always have not nullable [t1]
|
* This type [Either] will always have not nullable [t1]
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class EitherFirst<T1, T2>(
|
data class EitherFirst<T1, T2>(
|
||||||
override val t1: T1
|
override val t1: T1
|
||||||
) : Either<T1, T2> {
|
) : Either<T1, T2> {
|
||||||
@@ -30,6 +109,7 @@ data class EitherFirst<T1, T2>(
|
|||||||
/**
|
/**
|
||||||
* This type [Either] will always have not nullable [t2]
|
* This type [Either] will always have not nullable [t2]
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class EitherSecond<T1, T2>(
|
data class EitherSecond<T1, T2>(
|
||||||
override val t2: T2
|
override val t2: T2
|
||||||
) : Either<T1, T2> {
|
) : Either<T1, T2> {
|
||||||
@@ -44,7 +124,7 @@ inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst
|
|||||||
/**
|
/**
|
||||||
* @return New instance of [EitherSecond]
|
* @return New instance of [EitherSecond]
|
||||||
*/
|
*/
|
||||||
inline fun <T1, T2> Either.Companion.second(t1: T1): Either<T1, T2> = EitherFirst(t1)
|
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will call [block] in case when [Either.t1] of [this] is not null
|
* Will call [block] in case when [Either.t1] of [this] is not null
|
||||||
@@ -63,3 +143,9 @@ inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(crossinline block: (T2) -> Un
|
|||||||
t2 ?.let(block)
|
t2 ?.let(block)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T1, reified T2> Any.either() = when (this) {
|
||||||
|
is T1 -> Either.first<T1, T2>(this)
|
||||||
|
is T2 -> Either.second<T1, T2>(this)
|
||||||
|
else -> error("Incorrect type of either argument $this")
|
||||||
|
}
|
||||||
|
@@ -0,0 +1,75 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type represents [T] as not only potentially nullable data, but also as a data which can not be presented. This
|
||||||
|
* type will be useful in cases when [T] is nullable and null as valuable data too in time of data absence should be
|
||||||
|
* presented by some third type.
|
||||||
|
*
|
||||||
|
* Let's imagine, you have nullable name in some database. In case when name is not nullable everything is clear - null
|
||||||
|
* will represent absence of row in the database. In case when name is nullable null will be a little bit dual-meaning,
|
||||||
|
* cause this null will say nothing about availability of the row (of course, it is exaggerated example)
|
||||||
|
*
|
||||||
|
* @see Optional.presented
|
||||||
|
* @see Optional.absent
|
||||||
|
* @see Optional.optional
|
||||||
|
* @see Optional.onPresented
|
||||||
|
* @see Optional.onAbsent
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class Optional<T> internal constructor(
|
||||||
|
internal val data: T?,
|
||||||
|
internal val dataPresented: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Will create [Optional] with presented data
|
||||||
|
*/
|
||||||
|
fun <T> presented(data: T) = Optional(data, true)
|
||||||
|
/**
|
||||||
|
* Will create [Optional] without data
|
||||||
|
*/
|
||||||
|
fun <T> absent() = Optional<T>(null, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline val <T> T.optional
|
||||||
|
get() = Optional.presented(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
|
||||||
|
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data absent ([Optional.dataPresented] == false)
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
|
||||||
|
if (!dataPresented) { block() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||||
|
*/
|
||||||
|
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
@@ -0,0 +1,19 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
|
||||||
|
start == other.start && endInclusive == other.endInclusive -> start to endInclusive
|
||||||
|
start > other.endInclusive || other.start > endInclusive -> null
|
||||||
|
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IntRange.intersect(
|
||||||
|
other: IntRange
|
||||||
|
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
|
||||||
|
it.first .. it.second
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LongRange.intersect(
|
||||||
|
other: LongRange
|
||||||
|
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {
|
||||||
|
it.first .. it.second
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define checkable holder which can be used to precheck that this handler may handle incoming [State]
|
||||||
|
*/
|
||||||
|
interface CheckableHandlerHolder<I : State, O : State> : StatesHandler<I, O> {
|
||||||
|
suspend fun checkHandleable(state: O): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
|
||||||
|
* casting in [handleState]
|
||||||
|
*/
|
||||||
|
class CustomizableHandlerHolder<I : O, O : State>(
|
||||||
|
private val delegateTo: StatesHandler<I, O>,
|
||||||
|
private val filter: suspend (state: O) -> Boolean
|
||||||
|
) : CheckableHandlerHolder<I, O> {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
override suspend fun checkHandleable(state: O) = filter(state)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
|
||||||
|
* to be sure that this [StatesHandlerHolder] will be able to handle [state]
|
||||||
|
*/
|
||||||
|
override suspend fun StatesMachine<in O>.handleState(state: I): O? {
|
||||||
|
return delegateTo.run { handleState(state) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : O, O : State> CheckableHandlerHolder(
|
||||||
|
inputKlass: KClass<I>,
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CustomizableHandlerHolder(
|
||||||
|
StatesHandler<O, O> {
|
||||||
|
delegateTo.run { handleState(it as I) }
|
||||||
|
},
|
||||||
|
if (strict) {
|
||||||
|
{ it::class == inputKlass }
|
||||||
|
} else {
|
||||||
|
{ inputKlass.isInstance(it) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
|
||||||
|
fun <I : O, O : State> StateHandlerHolder(
|
||||||
|
inputKlass: KClass<I>,
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
|
||||||
|
|
||||||
|
inline fun <reified I : O, O : State> CheckableHandlerHolder(
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CheckableHandlerHolder(I::class, strict, delegateTo)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
|
||||||
|
inline fun <reified I : O, O : State> StateHandlerHolder(
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CheckableHandlerHolder(strict, delegateTo)
|
||||||
|
|
||||||
|
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
|
||||||
|
strict: Boolean = true
|
||||||
|
) = CheckableHandlerHolder<I, O>(
|
||||||
|
I::class,
|
||||||
|
strict,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <I : O, O: State> StatesHandler<I, O>.holder(
|
||||||
|
noinline filter: suspend (state: State) -> Boolean
|
||||||
|
) = CustomizableHandlerHolder<O, O>(
|
||||||
|
{ this@holder.run { handleState(it as I) } },
|
||||||
|
filter
|
||||||
|
)
|
@@ -1,6 +0,0 @@
|
|||||||
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
|
|
@@ -1,27 +0,0 @@
|
|||||||
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<I : State>(
|
|
||||||
private val inputKlass: KClass<I>,
|
|
||||||
private val strict: Boolean = false,
|
|
||||||
private val delegateTo: StatesHandler<I>
|
|
||||||
) : StatesHandler<State> {
|
|
||||||
/**
|
|
||||||
* 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) }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,10 +3,10 @@ package dev.inmo.micro_utils.fsm.common
|
|||||||
/**
|
/**
|
||||||
* Default realization of states handler
|
* Default realization of states handler
|
||||||
*/
|
*/
|
||||||
fun interface StatesHandler<I : State> {
|
fun interface StatesHandler<I : State, O: State> {
|
||||||
/**
|
/**
|
||||||
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
|
* 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.
|
* [State] it is assumed that chain is not completed.
|
||||||
*/
|
*/
|
||||||
suspend fun StatesMachine.handleState(state: I): State?
|
suspend fun StatesMachine<in O>.handleState(state: I): O?
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +1,24 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.*
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
|
* 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
|
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
|
||||||
* handling until [start] method will be called
|
* handling until [start] method will be called
|
||||||
*/
|
*/
|
||||||
interface StatesMachine : StatesHandler<State> {
|
interface StatesMachine<T : State> : StatesHandler<T, T> {
|
||||||
|
suspend fun launchStateHandling(
|
||||||
|
state: T,
|
||||||
|
handlers: List<CheckableHandlerHolder<in T, T>>
|
||||||
|
): T? {
|
||||||
|
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||||
|
handleState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts handling of [State]s
|
* Starts handling of [State]s
|
||||||
*/
|
*/
|
||||||
@@ -27,15 +27,15 @@ interface StatesMachine : StatesHandler<State> {
|
|||||||
/**
|
/**
|
||||||
* Start chain of [State]s witn [state]
|
* Start chain of [State]s witn [state]
|
||||||
*/
|
*/
|
||||||
suspend fun startChain(state: State)
|
suspend fun startChain(state: T)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Creates [DefaultStatesMachine]
|
* Creates [DefaultStatesMachine]
|
||||||
*/
|
*/
|
||||||
operator fun invoke(
|
operator fun <T: State> invoke(
|
||||||
statesManager: StatesManager,
|
statesManager: StatesManager<T>,
|
||||||
handlers: List<StateHandlerHolder<*>>
|
handlers: List<CheckableHandlerHolder<in T, T>>
|
||||||
) = DefaultStatesMachine(statesManager, handlers)
|
) = DefaultStatesMachine(statesManager, handlers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,14 +44,14 @@ interface StatesMachine : StatesHandler<State> {
|
|||||||
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
|
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
|
||||||
* resolving, and uses [launchStateHandling] for [State] handling
|
* resolving, and uses [launchStateHandling] for [State] handling
|
||||||
*/
|
*/
|
||||||
class DefaultStatesMachine (
|
class DefaultStatesMachine <T: State>(
|
||||||
private val statesManager: StatesManager,
|
private val statesManager: StatesManager<T>,
|
||||||
private val handlers: List<StateHandlerHolder<*>>
|
private val handlers: List<CheckableHandlerHolder<in T, T>>
|
||||||
) : StatesMachine {
|
) : StatesMachine<T> {
|
||||||
/**
|
/**
|
||||||
* Will call [launchStateHandling] for state handling
|
* Will call [launchStateHandling] for state handling
|
||||||
*/
|
*/
|
||||||
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
|
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
|
||||||
@@ -60,7 +60,7 @@ class DefaultStatesMachine (
|
|||||||
* [StatesManager.endChain].
|
* [StatesManager.endChain].
|
||||||
*/
|
*/
|
||||||
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
val statePerformer: suspend (State) -> Unit = { state: State ->
|
val statePerformer: suspend (T) -> Unit = { state: T ->
|
||||||
val newState = launchStateHandling(state, handlers)
|
val newState = launchStateHandling(state, handlers)
|
||||||
if (newState != null) {
|
if (newState != null) {
|
||||||
statesManager.update(state, newState)
|
statesManager.update(state, newState)
|
||||||
@@ -83,7 +83,7 @@ class DefaultStatesMachine (
|
|||||||
/**
|
/**
|
||||||
* Just calls [StatesManager.startChain] of [statesManager]
|
* Just calls [StatesManager.startChain] of [statesManager]
|
||||||
*/
|
*/
|
||||||
override suspend fun startChain(state: State) {
|
override suspend fun startChain(state: T) {
|
||||||
statesManager.startChain(state)
|
statesManager.startChain(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,30 +1,30 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface StatesManager {
|
interface StatesManager<T : State> {
|
||||||
val onChainStateUpdated: Flow<Pair<State, State>>
|
val onChainStateUpdated: Flow<Pair<T, T>>
|
||||||
val onStartChain: Flow<State>
|
val onStartChain: Flow<T>
|
||||||
val onEndChain: Flow<State>
|
val onEndChain: Flow<T>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must set current set using [State.context]
|
* Must set current set using [State.context]
|
||||||
*/
|
*/
|
||||||
suspend fun update(old: State, new: State)
|
suspend fun update(old: T, new: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
||||||
* busy by the other [State]
|
* busy by the other [State]
|
||||||
*/
|
*/
|
||||||
suspend fun startChain(state: State)
|
suspend fun startChain(state: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
||||||
* ignored
|
* ignored
|
||||||
*/
|
*/
|
||||||
suspend fun endChain(state: State)
|
suspend fun endChain(state: T)
|
||||||
|
|
||||||
suspend fun getActiveStates(): List<State>
|
suspend fun getActiveStates(): List<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,37 +1,55 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common.dsl
|
package dev.inmo.micro_utils.fsm.common.dsl
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.*
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
import dev.inmo.micro_utils.fsm.common.managers.*
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
||||||
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
|
import dev.inmo.micro_utils.fsm.common.managers.InMemoryDefaultStatesManagerRepo
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FSMBuilder(
|
class FSMBuilder<T : State>(
|
||||||
var statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo())
|
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
|
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
|
||||||
) {
|
) {
|
||||||
private var states = mutableListOf<StateHandlerHolder<*>>()
|
private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
|
||||||
|
|
||||||
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
|
fun add(handler: CheckableHandlerHolder<T, T>) {
|
||||||
states.add(StateHandlerHolder(kClass, false, handler))
|
states.add(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
|
fun <I : T> add(kClass: KClass<I>, handler: StatesHandler<I, T>) {
|
||||||
states.add(StateHandlerHolder(kClass, true, handler))
|
add(CheckableHandlerHolder(kClass, false, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : T> add(filter: suspend (state: State) -> Boolean, handler: StatesHandler<I, T>) {
|
||||||
|
add(handler.holder(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : T> addStrict(kClass: KClass<I>, handler: StatesHandler<I, T>) {
|
||||||
|
states.add(CheckableHandlerHolder(kClass, true, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : T> onStateOrSubstate(handler: StatesHandler<I, T>) {
|
||||||
|
add(I::class, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : T> strictlyOn(handler: StatesHandler<I, T>) {
|
||||||
|
addStrict(I::class, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : T> doWhen(
|
||||||
|
noinline filter: suspend (state: State) -> Boolean,
|
||||||
|
handler: StatesHandler<I, T>
|
||||||
|
) {
|
||||||
|
add(filter, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build() = StatesMachine(
|
fun build() = StatesMachine(
|
||||||
statesManager,
|
statesManager,
|
||||||
states.toList()
|
states.toList().let { list ->
|
||||||
|
defaultStateHandler ?.let { list + it.holder { true } } ?: list
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
|
fun <T : State> buildFSM(
|
||||||
add(I::class, handler)
|
block: FSMBuilder<T>.() -> Unit
|
||||||
}
|
): StatesMachine<T> = FSMBuilder<T>().apply(block).build()
|
||||||
|
|
||||||
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
|
|
||||||
addStrict(I::class, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildFSM(
|
|
||||||
block: FSMBuilder.() -> Unit
|
|
||||||
): StatesMachine = FSMBuilder().apply(block).build()
|
|
||||||
|
@@ -9,25 +9,25 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
/**
|
/**
|
||||||
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
|
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
|
||||||
*/
|
*/
|
||||||
interface DefaultStatesManagerRepo {
|
interface DefaultStatesManagerRepo<T : State> {
|
||||||
/**
|
/**
|
||||||
* Must save [state] as current state of chain with [State.context] of [state]
|
* Must save [state] as current state of chain with [State.context] of [state]
|
||||||
*/
|
*/
|
||||||
suspend fun set(state: State)
|
suspend fun set(state: T)
|
||||||
/**
|
/**
|
||||||
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
|
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
|
||||||
* NOT be removed
|
* NOT be removed
|
||||||
*/
|
*/
|
||||||
suspend fun removeState(state: State)
|
suspend fun removeState(state: T)
|
||||||
/**
|
/**
|
||||||
* @return Current list of available and saved states
|
* @return Current list of available and saved states
|
||||||
*/
|
*/
|
||||||
suspend fun getStates(): List<State>
|
suspend fun getStates(): List<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Current state by [context]
|
* @return Current state by [context]
|
||||||
*/
|
*/
|
||||||
suspend fun getContextState(context: Any): State?
|
suspend fun getContextState(context: Any): T?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Current state by [context]
|
* @return Current state by [context]
|
||||||
@@ -43,21 +43,21 @@ interface DefaultStatesManagerRepo {
|
|||||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
* 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
|
* new state by using [endChain] with that state
|
||||||
*/
|
*/
|
||||||
class DefaultStatesManager(
|
class DefaultStatesManager<T : State>(
|
||||||
private val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(),
|
private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||||
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
) : StatesManager {
|
) : StatesManager<T> {
|
||||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||||
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
||||||
private val _onStartChain = MutableSharedFlow<State>(0)
|
private val _onStartChain = MutableSharedFlow<T>(0)
|
||||||
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
|
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
|
||||||
private val _onEndChain = MutableSharedFlow<State>(0)
|
private val _onEndChain = MutableSharedFlow<T>(0)
|
||||||
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
|
||||||
|
|
||||||
private val mapMutex = Mutex()
|
private val mapMutex = Mutex()
|
||||||
|
|
||||||
override suspend fun update(old: State, new: State) = mapMutex.withLock {
|
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||||
val stateByOldContext: State? = repo.getContextState(old.context)
|
val stateByOldContext: T? = repo.getContextState(old.context)
|
||||||
when {
|
when {
|
||||||
stateByOldContext != old -> return@withLock
|
stateByOldContext != old -> return@withLock
|
||||||
stateByOldContext == null || old.context == new.context -> {
|
stateByOldContext == null || old.context == new.context -> {
|
||||||
@@ -76,26 +76,26 @@ class DefaultStatesManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startChain(state: State) = mapMutex.withLock {
|
override suspend fun startChain(state: T) = mapMutex.withLock {
|
||||||
if (!repo.contains(state.context)) {
|
if (!repo.contains(state.context)) {
|
||||||
repo.set(state)
|
repo.set(state)
|
||||||
_onStartChain.emit(state)
|
_onStartChain.emit(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun endChainWithoutLock(state: State) {
|
private suspend fun endChainWithoutLock(state: T) {
|
||||||
if (repo.getContextState(state.context) == state) {
|
if (repo.getContextState(state.context) == state) {
|
||||||
repo.removeState(state)
|
repo.removeState(state)
|
||||||
_onEndChain.emit(state)
|
_onEndChain.emit(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun endChain(state: State) {
|
override suspend fun endChain(state: T) {
|
||||||
mapMutex.withLock {
|
mapMutex.withLock {
|
||||||
endChainWithoutLock(state)
|
endChainWithoutLock(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getActiveStates(): List<State> = repo.getStates()
|
override suspend fun getActiveStates(): List<T> = repo.getStates()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,20 +6,20 @@ import dev.inmo.micro_utils.fsm.common.State
|
|||||||
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
|
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
|
||||||
* functionality
|
* functionality
|
||||||
*/
|
*/
|
||||||
class InMemoryDefaultStatesManagerRepo(
|
class InMemoryDefaultStatesManagerRepo<T : State>(
|
||||||
private val map: MutableMap<Any, State> = mutableMapOf()
|
private val map: MutableMap<Any, T> = mutableMapOf()
|
||||||
) : DefaultStatesManagerRepo {
|
) : DefaultStatesManagerRepo<T> {
|
||||||
override suspend fun set(state: State) {
|
override suspend fun set(state: T) {
|
||||||
map[state.context] = state
|
map[state.context] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun removeState(state: State) {
|
override suspend fun removeState(state: T) {
|
||||||
map.remove(state.context)
|
map.remove(state.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getStates(): List<State> = map.values.toList()
|
override suspend fun getStates(): List<T> = map.values.toList()
|
||||||
|
|
||||||
override suspend fun getContextState(context: Any): State? = map[context]
|
override suspend fun getContextState(context: Any): T? = map[context]
|
||||||
|
|
||||||
override suspend fun contains(context: Any): Boolean = map.contains(context)
|
override suspend fun contains(context: Any): Boolean = map.contains(context)
|
||||||
}
|
}
|
||||||
|
@@ -1,68 +1,16 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common.managers
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.State
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesManager
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
|
||||||
|
*
|
||||||
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
* @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
|
* 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
|
* new state by using [endChain] with that state
|
||||||
*/
|
*/
|
||||||
class InMemoryStatesManager(
|
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
|
||||||
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
fun <T: State> InMemoryStatesManager(
|
||||||
) : StatesManager {
|
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
|
||||||
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()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import dev.inmo.micro_utils.fsm.common.*
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
||||||
import dev.inmo.micro_utils.fsm.common.dsl.strictlyOn
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
sealed interface TrafficLightState : State {
|
sealed interface TrafficLightState : State {
|
||||||
@@ -25,9 +26,9 @@ class PlayableMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val statesManager = InMemoryStatesManager()
|
val statesManager = DefaultStatesManager<TrafficLightState>()
|
||||||
|
|
||||||
val machine = buildFSM {
|
val machine = buildFSM<TrafficLightState> {
|
||||||
strictlyOn<GreenCommon> {
|
strictlyOn<GreenCommon> {
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
YellowCommon(it.context).also(::println)
|
YellowCommon(it.context).also(::println)
|
||||||
|
@@ -5,21 +5,21 @@ import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
|||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
|
|
||||||
class KeyValueBasedDefaultStatesManagerRepo(
|
class KeyValueBasedDefaultStatesManagerRepo<T : State>(
|
||||||
private val keyValueRepo: KeyValueRepo<Any, State>
|
private val keyValueRepo: KeyValueRepo<Any, T>
|
||||||
) : DefaultStatesManagerRepo {
|
) : DefaultStatesManagerRepo<T> {
|
||||||
override suspend fun set(state: State) {
|
override suspend fun set(state: T) {
|
||||||
keyValueRepo.set(state.context, state)
|
keyValueRepo.set(state.context, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun removeState(state: State) {
|
override suspend fun removeState(state: T) {
|
||||||
if (keyValueRepo.get(state.context) == state) {
|
if (keyValueRepo.get(state.context) == state) {
|
||||||
keyValueRepo.unset(state.context)
|
keyValueRepo.unset(state.context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getStates(): List<State> = keyValueRepo.getAll { keys(it) }.map { it.second }
|
override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
|
||||||
override suspend fun getContextState(context: Any): State? = keyValueRepo.get(context)
|
override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
|
||||||
|
|
||||||
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
|
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
|
||||||
}
|
}
|
||||||
|
@@ -1,84 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.fsm.repos.common
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.State
|
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesManager
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
|
||||||
import dev.inmo.micro_utils.repos.mappers.withMapper
|
|
||||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
@Deprecated("Replace with DefaultStatesManager and KeyValueBasedDefaultStatesManagerRepo")
|
|
||||||
class KeyValueBasedStatesManager(
|
|
||||||
private val keyValueRepo: KeyValueRepo<Any, State>,
|
|
||||||
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 _onEndChain = MutableSharedFlow<State>(0)
|
|
||||||
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
|
||||||
|
|
||||||
override val onStartChain: Flow<State> = keyValueRepo.onNewValue.map { it.second }
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
override suspend fun update(old: State, new: State) {
|
|
||||||
mutex.withLock {
|
|
||||||
when {
|
|
||||||
keyValueRepo.get(old.context) != old -> return@withLock
|
|
||||||
old.context == new.context || !keyValueRepo.contains(new.context) -> {
|
|
||||||
keyValueRepo.set(old.context, new)
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val stateOnNewOneContext = keyValueRepo.get(new.context)!!
|
|
||||||
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
|
||||||
endChainWithoutLock(stateOnNewOneContext)
|
|
||||||
keyValueRepo.unset(old.context)
|
|
||||||
keyValueRepo.set(new.context, new)
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun startChain(state: State) {
|
|
||||||
if (!keyValueRepo.contains(state.context)) {
|
|
||||||
keyValueRepo.set(state.context, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun endChainWithoutLock(state: State) {
|
|
||||||
if (keyValueRepo.get(state.context) == state) {
|
|
||||||
keyValueRepo.unset(state.context)
|
|
||||||
_onEndChain.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun endChain(state: State) {
|
|
||||||
mutex.withLock { endChainWithoutLock(state) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getActiveStates(): List<State> {
|
|
||||||
return keyValueRepo.getAll { keys(it) }.map { it.second }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified TargetContextType, reified TargetStateType> createStatesManager(
|
|
||||||
targetKeyValueRepo: KeyValueRepo<TargetContextType, TargetStateType>,
|
|
||||||
noinline contextToOutTransformer: suspend Any.() -> TargetContextType,
|
|
||||||
noinline stateToOutTransformer: suspend State.() -> TargetStateType,
|
|
||||||
noinline outToContextTransformer: suspend TargetContextType.() -> Any,
|
|
||||||
noinline outToStateTransformer: suspend TargetStateType.() -> State,
|
|
||||||
) = KeyValueBasedStatesManager(
|
|
||||||
targetKeyValueRepo.withMapper<Any, State, TargetContextType, TargetStateType>(
|
|
||||||
contextToOutTransformer,
|
|
||||||
stateToOutTransformer,
|
|
||||||
outToContextTransformer,
|
|
||||||
outToStateTransformer
|
|
||||||
)
|
|
||||||
)
|
|
@@ -9,12 +9,12 @@ org.gradle.jvmargs=-Xmx2g
|
|||||||
|
|
||||||
kotlin_version=1.5.31
|
kotlin_version=1.5.31
|
||||||
kotlin_coroutines_version=1.5.2
|
kotlin_coroutines_version=1.5.2
|
||||||
kotlin_serialisation_core_version=1.3.0
|
kotlin_serialisation_core_version=1.3.1
|
||||||
kotlin_exposed_version=0.35.3
|
kotlin_exposed_version=0.36.2
|
||||||
|
|
||||||
ktor_version=1.6.4
|
ktor_version=1.6.5
|
||||||
|
|
||||||
klockVersion=2.4.6
|
klockVersion=2.4.8
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ uuidVersion=0.3.1
|
|||||||
|
|
||||||
# ANDROID
|
# ANDROID
|
||||||
|
|
||||||
core_ktx_version=1.6.0
|
core_ktx_version=1.7.0
|
||||||
androidx_recycler_version=1.2.1
|
androidx_recycler_version=1.2.1
|
||||||
appcompat_version=1.3.1
|
appcompat_version=1.3.1
|
||||||
|
|
||||||
@@ -45,5 +45,5 @@ dokka_version=1.5.31
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.7.4
|
version=0.8.4
|
||||||
android_code_version=78
|
android_code_version=84
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.ktor.server
|
package dev.inmo.micro_utils.ktor.server
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
@@ -31,3 +32,27 @@ fun createKtorServer(
|
|||||||
port: Int = Random.nextInt(1024, 65535),
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
block: Application.() -> Unit
|
block: Application.() -> Unit
|
||||||
): ApplicationEngine = createKtorServer(CIO, host, port, block)
|
): ApplicationEngine = createKtorServer(CIO, host, port, block)
|
||||||
|
|
||||||
|
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
|
||||||
|
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
||||||
|
host: String = "localhost",
|
||||||
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
configurators: List<KtorApplicationConfigurator>
|
||||||
|
): TEngine = createKtorServer(
|
||||||
|
engine,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
) {
|
||||||
|
configurators.forEach { it.apply { configure() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create server with [CIO] server engine without starting of it
|
||||||
|
*
|
||||||
|
* @see ApplicationEngine.start
|
||||||
|
*/
|
||||||
|
fun createKtorServer(
|
||||||
|
host: String = "localhost",
|
||||||
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
configurators: List<KtorApplicationConfigurator>
|
||||||
|
): ApplicationEngine = createKtorServer(CIO, host, port, configurators)
|
||||||
|
@@ -24,3 +24,8 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainClassName="MainKt"
|
mainClassName="MainKt"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
|
||||||
|
|
||||||
package dev.inmo.micro_utils.language_codes
|
package dev.inmo.micro_utils.language_codes
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
|
||||||
|
|
||||||
package dev.inmo.micro_utils.mime_types
|
package dev.inmo.micro_utils.mime_types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@@ -26,7 +26,6 @@ kotlin {
|
|||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
languageVersion = JavaLanguageVersion.of(8)
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,13 @@ project.group = "$group"
|
|||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
@@ -28,7 +34,6 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
languageVersion = JavaLanguageVersion.of(8)
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,13 @@ project.group = "$group"
|
|||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
js (IR) {
|
js (IR) {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
@@ -50,7 +56,6 @@ kotlin {
|
|||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
languageVersion = JavaLanguageVersion.of(8)
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -5,3 +5,13 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.common")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.pagination
|
package dev.inmo.micro_utils.pagination
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.intersect
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import kotlin.math.floor
|
|||||||
* If you want to request something, you should use [SimplePagination]. If you need to return some result including
|
* If you want to request something, you should use [SimplePagination]. If you need to return some result including
|
||||||
* pagination - [PaginationResult]
|
* pagination - [PaginationResult]
|
||||||
*/
|
*/
|
||||||
interface Pagination {
|
interface Pagination : ClosedRange<Int> {
|
||||||
/**
|
/**
|
||||||
* Started with 0.
|
* Started with 0.
|
||||||
* Number of page inside of pagination. Offset can be calculated as [page] * [size]
|
* Number of page inside of pagination. Offset can be calculated as [page] * [size]
|
||||||
@@ -20,6 +21,17 @@ interface Pagination {
|
|||||||
* Size of current page. Offset can be calculated as [page] * [size]
|
* Size of current page. Offset can be calculated as [page] * [size]
|
||||||
*/
|
*/
|
||||||
val size: Int
|
val size: Int
|
||||||
|
|
||||||
|
override val start: Int
|
||||||
|
get() = page * size
|
||||||
|
override val endInclusive: Int
|
||||||
|
get() = start + size - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Pagination.intersect(
|
||||||
|
other: Pagination
|
||||||
|
): Pagination? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
|
||||||
|
PaginationByIndexes(it.first, it.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +44,7 @@ inline val Pagination.isFirstPage
|
|||||||
* First number in index of objects. It can be used as offset for databases or other data sources
|
* First number in index of objects. It can be used as offset for databases or other data sources
|
||||||
*/
|
*/
|
||||||
val Pagination.firstIndex: Int
|
val Pagination.firstIndex: Int
|
||||||
get() = page * size
|
get() = start
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||||
@@ -41,7 +53,7 @@ val Pagination.firstIndex: Int
|
|||||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
|
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
|
||||||
*/
|
*/
|
||||||
val Pagination.lastIndexExclusive: Int
|
val Pagination.lastIndexExclusive: Int
|
||||||
get() = firstIndex + size
|
get() = endInclusive + 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||||
@@ -50,7 +62,7 @@ val Pagination.lastIndexExclusive: Int
|
|||||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
||||||
*/
|
*/
|
||||||
val Pagination.lastIndex: Int
|
val Pagination.lastIndex: Int
|
||||||
get() = lastIndexExclusive - 1
|
get() = endInclusive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates pages count for given [datasetSize]
|
* Calculates pages count for given [datasetSize]
|
||||||
|
Reference in New Issue
Block a user