mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-30 19:50:31 +00:00 
			
		
		
		
	Compare commits
	
		
			38 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6fedd6f859 | |||
| e52b59665f | |||
| cda9d09689 | |||
| c9237b3f00 | |||
| 18bba66c4a | |||
| 63418c4a8a | |||
| 2e66c6f4e3 | |||
| e9c5df4c13 | |||
| bc7789ad2c | |||
| e3da761249 | |||
| 4082f65afa | |||
| 5d1cab075d | |||
| bcf67f7e59 | |||
| 7d3b1f8e75 | |||
| 119a0588cc | |||
| fab789d9c0 | |||
| ceba81c08f | |||
| a061af0558 | |||
| c7a53846ad | |||
| a683cccf0c | |||
| 50d41e35c1 | |||
| aa0e831cea | |||
| 44e26ccb4f | |||
| 2a783f6e2b | |||
| 6058d6a724 | |||
| 2e9c7eb5fa | |||
| e75465ad10 | |||
| de01ad54e9 | |||
| eeea7ddbe3 | |||
| e0b18bec05 | |||
| 410e89bba9 | |||
| 9ef19dc42b | |||
| 0337d1b82d | |||
| f5bd4c5ccb | |||
| 630f9bc0d4 | |||
| 18b4ffece1 | |||
| f64e1effa3 | |||
| 847fcbb488 | 
							
								
								
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,12 +0,0 @@ | ||||
| name: Regular build | ||||
| on: [push] | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Build | ||||
|         run: ./gradlew build | ||||
							
								
								
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,9 @@ jobs: | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Fix android 31.0.0 dx | ||||
|         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 | ||||
|       - name: Build | ||||
|         run: ./gradlew dokkaHtml | ||||
|       - name: Publish KDocs | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/packages_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/packages_push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,6 +9,9 @@ jobs: | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Fix android 31.0.0 dx | ||||
|         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 | ||||
|       - name: Rewrite version | ||||
|         run: | | ||||
|           branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" | ||||
| @@ -18,6 +21,7 @@ jobs: | ||||
|       - name: Build | ||||
|         run: ./gradlew build | ||||
|       - name: Publish | ||||
|         continue-on-error: true | ||||
|         run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication | ||||
|         env: | ||||
|           GITHUBPACKAGES_USER: ${{ github.actor }} | ||||
|   | ||||
							
								
								
									
										56
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,61 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 0.7.3 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Exposed`: `0.35.2` -> `0.35.3` | ||||
|  | ||||
| ## 0.7.2 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Klock`: `2.4.5` -> `2.4.6` | ||||
|  | ||||
| ## 0.7.1 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Klock`: `2.4.3` -> `2.4.5` | ||||
|     * `Exposed`: `0.35.1` -> `0.35.2` | ||||
| * `Coroutines`: | ||||
|     * `Common`: | ||||
|         * New `Flow` - `AccumulatorFlow` | ||||
| * `FSM`: | ||||
|     * `Common`: | ||||
|         * `InMemoryStatesManager` has been replaced | ||||
|         * `StatesMachine` became an interface | ||||
|         * New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of | ||||
|           data info | ||||
|  | ||||
| ## 0.7.0 | ||||
|  | ||||
| **THIS VERSION HAS MIGRATED FROM KOTLINX DATETIME TO KORLIBS KLOCK. CAREFUL** | ||||
|  | ||||
| * `Versions` | ||||
|     * `kotlinx.datetime` -> `Klock` | ||||
|  | ||||
| ## 0.6.0 DO NOT RECOMMENDED | ||||
|  | ||||
| **THIS VERSION HAS MIGRATED FROM KORLIBS KLOCK TO KOTLINX DATETIME. CAREFUL** | ||||
| **ALL DEPRECATION HAVE BEEN REMOVED** | ||||
|  | ||||
| * `Versions` | ||||
|     * `Klock` -> `kotlinx.datetime` | ||||
|  | ||||
| ## 0.5.31 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Klock`: `2.4.2` -> `2.4.3` | ||||
|     * `Ktor`: `1.6.3` -> `1.6.4` | ||||
|  | ||||
| ## 0.5.30 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Serialization`: `1.2.2` -> `1.3.0` | ||||
|  | ||||
| ## 0.5.29 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Exposed`: `0.34.2` -> `0.35.1` | ||||
|  | ||||
| ## 0.5.28 | ||||
|  | ||||
| * `Versions`: | ||||
|   | ||||
| @@ -1,5 +0,0 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| @Deprecated("Redundant", ReplaceWith("coerceIn(min, max)")) | ||||
| @Suppress("NOTHING_TO_INLINE") | ||||
| inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T = coerceIn(min, max) | ||||
| @@ -6,6 +6,9 @@ import org.w3c.files.File | ||||
| import org.w3c.files.FileReader | ||||
| import kotlin.js.Promise | ||||
|  | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual typealias MPPFile = File | ||||
|  | ||||
| fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure -> | ||||
| @@ -23,10 +26,19 @@ fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure -> | ||||
|  | ||||
| private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await() | ||||
|  | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual val MPPFile.filename: FileName | ||||
|     get() = FileName(name) | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual val MPPFile.filesize: Long | ||||
|     get() = size.toLong() | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| @Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can") | ||||
| actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||
|     get() = ::dirtyReadBytes | ||||
|   | ||||
| @@ -4,12 +4,24 @@ import dev.inmo.micro_utils.coroutines.doInIO | ||||
| import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine | ||||
| import java.io.File | ||||
|  | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual typealias MPPFile = File | ||||
|  | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual val MPPFile.filename: FileName | ||||
|     get() = FileName(name) | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual val MPPFile.filesize: Long | ||||
|     get() = length() | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||
|     get() = { | ||||
|         doInIO { | ||||
|   | ||||
| @@ -0,0 +1,94 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.channels.Channel | ||||
| import kotlinx.coroutines.flow.* | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
|  | ||||
| private sealed interface AccumulatorFlowStep | ||||
| private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep | ||||
| private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep | ||||
| private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep | ||||
|  | ||||
| /** | ||||
|  * This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences: | ||||
|  * | ||||
|  * * All unhandled by [FlowCollector] data will not be removed from [AccumulatorFlow] and will be sent to new | ||||
|  * [FlowCollector]s until anybody will handle it | ||||
|  * * Here there are an [activeData] where data [T] will be stored until somebody will handle it | ||||
|  */ | ||||
| class AccumulatorFlow<T>( | ||||
|     sourceDataFlow: Flow<T>, | ||||
|     scope: CoroutineScope | ||||
| ) : AbstractFlow<T>() { | ||||
|     private val subscope = scope.LinkedSupervisorScope() | ||||
|     private val activeData = ArrayDeque<T>() | ||||
|     private val dataMutex = Mutex() | ||||
|     private val channelsForBroadcast = mutableListOf<Channel<Any>>() | ||||
|     private val channelsMutex = Mutex() | ||||
|     private val steps = subscope.actor<AccumulatorFlowStep> { step -> | ||||
|         when (step) { | ||||
|             is DataRetrievedAccumulatorFlowStep -> { | ||||
|                 if (activeData.first() === step.data) { | ||||
|                     dataMutex.withLock { | ||||
|                         activeData.removeFirst() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             is SubscribeAccumulatorFlowStep -> channelsMutex.withLock { | ||||
|                 channelsForBroadcast.add(step.channel) | ||||
|                 dataMutex.withLock { | ||||
|                     val dataToSend = activeData.toList() | ||||
|                     safelyWithoutExceptions { | ||||
|                         dataToSend.forEach { step.channel.send(it as Any) } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             is UnsubscribeAccumulatorFlowStep -> channelsMutex.withLock { | ||||
|                 channelsForBroadcast.remove(step.channel) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     private val subscriptionJob = sourceDataFlow.subscribeSafelyWithoutExceptions(subscope) { | ||||
|         dataMutex.withLock { | ||||
|             activeData.addLast(it) | ||||
|         } | ||||
|         channelsMutex.withLock { | ||||
|             channelsForBroadcast.forEach { channel -> | ||||
|                 safelyWithResult { | ||||
|                     channel.send(it as Any) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun collectSafely(collector: FlowCollector<T>) { | ||||
|         val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND) | ||||
|         steps.send(SubscribeAccumulatorFlowStep(channel)) | ||||
|         for (data in channel) { | ||||
|             try { | ||||
|                 collector.emit(data as T) | ||||
|                 steps.send(DataRetrievedAccumulatorFlowStep(data)) | ||||
|             } finally { | ||||
|                 channel.cancel() | ||||
|                 steps.send(UnsubscribeAccumulatorFlowStep(channel)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates [AccumulatorFlow] using [this] as base [Flow] | ||||
|  */ | ||||
| fun <T> Flow<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> { | ||||
|     return AccumulatorFlow(this, scope) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates [AccumulatorFlow] using [this] with [receiveAsFlow] to get | ||||
|  */ | ||||
| fun <T> Channel<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> { | ||||
|     return receiveAsFlow().accumulatorFlow(scope) | ||||
| } | ||||
| @@ -1,3 +1,6 @@ | ||||
| package dev.inmo.micro_utils.crypto | ||||
|  | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString()) | ||||
|   | ||||
| @@ -3,6 +3,9 @@ package dev.inmo.micro_utils.crypto | ||||
| import java.math.BigInteger | ||||
| import java.security.MessageDigest | ||||
|  | ||||
| /** | ||||
|  * @suppress | ||||
|  */ | ||||
| actual fun SourceBytes.md5(): MD5 = BigInteger( | ||||
|     1, | ||||
|     MessageDigest.getInstance("MD5").digest(this) | ||||
|   | ||||
| @@ -13,10 +13,10 @@ repositories { | ||||
|  | ||||
| kotlin { | ||||
|     jvm() | ||||
|     js(IR) { | ||||
|         browser() | ||||
|         nodejs() | ||||
|     } | ||||
| //    js(IR) { | ||||
| //        browser() | ||||
| //        nodejs() | ||||
| //    } | ||||
|     android {} | ||||
|  | ||||
|     sourceSets { | ||||
| @@ -29,7 +29,7 @@ kotlin { | ||||
|                         it != project | ||||
|                         && it.hasProperty("kotlin") | ||||
|                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||
|                         && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||
| //                        && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||
|                         && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } | ||||
|                         && it.kotlin.sourceSets.any { it.name.contains("androidMain") } | ||||
|                     ) { | ||||
| @@ -38,22 +38,22 @@ kotlin { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         jsMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
| //        jsMain { | ||||
| //            dependencies { | ||||
| //                implementation kotlin('stdlib') | ||||
|  | ||||
|                 project.parent.subprojects.forEach { | ||||
|                     if ( | ||||
|                         it != project | ||||
|                         && it.hasProperty("kotlin") | ||||
|                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||
|                         && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||
|                     ) { | ||||
|                         api it | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| //                project.parent.subprojects.forEach { | ||||
| //                    if ( | ||||
| //                        it != project | ||||
| //                        && it.hasProperty("kotlin") | ||||
| //                        && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||
| //                        && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||
| //                    ) { | ||||
| //                        api it | ||||
| //                    } | ||||
| //                } | ||||
| //            } | ||||
| //        } | ||||
|         jvmMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
| @@ -116,9 +116,9 @@ tasks.dokkaHtml { | ||||
|             sourceRoots.setFrom(findSourcesWithName("commonMain")) | ||||
|         } | ||||
|  | ||||
|         named("jsMain") { | ||||
|             sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain")) | ||||
|         } | ||||
| //        named("jsMain") { | ||||
| //            sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain")) | ||||
| //        } | ||||
|  | ||||
|         named("jvmMain") { | ||||
|             sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain")) | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| 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 | ||||
| @@ -2,13 +2,25 @@ 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) } | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,12 @@ | ||||
| package dev.inmo.micro_utils.fsm.common | ||||
|  | ||||
| /** | ||||
|  * Default realization of states handler | ||||
|  */ | ||||
| fun interface StatesHandler<I : State> { | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     suspend fun StatesMachine.handleState(state: I): State? | ||||
| } | ||||
|   | ||||
| @@ -13,13 +13,53 @@ private suspend fun <I : State> StatesMachine.launchStateHandling( | ||||
|     } | ||||
| } | ||||
|  | ||||
| class StatesMachine ( | ||||
| /** | ||||
|  * 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 | ||||
|  * handling until [start] method will be called | ||||
|  */ | ||||
| interface StatesMachine : StatesHandler<State> { | ||||
|     /** | ||||
|      * Starts handling of [State]s | ||||
|      */ | ||||
|     fun start(scope: CoroutineScope): Job | ||||
|  | ||||
|     /** | ||||
|      * Start chain of [State]s witn [state] | ||||
|      */ | ||||
|     suspend fun startChain(state: State) | ||||
|  | ||||
|     companion object { | ||||
|         /** | ||||
|          * Creates [DefaultStatesMachine] | ||||
|          */ | ||||
|         operator fun invoke( | ||||
|             statesManager: StatesManager, | ||||
|             handlers: List<StateHandlerHolder<*>> | ||||
|         ) = DefaultStatesMachine(statesManager, handlers) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts | ||||
|  * resolving, and uses [launchStateHandling] for [State] handling | ||||
|  */ | ||||
| class DefaultStatesMachine ( | ||||
|     private val statesManager: StatesManager, | ||||
|     private val handlers: List<StateHandlerHolder<*>> | ||||
| ) : StatesHandler<State> { | ||||
| ) : StatesMachine { | ||||
|     /** | ||||
|      * Will call [launchStateHandling] for state handling | ||||
|      */ | ||||
|     override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers) | ||||
|  | ||||
|     fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { | ||||
|     /** | ||||
|      * Launch handling of states. On [statesManager] [StatesManager.onStartChain], | ||||
|      * [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If | ||||
|      * [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise | ||||
|      * [StatesManager.endChain]. | ||||
|      */ | ||||
|     override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { | ||||
|         val statePerformer: suspend (State) -> Unit = { state: State -> | ||||
|             val newState = launchStateHandling(state, handlers) | ||||
|             if (newState != null) { | ||||
| @@ -40,7 +80,10 @@ class StatesMachine ( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     suspend fun startChain(state: State) { | ||||
|     /** | ||||
|      * Just calls [StatesManager.startChain] of [statesManager] | ||||
|      */ | ||||
|     override suspend fun startChain(state: State) { | ||||
|         statesManager.startChain(state) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| package dev.inmo.micro_utils.fsm.common | ||||
|  | ||||
| import kotlinx.coroutines.flow.* | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
|  | ||||
| interface StatesManager { | ||||
|     val onChainStateUpdated: Flow<Pair<State, State>> | ||||
| @@ -30,63 +28,3 @@ interface StatesManager { | ||||
|     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() | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| package dev.inmo.micro_utils.fsm.common.dsl | ||||
|  | ||||
| import dev.inmo.micro_utils.fsm.common.* | ||||
| import dev.inmo.micro_utils.fsm.common.managers.* | ||||
| import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| class FSMBuilder( | ||||
|     var statesManager: StatesManager = InMemoryStatesManager() | ||||
|     var statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()) | ||||
| ) { | ||||
|     private var states = mutableListOf<StateHandlerHolder<*>>() | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,101 @@ | ||||
| package dev.inmo.micro_utils.fsm.common.managers | ||||
|  | ||||
| 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] | ||||
|  */ | ||||
| interface DefaultStatesManagerRepo { | ||||
|     /** | ||||
|      * Must save [state] as current state of chain with [State.context] of [state] | ||||
|      */ | ||||
|     suspend fun set(state: State) | ||||
|     /** | ||||
|      * Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should | ||||
|      * NOT be removed | ||||
|      */ | ||||
|     suspend fun removeState(state: State) | ||||
|     /** | ||||
|      * @return Current list of available and saved states | ||||
|      */ | ||||
|     suspend fun getStates(): List<State> | ||||
|  | ||||
|     /** | ||||
|      * @return Current state by [context] | ||||
|      */ | ||||
|     suspend fun getContextState(context: Any): State? | ||||
|  | ||||
|     /** | ||||
|      * @return Current state by [context] | ||||
|      */ | ||||
|     suspend fun contains(context: Any): Boolean = getContextState(context) != null | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE | ||||
|  * any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default will be used | ||||
|  * [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter | ||||
|  * @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 DefaultStatesManager( | ||||
|     private val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(), | ||||
|     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 mapMutex = Mutex() | ||||
|  | ||||
|     override suspend fun update(old: State, new: State) = mapMutex.withLock { | ||||
|         val stateByOldContext: State? = repo.getContextState(old.context) | ||||
|         when { | ||||
|             stateByOldContext != old -> return@withLock | ||||
|             stateByOldContext == null || old.context == new.context -> { | ||||
|                 repo.set(new) | ||||
|                 _onChainStateUpdated.emit(old to new) | ||||
|             } | ||||
|             else -> { | ||||
|                 val stateOnNewOneContext = repo.getContextState(new.context) | ||||
|                 if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) { | ||||
|                     stateOnNewOneContext ?.let { endChainWithoutLock(it) } | ||||
|                     repo.removeState(old) | ||||
|                     repo.set(new) | ||||
|                     _onChainStateUpdated.emit(old to new) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun startChain(state: State) = mapMutex.withLock { | ||||
|         if (!repo.contains(state.context)) { | ||||
|             repo.set(state) | ||||
|             _onStartChain.emit(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private suspend fun endChainWithoutLock(state: State) { | ||||
|         if (repo.getContextState(state.context) == state) { | ||||
|             repo.removeState(state) | ||||
|             _onEndChain.emit(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun endChain(state: State) { | ||||
|         mapMutex.withLock { | ||||
|             endChainWithoutLock(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun getActiveStates(): List<State> = repo.getStates() | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,25 @@ | ||||
| package dev.inmo.micro_utils.fsm.common.managers | ||||
|  | ||||
| import dev.inmo.micro_utils.fsm.common.State | ||||
|  | ||||
| /** | ||||
|  * Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive | ||||
|  * functionality | ||||
|  */ | ||||
| class InMemoryDefaultStatesManagerRepo( | ||||
|     private val map: MutableMap<Any, State> = mutableMapOf() | ||||
| ) : DefaultStatesManagerRepo { | ||||
|     override suspend fun set(state: State) { | ||||
|         map[state.context] = state | ||||
|     } | ||||
|  | ||||
|     override suspend fun removeState(state: State) { | ||||
|         map.remove(state.context) | ||||
|     } | ||||
|  | ||||
|     override suspend fun getStates(): List<State> = map.values.toList() | ||||
|  | ||||
|     override suspend fun getContextState(context: Any): State? = map[context] | ||||
|  | ||||
|     override suspend fun contains(context: Any): Boolean = map.contains(context) | ||||
| } | ||||
| @@ -0,0 +1,68 @@ | ||||
| package dev.inmo.micro_utils.fsm.common.managers | ||||
|  | ||||
| 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 | ||||
|  | ||||
| /** | ||||
|  * @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context] | ||||
|  * key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by | ||||
|  * new state by using [endChain] with that state | ||||
|  */ | ||||
| class InMemoryStatesManager( | ||||
|     private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } | ||||
| ) : StatesManager { | ||||
|     private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0) | ||||
|     override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow() | ||||
|     private val _onStartChain = MutableSharedFlow<State>(0) | ||||
|     override val onStartChain: Flow<State> = _onStartChain.asSharedFlow() | ||||
|     private val _onEndChain = MutableSharedFlow<State>(0) | ||||
|     override val onEndChain: Flow<State> = _onEndChain.asSharedFlow() | ||||
|  | ||||
|     private val contextsToStates = mutableMapOf<Any, State>() | ||||
|     private val mapMutex = Mutex() | ||||
|  | ||||
|     override suspend fun update(old: State, new: State) = mapMutex.withLock { | ||||
|         when { | ||||
|             contextsToStates[old.context] != old -> return@withLock | ||||
|             old.context == new.context || !contextsToStates.containsKey(new.context) -> { | ||||
|                 contextsToStates[old.context] = new | ||||
|                 _onChainStateUpdated.emit(old to new) | ||||
|             } | ||||
|             else -> { | ||||
|                 val stateOnNewOneContext = contextsToStates.getValue(new.context) | ||||
|                 if (onContextsConflictResolver(old, new, stateOnNewOneContext)) { | ||||
|                     endChainWithoutLock(stateOnNewOneContext) | ||||
|                     contextsToStates.remove(old.context) | ||||
|                     contextsToStates[new.context] = new | ||||
|                     _onChainStateUpdated.emit(old to new) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun startChain(state: State) = mapMutex.withLock { | ||||
|         if (!contextsToStates.containsKey(state.context)) { | ||||
|             contextsToStates[state.context] = state | ||||
|             _onStartChain.emit(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private suspend fun endChainWithoutLock(state: State) { | ||||
|         if (contextsToStates[state.context] == state) { | ||||
|             contextsToStates.remove(state.context) | ||||
|             _onEndChain.emit(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun endChain(state: State) { | ||||
|         mapMutex.withLock { | ||||
|             endChainWithoutLock(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList() | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,25 @@ | ||||
| package dev.inmo.micro_utils.fsm.repos.common | ||||
|  | ||||
| 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 | ||||
|  | ||||
| class KeyValueBasedDefaultStatesManagerRepo( | ||||
|     private val keyValueRepo: KeyValueRepo<Any, State> | ||||
| ) : DefaultStatesManagerRepo { | ||||
|     override suspend fun set(state: State) { | ||||
|         keyValueRepo.set(state.context, state) | ||||
|     } | ||||
|  | ||||
|     override suspend fun removeState(state: State) { | ||||
|         if (keyValueRepo.get(state.context) == state) { | ||||
|             keyValueRepo.unset(state.context) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun getStates(): List<State> = keyValueRepo.getAll { keys(it) }.map { it.second } | ||||
|     override suspend fun getContextState(context: Any): State? = keyValueRepo.get(context) | ||||
|  | ||||
|     override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) | ||||
| } | ||||
| @@ -9,6 +9,7 @@ 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 } | ||||
|   | ||||
| @@ -9,12 +9,12 @@ org.gradle.jvmargs=-Xmx2g | ||||
|  | ||||
| kotlin_version=1.5.31 | ||||
| kotlin_coroutines_version=1.5.2 | ||||
| kotlin_serialisation_core_version=1.2.2 | ||||
| kotlin_exposed_version=0.34.2 | ||||
| kotlin_serialisation_core_version=1.3.0 | ||||
| kotlin_exposed_version=0.35.3 | ||||
|  | ||||
| ktor_version=1.6.3 | ||||
| ktor_version=1.6.4 | ||||
|  | ||||
| klockVersion=2.4.2 | ||||
| klockVersion=2.4.6 | ||||
|  | ||||
| github_release_plugin_version=2.2.12 | ||||
|  | ||||
| @@ -27,8 +27,8 @@ androidx_recycler_version=1.2.1 | ||||
| appcompat_version=1.3.1 | ||||
|  | ||||
| android_minSdkVersion=19 | ||||
| android_compileSdkVersion=30 | ||||
| android_buildToolsVersion=30.0.3 | ||||
| android_compileSdkVersion=31 | ||||
| android_buildToolsVersion=31.0.0 | ||||
| dexcount_version=3.0.0 | ||||
| junit_version=4.12 | ||||
| test_ext_junit_version=1.1.2 | ||||
| @@ -40,10 +40,10 @@ crypto_js_version=4.1.1 | ||||
|  | ||||
| # Dokka | ||||
|  | ||||
| dokka_version=1.5.0 | ||||
| dokka_version=1.5.31 | ||||
|  | ||||
| # Project data | ||||
|  | ||||
| group=dev.inmo | ||||
| version=0.5.28 | ||||
| android_code_version=69 | ||||
| version=0.7.3 | ||||
| android_code_version=77 | ||||
|   | ||||
| @@ -20,18 +20,6 @@ open class TypedSerializer<T : Any>( | ||||
|         element("type", String.serializer().descriptor) | ||||
|         element("value", ContextualSerializer(kClass).descriptor) | ||||
|     } | ||||
|     @InternalSerializationApi | ||||
|     @Deprecated( | ||||
|         "This descriptor was deprecated due to incorrect serial name. You may use it in case something require it, " + | ||||
|             "but it is strongly recommended to migrate onto new descriptor" | ||||
|     ) | ||||
|     protected val oldDescriptor: SerialDescriptor = buildSerialDescriptor( | ||||
|         "TextSourceSerializer", | ||||
|         SerialKind.CONTEXTUAL | ||||
|     ) { | ||||
|         element("type", String.serializer().descriptor) | ||||
|         element("value", ContextualSerializer(kClass).descriptor) | ||||
|     } | ||||
|  | ||||
|     @ExperimentalSerializationApi | ||||
|     @InternalSerializationApi | ||||
|   | ||||
		Reference in New Issue
	
	Block a user