mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
a8e226786d | |||
ce717a4c9f | |||
fd41bf0ae7 | |||
b2b68bf29f |
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.24.4
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* Improve `CommonExposedRepo.selectByIds`
|
||||
* `FSM`:
|
||||
* Fixes and improvements
|
||||
|
||||
## 0.24.3
|
||||
|
||||
* `Ksp`:
|
||||
|
@@ -12,6 +12,7 @@ kotlin {
|
||||
dependencies {
|
||||
api project(":micro_utils.common")
|
||||
api project(":micro_utils.coroutines")
|
||||
api libs.kslog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
import dev.inmo.kslog.common.TagLogger
|
||||
import dev.inmo.kslog.common.e
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
import dev.inmo.micro_utils.coroutines.*
|
||||
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
|
||||
@@ -68,6 +70,7 @@ open class DefaultStatesMachine <T: State>(
|
||||
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||
) : StatesMachine<T> {
|
||||
protected val logger = TagLogger(this::class.simpleName!!)
|
||||
/**
|
||||
* Will call [launchStateHandling] for state handling
|
||||
*/
|
||||
@@ -96,7 +99,13 @@ open class DefaultStatesMachine <T: State>(
|
||||
statesJobsMutex.withLock {
|
||||
statesJobs[actualState] ?.cancel()
|
||||
statesJobs[actualState] = scope.launch {
|
||||
performUpdate(actualState)
|
||||
runCatching {
|
||||
performUpdate(actualState)
|
||||
}.onFailure {
|
||||
logger.e(it) {
|
||||
"Unable to perform update of state from $actualState"
|
||||
}
|
||||
}.getOrThrow()
|
||||
}.also { job ->
|
||||
job.invokeOnCompletion { _ ->
|
||||
scope.launch {
|
||||
|
@@ -9,7 +9,12 @@ interface StatesManager<T : State> {
|
||||
|
||||
|
||||
/**
|
||||
* Must set current set using [State.context]
|
||||
* It is expected, that [new] state will be saved in manager.
|
||||
*
|
||||
* If [new] context will not be equal to [old] one, it must do some check of availability for replacement
|
||||
* of potentially exists state on [new] context. If this state can't be replaced, it will throw [IllegalStateException]
|
||||
*
|
||||
* @throws IllegalStateException - in case when [new] [State] can't be set
|
||||
*/
|
||||
suspend fun update(old: T, new: T)
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
import dev.inmo.kslog.common.e
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
|
||||
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
|
||||
@@ -44,7 +45,13 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
||||
val job = previousState.mapOnPresented {
|
||||
statesJobs.remove(it)
|
||||
} ?.takeIf { it.isActive } ?: scope.launch {
|
||||
performUpdate(actualState)
|
||||
runCatching {
|
||||
performUpdate(actualState)
|
||||
}.onFailure {
|
||||
logger.e(it) {
|
||||
"Unable to perform update of state up to $actualState"
|
||||
}
|
||||
}.getOrThrow()
|
||||
}.also { job ->
|
||||
job.invokeOnCompletion { _ ->
|
||||
scope.launch {
|
||||
|
@@ -1,10 +1,11 @@
|
||||
package dev.inmo.micro_utils.fsm.common.managers
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||
import dev.inmo.micro_utils.fsm.common.State
|
||||
import dev.inmo.micro_utils.fsm.common.StatesManager
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
/**
|
||||
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
|
||||
@@ -19,6 +20,14 @@ interface DefaultStatesManagerRepo<T : State> {
|
||||
* NOT be removed
|
||||
*/
|
||||
suspend fun removeState(state: T)
|
||||
|
||||
/**
|
||||
* Semantically, calls [removeState] and then [set]
|
||||
*/
|
||||
suspend fun removeAndSet(toRemove: T, toSet: T) {
|
||||
removeState(toRemove)
|
||||
set(toSet)
|
||||
}
|
||||
/**
|
||||
* @return Current list of available and saved states
|
||||
*/
|
||||
@@ -58,7 +67,7 @@ open class DefaultStatesManager<T : State>(
|
||||
protected val _onEndChain = MutableSharedFlow<T>(0)
|
||||
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
|
||||
|
||||
protected val mapMutex = Mutex()
|
||||
protected val internalLocker = SmartRWLocker()
|
||||
|
||||
constructor(
|
||||
repo: DefaultStatesManagerRepo<T>,
|
||||
@@ -68,28 +77,30 @@ open class DefaultStatesManager<T : State>(
|
||||
onUpdateContextsConflictResolver = onContextsConflictResolver
|
||||
)
|
||||
|
||||
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||
override suspend fun update(old: T, new: T) = internalLocker.withWriteLock {
|
||||
val stateByOldContext: T? = repo.getContextState(old.context)
|
||||
when {
|
||||
stateByOldContext != old -> return@withLock
|
||||
stateByOldContext == null || old.context == new.context -> {
|
||||
repo.removeState(old)
|
||||
repo.set(new)
|
||||
stateByOldContext != old -> return@withWriteLock
|
||||
old.context == new.context -> {
|
||||
repo.removeAndSet(old, new)
|
||||
_onChainStateUpdated.emit(old to new)
|
||||
}
|
||||
else -> {
|
||||
old.context != new.context -> {
|
||||
val stateOnNewOneContext = repo.getContextState(new.context)
|
||||
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
|
||||
repo.removeState(old)
|
||||
repo.set(new)
|
||||
repo.removeAndSet(old, new)
|
||||
_onChainStateUpdated.emit(old to new)
|
||||
} else {
|
||||
error(
|
||||
"Unable to update state from $old to $new due to false answer from $onUpdateContextsConflictResolver and state on old context $stateOnNewOneContext"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun startChain(state: T) = mapMutex.withLock {
|
||||
override suspend fun startChain(state: T) = internalLocker.withWriteLock {
|
||||
val stateOnContext = repo.getContextState(state.context)
|
||||
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
|
||||
stateOnContext ?.let {
|
||||
@@ -108,11 +119,13 @@ open class DefaultStatesManager<T : State>(
|
||||
}
|
||||
|
||||
override suspend fun endChain(state: T) {
|
||||
mapMutex.withLock {
|
||||
internalLocker.withWriteLock {
|
||||
endChainWithoutLock(state)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getActiveStates(): List<T> = repo.getStates()
|
||||
override suspend fun getActiveStates(): List<T> = internalLocker.withReadAcquire {
|
||||
repo.getStates()
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,25 +1,59 @@
|
||||
package dev.inmo.micro_utils.fsm.repos.common
|
||||
|
||||
import dev.inmo.kslog.common.TagLogger
|
||||
import dev.inmo.kslog.common.i
|
||||
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||
import dev.inmo.micro_utils.fsm.common.State
|
||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||
import dev.inmo.micro_utils.repos.unset
|
||||
|
||||
class KeyValueBasedDefaultStatesManagerRepo<T : State>(
|
||||
private val keyValueRepo: KeyValueRepo<Any, T>
|
||||
) : DefaultStatesManagerRepo<T> {
|
||||
private val locker = SmartRWLocker()
|
||||
private val logger = TagLogger("KeyValueBasedDefaultStatesManagerRepo")
|
||||
override suspend fun set(state: T) {
|
||||
keyValueRepo.set(state.context, state)
|
||||
}
|
||||
|
||||
override suspend fun removeState(state: T) {
|
||||
if (keyValueRepo.get(state.context) == state) {
|
||||
keyValueRepo.unset(state.context)
|
||||
locker.withWriteLock {
|
||||
keyValueRepo.set(state.context, state)
|
||||
logger.i { "Set ${state.context} value to $state" }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
|
||||
override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
|
||||
override suspend fun removeState(state: T) {
|
||||
locker.withWriteLock {
|
||||
if (keyValueRepo.get(state.context) == state) {
|
||||
keyValueRepo.unset(state.context)
|
||||
logger.i { "Unset $state" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
|
||||
override suspend fun removeAndSet(toRemove: T, toSet: T) {
|
||||
locker.withWriteLock {
|
||||
when {
|
||||
toRemove.context == toSet.context -> {
|
||||
keyValueRepo.set(toSet.context, toSet)
|
||||
}
|
||||
else -> {
|
||||
keyValueRepo.set(toSet.context, toSet)
|
||||
keyValueRepo.unset(toRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getStates(): List<T> = locker.withReadAcquire {
|
||||
keyValueRepo.getAll { keys(it) }.map { it.second }
|
||||
}
|
||||
override suspend fun getContextState(context: Any): T? = locker.withReadAcquire {
|
||||
keyValueRepo.get(context)
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Any): Boolean = locker.withReadAcquire {
|
||||
keyValueRepo.contains(context)
|
||||
}
|
||||
}
|
||||
|
@@ -15,5 +15,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.24.3
|
||||
android_code_version=282
|
||||
version=0.24.4
|
||||
android_code_version=283
|
||||
|
@@ -8,8 +8,16 @@ interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
|
||||
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||
get() = {
|
||||
it.foldRight<IdType, Op<Boolean>?>(null) { id, acc ->
|
||||
acc ?.or(selectById(id)) ?: selectById(id)
|
||||
} ?: Op.FALSE
|
||||
if (it.isEmpty()) {
|
||||
Op.FALSE
|
||||
} else {
|
||||
var op = it.firstOrNull() ?.let { selectById(it) } ?: Op.FALSE
|
||||
var i = 1
|
||||
while (i < it.size) {
|
||||
op = op.or(selectById(it[i]))
|
||||
i++
|
||||
}
|
||||
op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user