Merge pull request #119 from InsanusMokrassar/0.8.8

0.8.8
This commit is contained in:
InsanusMokrassar 2021-12-26 22:11:04 +06:00 committed by GitHub
commit c48465b90b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 155 additions and 33 deletions

View File

@ -11,9 +11,9 @@ jobs:
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 1.8
- name: Fix android 31.0.0 dx - name: Fix android 32.0.0 dx
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build - name: Build
run: ./gradlew dokkaHtml run: ./gradlew dokkaHtml
- name: Publish KDocs - name: Publish KDocs

View File

@ -9,9 +9,9 @@ jobs:
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 1.8
- name: Fix android 31.0.0 dx - name: Fix android 32.0.0 dx
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Rewrite version - name: Rewrite version
run: | run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"

View File

@ -1,5 +1,14 @@
# Changelog # Changelog
## 0.8.8
* `Versions`:
* `AppCompat`: `1.3.1` -> `1.4.0`
* Android Compile SDK: `31.0.0` -> `32.0.0`
* `FSM`:
* `DefaultStatesMachine` now is extendable
* New type `UpdatableStatesMachine` with default realization`DefaultUpdatableStatesMachine`
## 0.8.7 ## 0.8.7
* `Ktor`: * `Ktor`:

View File

@ -21,8 +21,10 @@ import kotlinx.serialization.Serializable
*/ */
@Serializable @Serializable
data class Optional<T> internal constructor( data class Optional<T> internal constructor(
internal val data: T?, @Warning("It is unsafe to use this data directly")
internal val dataPresented: Boolean val data: T?,
@Warning("It is unsafe to use this data directly")
val dataPresented: Boolean
) { ) {
companion object { companion object {
/** /**
@ -42,17 +44,31 @@ inline val <T> T.optional
/** /**
* Will call [block] when data presented ([Optional.dataPresented] == true) * Will call [block] when data presented ([Optional.dataPresented] == true)
*/ */
fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply { inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
} }
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
}
/** /**
* Will call [block] when data absent ([Optional.dataPresented] == false) * Will call [block] when data absent ([Optional.dataPresented] == false)
*/ */
fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply { inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
if (!dataPresented) { block() } if (!dataPresented) { block() }
} }
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
}
/** /**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/ */
@ -67,9 +83,10 @@ fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Supp
/** /**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it * 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() inline 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 * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/ */
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block() suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@ -10,6 +10,7 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api project(":micro_utils.common")
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
} }
} }

View File

@ -1,8 +1,11 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/** /**
* 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
@ -42,17 +45,53 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
/** /**
* 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.
*
* This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
*/ */
class DefaultStatesMachine <T: State>( open class DefaultStatesMachine <T: State>(
private val statesManager: StatesManager<T>, protected val statesManager: StatesManager<T>,
private val handlers: List<CheckableHandlerHolder<in T, T>> protected val handlers: List<CheckableHandlerHolder<in T, T>>,
) : StatesMachine<T> { ) : StatesMachine<T> {
/** /**
* Will call [launchStateHandling] for state handling * Will call [launchStateHandling] for state handling
*/ */
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers) override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
/**
* This
*/
protected val statesJobs = mutableMapOf<T, Job>()
protected val statesJobsMutex = Mutex()
protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
statesJobs[actualState] ?.cancel()
statesJobs[actualState] = scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
if (statesJobs[actualState] == job) {
statesJobs.remove(actualState)
}
}
}
}
}
}
}
/** /**
* Launch handling of states. On [statesManager] [StatesManager.onStartChain], * Launch handling of states. On [statesManager] [StatesManager.onStartChain],
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If * [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
@ -60,23 +99,15 @@ class DefaultStatesMachine <T: State>(
* [StatesManager.endChain]. * [StatesManager.endChain].
*/ */
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (T) -> Unit = { state: T ->
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) { statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it) } launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
} }
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it.second) } launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
} }
statesManager.getActiveStates().forEach { statesManager.getActiveStates().forEach {
launch { statePerformer(it) } launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
} }
} }

View File

@ -0,0 +1,58 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
/**
* This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
* must be able to perform update of chain in internal [StatesManager]
*/
interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
/**
* Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
* in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
* [StatesManager.update] function
*/
suspend fun updateChain(currentState: T, newState: T)
}
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
) : DefaultStatesMachine<T>(
statesManager,
handlers
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (previousState.dataOrNull() != actualState) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {
statesJobs.remove(it)
} ?: scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
statesJobs.remove(
jobsStates[job] ?: return@withLock
)
}
}
}
}
jobsStates.remove(job)
statesJobs[actualState] = job
jobsStates[job] = actualState
}
}
override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState)
}
}

View File

@ -7,6 +7,12 @@ import kotlin.reflect.KClass
class FSMBuilder<T : State>( class FSMBuilder<T : State>(
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
val fsmBuilder: (statesManager: StatesManager<T>, states: List<CheckableHandlerHolder<T, T>>) -> StatesMachine<T> = { statesManager, states ->
StatesMachine(
statesManager,
states
)
},
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null } var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
) { ) {
private var states = mutableListOf<CheckableHandlerHolder<T, T>>() private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
@ -42,7 +48,7 @@ class FSMBuilder<T : State>(
add(filter, handler) add(filter, handler)
} }
fun build() = StatesMachine( fun build() = fsmBuilder(
statesManager, statesManager,
states.toList().let { list -> states.toList().let { list ->
defaultStateHandler ?.let { list + it.holder { true } } ?: list defaultStateHandler ?.let { list + it.holder { true } } ?: list

View File

@ -24,12 +24,12 @@ uuidVersion=0.3.1
core_ktx_version=1.7.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.4.0
android_minSdkVersion=19 android_minSdkVersion=19
android_compileSdkVersion=31 android_compileSdkVersion=32
android_buildToolsVersion=31.0.0 android_buildToolsVersion=32.0.0
dexcount_version=3.0.0 dexcount_version=3.0.1
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.2 test_ext_junit_version=1.1.2
espresso_core=3.3.0 espresso_core=3.3.0
@ -45,5 +45,5 @@ dokka_version=1.5.31
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.8.7 version=0.8.8
android_code_version=87 android_code_version=88