From 2431a7e35d063972a6b0782f9646379b7ff23806 Mon Sep 17 00:00:00 2001 From: matt-ramotar Date: Sun, 21 Jul 2024 09:48:54 -0400 Subject: [PATCH 1/3] Add immutable and mutable operation pipeline Signed-off-by: matt-ramotar --- .../storex/paging/custom/FetchingStrategy.kt | 6 +-- .../storex/paging/runtime/Action.kt | 22 ++++++--- .../storex/paging/runtime/Dispatcher.kt | 4 +- .../paging/runtime/ExperimentalPagingApi.kt | 6 +++ .../storex/paging/runtime/Operation.kt | 8 ++-- .../paging/runtime/OperationPipeline.kt | 3 ++ .../storex/paging/runtime/Pager.kt | 4 +- .../storex/paging/runtime/PagingScope.kt | 45 ++++++++++--------- .../storex/paging/runtime/PagingState.kt | 10 +++-- .../pager/api/MutableOperationPipeline.kt | 18 ++++++++ .../internal/pager/api/OperationApplier.kt | 2 +- .../internal/pager/api/PagingStateManager.kt | 4 +- .../pager/impl/ConcurrentOperationApplier.kt | 20 +++++---- .../pager/impl/DefaultFetchingStrategy.kt | 18 ++++---- .../pager/impl/DefaultLoadingHandler.kt | 4 +- .../internal/pager/impl/RealLoadingHandler.kt | 2 +- .../impl/RealMutableOperationPipeline.kt | 9 ++++ .../runtime/internal/pager/impl/RealPager.kt | 32 +++++++------ .../pager/impl/RealPagingStateManager.kt | 13 +++--- .../pagingScope/impl/PagingScopeBuilder.kt | 24 +++++++--- .../pagingScope/impl/RealDispatcher.kt | 8 ++-- .../pagingScope/impl/RealPagingScope.kt | 10 ++--- .../runtime/DefaultLoadingHandlerTest.kt | 4 +- 23 files changed, 174 insertions(+), 102 deletions(-) create mode 100644 paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/ExperimentalPagingApi.kt create mode 100644 paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationPipeline.kt create mode 100644 paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/MutableOperationPipeline.kt create mode 100644 paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealMutableOperationPipeline.kt diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/custom/FetchingStrategy.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/custom/FetchingStrategy.kt index 3b0a3ff..b69352a 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/custom/FetchingStrategy.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/custom/FetchingStrategy.kt @@ -17,7 +17,7 @@ import org.mobilenativefoundation.storex.paging.runtime.PagingState * @param ItemId The type of the item identifier. * @param PageRequestKey The type of the paging key. */ -interface FetchingStrategy { +interface FetchingStrategy { /** * Determines whether to fetch more data in the forward direction based on the current state of the pager. @@ -29,7 +29,7 @@ interface FetchingStrategy { */ fun shouldFetchForward( params: PagingSource.LoadParams, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState, ): Boolean @@ -43,7 +43,7 @@ interface FetchingStrategy { */ fun shouldFetchBackward( params: PagingSource.LoadParams, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState, ): Boolean } \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Action.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Action.kt index e7c0529..487b4f5 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Action.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Action.kt @@ -1,25 +1,37 @@ package org.mobilenativefoundation.storex.paging.runtime -sealed class Action { +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline + +sealed class Action { data class ProcessQueue internal constructor( val direction: LoadDirection - ) : Action() + ) : Action() data class SkipQueue internal constructor( val key: K, val direction: LoadDirection, val strategy: LoadStrategy, - ) : Action() + ) : Action() data class Enqueue internal constructor( val key: K, val direction: LoadDirection, val strategy: LoadStrategy, val jump: Boolean - ) : Action() + ) : Action() + + data object Invalidate : Action() + + data class AddOperation( + val operation: Operation + ) : Action() + + data class RemoveOperation( + val operation: Operation + ) : Action() - data object Invalidate : Action() + data object ClearOperations : Action() companion object { fun skipQueue( diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Dispatcher.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Dispatcher.kt index d2df9f1..ca882f1 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Dispatcher.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Dispatcher.kt @@ -1,5 +1,5 @@ package org.mobilenativefoundation.storex.paging.runtime -interface Dispatcher { - suspend fun dispatch(action: Action) +interface Dispatcher { + suspend fun dispatch(action: Action) } \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/ExperimentalPagingApi.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/ExperimentalPagingApi.kt new file mode 100644 index 0000000..6fae646 --- /dev/null +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/ExperimentalPagingApi.kt @@ -0,0 +1,6 @@ +package org.mobilenativefoundation.storex.paging.runtime + +@RequiresOptIn(message = "This API is experimental. It may be changed in the future without notice.") +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +annotation class ExperimentalPagingApi \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Operation.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Operation.kt index 8d38912..3a98df9 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Operation.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Operation.kt @@ -7,7 +7,7 @@ package org.mobilenativefoundation.storex.paging.runtime * @param PageRequestKey The type of the paging key. * @param ItemValue The type of the item value. */ -interface Operation { +abstract class Operation { /** * Determines whether this operation should be applied based on the current state. * @@ -16,7 +16,7 @@ interface Operation { * @param fetchingState The current fetching state. * @return True if the operation should be applied, false otherwise. */ - fun shouldApply(key: PageRequestKey?, pagingState: PagingState, fetchingState: FetchingState): Boolean + internal abstract fun shouldApply(key: PageRequestKey?, pagingState: PagingState, fetchingState: FetchingState): Boolean /** * Applies the operation to the given snapshot. @@ -27,10 +27,10 @@ interface Operation { * @param fetchingState The current fetching state. * @return The transformed snapshot after applying the operation. */ - fun apply( + internal abstract fun apply( snapshot: ItemSnapshotList, key: PageRequestKey?, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState ): ItemSnapshotList } \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationPipeline.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationPipeline.kt new file mode 100644 index 0000000..f2ea670 --- /dev/null +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationPipeline.kt @@ -0,0 +1,3 @@ +package org.mobilenativefoundation.storex.paging.runtime + +interface OperationPipeline: List> \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Pager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Pager.kt index 3562ae3..c165992 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Pager.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/Pager.kt @@ -2,6 +2,6 @@ package org.mobilenativefoundation.storex.paging.runtime import kotlinx.coroutines.flow.StateFlow -interface Pager { - val state: StateFlow> +interface Pager { + val state: StateFlow> } diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt index 1ab43ab..8bfd083 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt @@ -10,34 +10,35 @@ import org.mobilenativefoundation.storex.paging.persistence.api.ItemPersistence import org.mobilenativefoundation.storex.paging.persistence.api.PagePersistence import org.mobilenativefoundation.storex.paging.runtime.internal.pagingScope.impl.PagingScopeBuilder -interface PagingScope { - fun getPager(): Pager +interface PagingScope { + fun getPager(): Pager fun getOperationManager(): OperationManager - fun getDispatcher(): Dispatcher + fun getDispatcher(): Dispatcher fun getUpdatingItemProvider(): UpdatingItemProvider - interface Builder { - fun setInitialState(state: PagingState): Builder - fun setInitialLoadParams(params: PagingSource.LoadParams): Builder - fun setPagingSource(source: PagingSource): Builder - fun setCoroutineDispatcher(dispatcher: CoroutineDispatcher): Builder - fun addLaunchEffect(effect: LaunchEffect): Builder - fun addSideEffect(effect: SideEffect): Builder - fun addMiddleware(mw: Middleware): Builder - fun setInitialFetchingState(state: FetchingState): Builder - fun setFetchingStrategy(strategy: FetchingStrategy): Builder - fun setErrorHandlingStrategy(strategy: ErrorHandlingStrategy): Builder - fun setItemMemoryCache(cache: MutableMap): Builder - fun setPageMemoryCache(cache: MutableMap>): Builder - fun setItemPersistence(persistence: ItemPersistence): Builder - fun setPagePersistence(persistence: PagePersistence): Builder - fun setItemUpdater(updater: Updater): Builder - fun setPlaceholderFactory(placeholderFactory: PlaceholderFactory): Builder - fun build(): PagingScope + interface Builder { + fun setInitialState(state: PagingState): Builder + fun setInitialLoadParams(params: PagingSource.LoadParams): Builder + fun setPagingSource(source: PagingSource): Builder + fun setCoroutineDispatcher(dispatcher: CoroutineDispatcher): Builder + fun addLaunchEffect(effect: LaunchEffect): Builder + fun addSideEffect(effect: SideEffect): Builder + fun addMiddleware(mw: Middleware): Builder + fun setInitialFetchingState(state: FetchingState): Builder + fun setFetchingStrategy(strategy: FetchingStrategy): Builder + fun setErrorHandlingStrategy(strategy: ErrorHandlingStrategy): Builder + fun setItemMemoryCache(cache: MutableMap): Builder + fun setPageMemoryCache(cache: MutableMap>): Builder + fun setItemPersistence(persistence: ItemPersistence): Builder + fun setPagePersistence(persistence: PagePersistence): Builder + fun setItemUpdater(updater: Updater): Builder + fun setPlaceholderFactory(placeholderFactory: PlaceholderFactory): Builder + fun setInitialOperations(operations: List>): Builder + fun build(): PagingScope } companion object { - fun builder( + fun builder( pagingConfig: PagingConfig ): Builder = PagingScopeBuilder(pagingConfig) } diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingState.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingState.kt index 850472d..f1655e8 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingState.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingState.kt @@ -1,14 +1,18 @@ package org.mobilenativefoundation.storex.paging.runtime +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline -data class PagingState( + +data class PagingState( val ids: List, val loadStates: CombinedLoadStates, + val mutableOperationPipeline: MutableOperationPipeline ) { companion object { - fun initial() = PagingState( + fun initial() = PagingState( emptyList(), - CombinedLoadStates.initial() + CombinedLoadStates.initial(), + MutableOperationPipeline.empty() ) } } diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/MutableOperationPipeline.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/MutableOperationPipeline.kt new file mode 100644 index 0000000..ea74da9 --- /dev/null +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/MutableOperationPipeline.kt @@ -0,0 +1,18 @@ +package org.mobilenativefoundation.storex.paging.runtime.internal.pager.api + +import org.mobilenativefoundation.storex.paging.runtime.Operation +import org.mobilenativefoundation.storex.paging.runtime.OperationPipeline +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealMutableOperationPipeline + +interface MutableOperationPipeline : + OperationPipeline, MutableList> { + + companion object { + fun empty(): MutableOperationPipeline { + return RealMutableOperationPipeline(mutableListOf()) + } + } +} + + + diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/OperationApplier.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/OperationApplier.kt index 3da8752..afc9182 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/OperationApplier.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/OperationApplier.kt @@ -11,7 +11,7 @@ internal interface OperationApplier, key: PageRequestKey?, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState ): ItemSnapshotList } \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/PagingStateManager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/PagingStateManager.kt index f7599f4..a3e069b 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/PagingStateManager.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/PagingStateManager.kt @@ -6,8 +6,8 @@ import org.mobilenativefoundation.storex.paging.runtime.PagingState /** * Manages the paging state and provides methods to update it. */ -internal interface PagingStateManager { - val pagingState: StateFlow> +internal interface PagingStateManager { + val pagingState: StateFlow> fun updateWithAppendData(ids: List, endOfPaginationReached: Boolean) fun updateWithPrependData(ids: List, endOfPaginationReached: Boolean) fun updateWithAppendError(error: Throwable) diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt index 576be89..41d47ca 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt @@ -7,6 +7,7 @@ import org.mobilenativefoundation.storex.paging.runtime.ItemSnapshotList import org.mobilenativefoundation.storex.paging.runtime.Operation import org.mobilenativefoundation.storex.paging.runtime.OperationManager import org.mobilenativefoundation.storex.paging.runtime.PagingState +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.OperationApplier /** @@ -19,8 +20,9 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.Opera * @param PageRequestKey The type of the paging key. * @param ItemValue The type of the item value. */ -class ConcurrentOperationApplier( - private val operationManager: OperationManager +class ConcurrentOperationApplier( + private val operationManager: OperationManager, + private val mutableOperationPipeline: MutableOperationPipeline ) : OperationApplier { // Mutex for ensuring thread-safe access to shared resources @@ -44,7 +46,7 @@ class ConcurrentOperationApplier, key: PageRequestKey?, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState ): ItemSnapshotList = mutex.withLock { operationManager.get().fold(snapshot) { acc, operation -> @@ -68,12 +70,12 @@ class ConcurrentOperationApplier( - val operation: Operation, - val snapshot: ItemSnapshotList, - val key: K?, - val pagingState: PagingState, - val fetchingState: FetchingState + private data class CacheKey( + val operation: Operation, + val snapshot: ItemSnapshotList, + val key: PageRequestKey?, + val pagingState: PagingState, + val fetchingState: FetchingState ) } diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultFetchingStrategy.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultFetchingStrategy.kt index 4fd5241..a70e1ed 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultFetchingStrategy.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultFetchingStrategy.kt @@ -21,12 +21,12 @@ import kotlin.math.abs * @param ItemId The type of the item identifier. * @param PageRequestKey The type of the key used for loading pages. */ -class DefaultFetchingStrategy( +class DefaultFetchingStrategy( private val pagingConfig: PagingConfig, private val logger: PagingLogger, private val listSortAnalyzer: ListSortAnalyzer, private val itemIdComparator: Comparator -) : FetchingStrategy { +) : FetchingStrategy { // Thread-safe caching of the last analyzed list and its sort order private data class CachedOrder(val ids: List, val order: Order) @@ -38,7 +38,7 @@ class DefaultFetchingStrategy( */ override fun shouldFetchForward( params: PagingSource.LoadParams, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState ): Boolean { return shouldFetch(pagingState, fetchingState, FetchDirection.FORWARD) @@ -49,7 +49,7 @@ class DefaultFetchingStrategy( */ override fun shouldFetchBackward( params: PagingSource.LoadParams, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState ): Boolean { return shouldFetch(pagingState, fetchingState, FetchDirection.BACKWARD) @@ -60,7 +60,7 @@ class DefaultFetchingStrategy( * This method is optimized for performance and thread safety. */ private fun shouldFetch( - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState, fetchDirection: FetchDirection ): Boolean { @@ -129,7 +129,7 @@ class DefaultFetchingStrategy( * Inlined for performance in high-frequency calls. */ private inline fun checkFetchCondition( - pagingState: PagingState, + pagingState: PagingState, itemLoadedSoFar: ItemId?, itemAccessedSoFar: ItemId?, sortOrder: Order @@ -150,11 +150,11 @@ class DefaultFetchingStrategy( * Calculates the distance between two items. * Uses Comparator's distance method if available, otherwise falls back to index-based calculation. */ - private fun getDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState): Int { + private fun getDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState): Int { return itemIdComparator.distance(itemLoaded, itemAccessed) ?: calculateIndexDistance(itemLoaded, itemAccessed, pagingState) } - private fun calculateIndexDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState): Int { + private fun calculateIndexDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState): Int { var loadedIndex = -1 var accessedIndex = -1 @@ -181,7 +181,7 @@ class DefaultFetchingStrategy( * Inlined for performance in high-frequency calls. */ private inline fun isUnderPrefetchLimit( - pagingState: PagingState, + pagingState: PagingState, itemLoadedSoFar: ItemId, sortOrder: Order ): Boolean { diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt index 8d4e1fc..de574b9 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt @@ -22,9 +22,9 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.Retry import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.NormalizedStore import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.PageLoadState -internal class DefaultLoadingHandler( +internal class DefaultLoadingHandler( private val store: NormalizedStore, - private val pagingStateManager: PagingStateManager, + private val pagingStateManager: PagingStateManager, private val queueManager: QueueManager, private val fetchingStateHolder: FetchingStateHolder, private val errorHandlingStrategy: ErrorHandlingStrategy, diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadingHandler.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadingHandler.kt index 48bc299..ced560a 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadingHandler.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadingHandler.kt @@ -21,7 +21,7 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.PageL */ internal class RealLoadingHandler( private val store: NormalizedStore, - private val pagingStateManager: PagingStateManager, + private val pagingStateManager: PagingStateManager, private val queueManager: QueueManager, private val fetchingStateHolder: FetchingStateHolder, private val errorHandlingStrategy: ErrorHandlingStrategy, diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealMutableOperationPipeline.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealMutableOperationPipeline.kt new file mode 100644 index 0000000..7597002 --- /dev/null +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealMutableOperationPipeline.kt @@ -0,0 +1,9 @@ +package org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl + +import org.mobilenativefoundation.storex.paging.runtime.Operation +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline + + +class RealMutableOperationPipeline( + private val operations: MutableList> +) : MutableOperationPipeline, MutableList> by operations \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt index 4d29e0f..4355f64 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt @@ -33,20 +33,20 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.Norma * Implementation of [Pager] that coordinates paging operations. */ -internal class RealPager( +internal class RealPager( recompositionMode: RecompositionMode, private val fetchingStateHolder: FetchingStateHolder, private val launchEffects: List, - private val fetchingStrategy: FetchingStrategy, + private val fetchingStrategy: FetchingStrategy, private val initialLoadParams: PagingSource.LoadParams, private val store: NormalizedStore, - private val actions: Flow>, + private val actions: Flow>, private val logger: PagingLogger, - private val pagingStateManager: PagingStateManager, + private val pagingStateManager: PagingStateManager, private val queueManager: QueueManager, private val loadingHandler: LoadingHandler, private val coroutineScope: CoroutineScope, -) : Pager { +) : Pager { init { @@ -56,7 +56,7 @@ internal class RealPager( handleEagerLoading() } - override val state: StateFlow> = + override val state: StateFlow> = coroutineScope.launchMolecule(recompositionMode.toCash()) { pagingState(actions) } @@ -66,7 +66,7 @@ internal class RealPager( * @param actions Flow of [Action] objects. */ @Composable - private fun pagingState(actions: Flow>): PagingState { + private fun pagingState(actions: Flow>): PagingState { val fetchingState by fetchingStateHolder.state.collectAsState() val pagingState by pagingStateManager.pagingState.collectAsState() @@ -133,7 +133,7 @@ internal class RealPager( * * @param actions Flow of paging actions. */ - private suspend fun handleActions(actions: Flow>) { + private suspend fun handleActions(actions: Flow>) { actions.distinctUntilChanged().collect { action -> logger.debug("Handling action: $action") @@ -142,6 +142,10 @@ internal class RealPager( is Action.SkipQueue -> handleSkipQueueAction(action) is Action.Enqueue -> handleEnqueueAction(action) Action.Invalidate -> handleInvalidateAction() + + is Action.AddOperation -> TODO() + Action.ClearOperations -> TODO() + is Action.RemoveOperation -> TODO() } } } @@ -266,16 +270,16 @@ internal class RealPager( */ private fun shouldFetchForward( queueElement: LoadParamsQueue.Element, - pagingState: PagingState, + pagingState: PagingState, fetchingState: FetchingState ): Boolean { val shouldFetch = queueElement.mechanism == LoadParamsQueue.Element.Mechanism.EnqueueRequest || - fetchingStrategy.shouldFetchForward( - queueElement.params, - pagingState, - fetchingState - ) + fetchingStrategy.shouldFetchForward( + queueElement.params, + pagingState, + fetchingState + ) logger.debug("Should fetch forward: $shouldFetch for key: ${queueElement.params.key}") diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPagingStateManager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPagingStateManager.kt index 2f52d18..b6b9d13 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPagingStateManager.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPagingStateManager.kt @@ -14,12 +14,12 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.Pagin /** * Implementation of PagingStateManager that manages the paging state. */ -class RealPagingStateManager( - initialState: PagingState, +class RealPagingStateManager( + initialState: PagingState, private val logger: PagingLogger, -) : PagingStateManager { +) : PagingStateManager { private val _pagingState = MutableStateFlow(initialState) - override val pagingState: StateFlow> = _pagingState.asStateFlow() + override val pagingState: StateFlow> = _pagingState.asStateFlow() override fun updateWithAppendData(ids: List, endOfPaginationReached: Boolean) { updateState("append data") { currentState -> @@ -59,7 +59,10 @@ class RealPagingStateManager( updateLoadState("prepend loading") { it.copy(prepend = LoadState.Loading) } } - private fun updateState(operation: String, update: (PagingState) -> PagingState) { + private fun updateState( + operation: String, + update: (PagingState) -> PagingState + ) { logger.debug("Updating paging state with $operation") logger.debug("Current paging state: ${pagingState.value}") diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt index d2e6654..3d4e49e 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt @@ -20,6 +20,7 @@ import org.mobilenativefoundation.storex.paging.runtime.FetchingState import org.mobilenativefoundation.storex.paging.runtime.IdExtractor import org.mobilenativefoundation.storex.paging.runtime.LoadDirection import org.mobilenativefoundation.storex.paging.runtime.LoadStrategy +import org.mobilenativefoundation.storex.paging.runtime.Operation import org.mobilenativefoundation.storex.paging.runtime.PagingConfig import org.mobilenativefoundation.storex.paging.runtime.PagingScope import org.mobilenativefoundation.storex.paging.runtime.PagingSource @@ -31,6 +32,7 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.Fetch import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LinkedHashMapManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.ListSortAnalyzer import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LoadingHandler +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.OperationApplier import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.PagingStateManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.QueueManager @@ -41,6 +43,7 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.Defa import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.DefaultListSortAnalyzer import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealLinkedHashMapManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealLoadingHandler +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealMutableOperationPipeline import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealPager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealPagingStateManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealQueueManager @@ -59,9 +62,9 @@ class PagingScopeBuilder( ) : PagingScope.Builder { private val logger = RealPagingLogger(pagingConfig.logging) - private val actionsFlow = MutableSharedFlow>(replay = 20) + private val actionsFlow = MutableSharedFlow>(replay = 20) - private var initialState: PagingState = PagingState.initial() + private var initialState: PagingState = PagingState.initial() private var initialLoadParams = PagingSource.LoadParams( pagingConfig.initialKey, LoadStrategy.SkipCache, @@ -78,9 +81,10 @@ class PagingScopeBuilder( private val middleware = mutableListOf>() private var initialFetchingState = FetchingState() private var listSortAnalyzer: ListSortAnalyzer = DefaultListSortAnalyzer(itemIdComparator) - private var fetchingStrategy: FetchingStrategy = + private var fetchingStrategy: FetchingStrategy = DefaultFetchingStrategy(pagingConfig, logger, listSortAnalyzer, itemIdComparator) private var errorHandlingStrategy: ErrorHandlingStrategy = ErrorHandlingStrategy.RetryLast() + private var initialOperations: MutableList> = mutableListOf() private lateinit var itemMemoryCache: MutableMap private lateinit var pageMemoryCache: MutableMap> @@ -89,7 +93,7 @@ class PagingScopeBuilder( private lateinit var itemUpdater: Updater private lateinit var placeholderFactory: PlaceholderFactory - override fun setInitialState(state: PagingState) = apply { initialState = state } + override fun setInitialState(state: PagingState) = apply { initialState = state } override fun setInitialLoadParams(params: PagingSource.LoadParams) = apply { initialLoadParams = params } @@ -103,7 +107,7 @@ class PagingScopeBuilder( override fun setInitialFetchingState(state: FetchingState) = apply { initialFetchingState = state } - override fun setFetchingStrategy(strategy: FetchingStrategy) = + override fun setFetchingStrategy(strategy: FetchingStrategy) = apply { fetchingStrategy = strategy } override fun setErrorHandlingStrategy(strategy: ErrorHandlingStrategy) = @@ -126,6 +130,11 @@ class PagingScopeBuilder( override fun setItemUpdater(updater: Updater) = apply { itemUpdater = updater } + override fun setInitialOperations(operations: List>): PagingScope.Builder = + apply { + this.initialOperations = operations.toMutableList() + } + override fun build(): PagingScope { val operationManager = ConcurrentOperationManager() val fetchingStateHolder = ConcurrentFetchingStateHolder(initialFetchingState, itemIdComparator, pageRequestKeyComparator) @@ -152,7 +161,8 @@ class PagingScopeBuilder( val pagingStateManager = RealPagingStateManager(initialState, logger) val queueManager = RealQueueManager(logger, pageRequestKeyComparator) - val operationApplier = ConcurrentOperationApplier(operationManager) + val mutableOperationPipeline = RealMutableOperationPipeline(initialOperations) + val operationApplier = ConcurrentOperationApplier(operationManager, mutableOperationPipeline) val loadingHandler = createLoadingHandler( store = store, pagingStateManager = pagingStateManager, @@ -207,7 +217,7 @@ class PagingScopeBuilder( private fun createLoadingHandler( store: NormalizedStore, - pagingStateManager: PagingStateManager, + pagingStateManager: PagingStateManager, queueManager: QueueManager, fetchingStateHolder: FetchingStateHolder, operationApplier: OperationApplier diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealDispatcher.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealDispatcher.kt index 83b4c01..7b8d8f0 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealDispatcher.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealDispatcher.kt @@ -4,10 +4,10 @@ import kotlinx.coroutines.flow.MutableSharedFlow import org.mobilenativefoundation.storex.paging.runtime.Action import org.mobilenativefoundation.storex.paging.runtime.Dispatcher -class RealDispatcher( - private val actionsFlow: MutableSharedFlow> -) : Dispatcher { - override suspend fun dispatch(action: Action) { +class RealDispatcher( + private val actionsFlow: MutableSharedFlow> +) : Dispatcher { + override suspend fun dispatch(action: Action) { actionsFlow.emit(action) } } \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt index 427af41..6525b0e 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt @@ -6,13 +6,13 @@ import org.mobilenativefoundation.storex.paging.runtime.Pager import org.mobilenativefoundation.storex.paging.runtime.PagingScope import org.mobilenativefoundation.storex.paging.runtime.UpdatingItemProvider -class RealPagingScope( - private val pager: Pager, +class RealPagingScope( + private val pager: Pager, private val operationManager: OperationManager, - private val dispatcher: Dispatcher, + private val dispatcher: Dispatcher, private val updatingItemProvider: UpdatingItemProvider ) : PagingScope { - override fun getPager(): Pager { + override fun getPager(): Pager { return pager } @@ -20,7 +20,7 @@ class RealPagingScope( return operationManager } - override fun getDispatcher(): Dispatcher { + override fun getDispatcher(): Dispatcher { return dispatcher } diff --git a/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt index da9d635..c945a65 100644 --- a/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt +++ b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt @@ -40,10 +40,10 @@ class DefaultLoadingHandlerTest { private val appendQueue = mock>() private val prependQueue = mock>() - private val pagingState = MutableStateFlow(PagingState.initial()) + private val pagingState = MutableStateFlow(PagingState.initial()) private val fetchingState = MutableStateFlow(FetchingState()) - private val pagingStateManager = mock> { + private val pagingStateManager = mock> { every { pagingState } returns this@DefaultLoadingHandlerTest.pagingState } private val store = mock>() From b37a2ce21a6330e2616d072f555d43f5aef3d45a Mon Sep 17 00:00:00 2001 From: matt-ramotar Date: Sun, 21 Jul 2024 10:10:24 -0400 Subject: [PATCH 2/3] Refactor from ConcurrentOperationManager to OperationPipeline Signed-off-by: matt-ramotar --- .../storex/paging/runtime/OperationManager.kt | 10 -- .../storex/paging/runtime/PagingScope.kt | 1 - .../pager/impl/ConcurrentOperationApplier.kt | 8 +- .../pager/impl/ConcurrentOperationManager.kt | 112 ------------------ .../runtime/internal/pager/impl/RealPager.kt | 8 +- .../pagingScope/impl/PagingScopeBuilder.kt | 9 +- .../pagingScope/impl/RealPagingScope.kt | 6 - 7 files changed, 11 insertions(+), 143 deletions(-) delete mode 100644 paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationManager.kt delete mode 100644 paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationManager.kt diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationManager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationManager.kt deleted file mode 100644 index df73f4f..0000000 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/OperationManager.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.mobilenativefoundation.storex.paging.runtime - -interface OperationManager { - suspend fun add(operation: Operation) - suspend fun remove(operation: Operation) - suspend fun removeAll(predicate: (Operation) -> Boolean) - suspend fun clear() - fun get(): List> - fun get(predicate: (Operation) -> Boolean): List> -} \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt index 8bfd083..fe7aae3 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/PagingScope.kt @@ -12,7 +12,6 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pagingScope.imp interface PagingScope { fun getPager(): Pager - fun getOperationManager(): OperationManager fun getDispatcher(): Dispatcher fun getUpdatingItemProvider(): UpdatingItemProvider diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt index 41d47ca..5cf7065 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationApplier.kt @@ -5,9 +5,8 @@ import kotlinx.coroutines.sync.withLock import org.mobilenativefoundation.storex.paging.runtime.FetchingState import org.mobilenativefoundation.storex.paging.runtime.ItemSnapshotList import org.mobilenativefoundation.storex.paging.runtime.Operation -import org.mobilenativefoundation.storex.paging.runtime.OperationManager +import org.mobilenativefoundation.storex.paging.runtime.OperationPipeline import org.mobilenativefoundation.storex.paging.runtime.PagingState -import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.OperationApplier /** @@ -21,8 +20,7 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.Opera * @param ItemValue The type of the item value. */ class ConcurrentOperationApplier( - private val operationManager: OperationManager, - private val mutableOperationPipeline: MutableOperationPipeline + private val operationPipeline: OperationPipeline ) : OperationApplier { // Mutex for ensuring thread-safe access to shared resources @@ -49,7 +47,7 @@ class ConcurrentOperationApplier, fetchingState: FetchingState ): ItemSnapshotList = mutex.withLock { - operationManager.get().fold(snapshot) { acc, operation -> + operationPipeline.fold(snapshot) { acc, operation -> if (operation.shouldApply(key, pagingState, fetchingState)) { val cacheKey = CacheKey(operation, acc, key, pagingState, fetchingState) operationCache.getOrPut(cacheKey) { diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationManager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationManager.kt deleted file mode 100644 index 002b7d3..0000000 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/ConcurrentOperationManager.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl - -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.updateAndGet -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.mobilenativefoundation.storex.paging.runtime.Operation -import org.mobilenativefoundation.storex.paging.runtime.OperationManager - -/** - * This file contains a thread-safe implementation of OperationManager for Kotlin Multiplatform. - * - * Key design decisions: - * 1. Use of atomicfu for lock-free reads and atomic updates. - * 2. Mutex for synchronizing write operations. - * 3. Immutable list snapshots for consistent reads without locking. - * 4. Suspension on write operations to work well with coroutines. - * - * Trade-offs: - * - Reads are very fast and non-blocking, but they may not always reflect the absolute latest state. - * - Writes are serialized and may be slower, but they ensure consistency. - * - * Note: This implementation requires the kotlinx-atomicfu dependency. - */ - -/** - * A thread-safe implementation of OperationManager that allows for non-blocking reads - * and synchronized writes. - * - * @param ItemId The type of the item identifier. - * @param PageRequestKey The type of the paging key. - * @param ItemValue The type of the item value. - */ -class ConcurrentOperationManager : - OperationManager { - - // Atomic reference to the list of operations, allowing for lock-free reads - private val operationsAtomic = atomic(listOf>()) - - // Mutex for synchronizing write operations - private val writeMutex = Mutex() - - /** - * Adds an operation to the manager if it's not already present. - * - * @param operation The operation to add. - */ - override suspend fun add(operation: Operation) { - writeMutex.withLock { - // Atomic update ensures that the change is visible to all threads immediately - operationsAtomic.updateAndGet { current -> - if (operation !in current) current + operation else current - } - } - } - - /** - * Removes a specific operation from the manager. - * - * @param operation The operation to remove. - */ - override suspend fun remove(operation: Operation) { - writeMutex.withLock { - operationsAtomic.updateAndGet { it - operation } - } - } - - /** - * Removes all operations that match the given predicate. - * - * @param predicate A function that determines which operations to remove. - */ - override suspend fun removeAll(predicate: (Operation) -> Boolean) { - writeMutex.withLock { - operationsAtomic.updateAndGet { it.filterNot(predicate) } - } - } - - /** - * Clears all operations from the manager. - */ - override suspend fun clear() { - writeMutex.withLock { - operationsAtomic.value = emptyList() - } - } - - /** - * Retrieves a snapshot of all current operations. - * - * This is a non-blocking operation and returns an immutable list. - * - * @return A list of all current operations. - */ - override fun get(): List> { - // Lock-free read of the current state - return operationsAtomic.value - } - - /** - * Retrieves a filtered snapshot of operations that match the given predicate. - * - * This is a non-blocking operation and returns an immutable list. - * - * @param predicate A function that determines which operations to include. - * @return A filtered list of operations. - */ - override fun get(predicate: (Operation) -> Boolean): List> { - // Lock-free read and filter - return operationsAtomic.value.filter(predicate) - } -} \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt index 4355f64..f13fe99 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealPager.kt @@ -24,6 +24,7 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.logger.api.Pagi import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.FetchingStateHolder import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LoadParamsQueue import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LoadingHandler +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.PagingStateManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.QueueManager import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.NormalizedStore @@ -46,6 +47,7 @@ internal class RealPager( private val queueManager: QueueManager, private val loadingHandler: LoadingHandler, private val coroutineScope: CoroutineScope, + private val mutableOperationPipeline: MutableOperationPipeline ) : Pager { init { @@ -143,9 +145,9 @@ internal class RealPager( is Action.Enqueue -> handleEnqueueAction(action) Action.Invalidate -> handleInvalidateAction() - is Action.AddOperation -> TODO() - Action.ClearOperations -> TODO() - is Action.RemoveOperation -> TODO() + is Action.AddOperation -> mutableOperationPipeline.add(action.operation) + Action.ClearOperations -> mutableOperationPipeline.clear() + is Action.RemoveOperation -> mutableOperationPipeline.remove(action.operation) } } } diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt index 3d4e49e..eeba50a 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt @@ -32,13 +32,11 @@ import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.Fetch import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LinkedHashMapManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.ListSortAnalyzer import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LoadingHandler -import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.OperationApplier import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.PagingStateManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.QueueManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.ConcurrentFetchingStateHolder import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.ConcurrentOperationApplier -import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.ConcurrentOperationManager import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.DefaultFetchingStrategy import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.DefaultListSortAnalyzer import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.RealLinkedHashMapManager @@ -136,7 +134,6 @@ class PagingScopeBuilder( } override fun build(): PagingScope { - val operationManager = ConcurrentOperationManager() val fetchingStateHolder = ConcurrentFetchingStateHolder(initialFetchingState, itemIdComparator, pageRequestKeyComparator) val dataPersistence = RealDataPersistence(itemPersistence, pagePersistence) @@ -162,7 +159,7 @@ class PagingScopeBuilder( val pagingStateManager = RealPagingStateManager(initialState, logger) val queueManager = RealQueueManager(logger, pageRequestKeyComparator) val mutableOperationPipeline = RealMutableOperationPipeline(initialOperations) - val operationApplier = ConcurrentOperationApplier(operationManager, mutableOperationPipeline) + val operationApplier = ConcurrentOperationApplier(mutableOperationPipeline) val loadingHandler = createLoadingHandler( store = store, pagingStateManager = pagingStateManager, @@ -184,12 +181,12 @@ class PagingScopeBuilder( pagingStateManager = pagingStateManager, queueManager = queueManager, loadingHandler = loadingHandler, - coroutineScope = coroutineScope + coroutineScope = coroutineScope, + mutableOperationPipeline = mutableOperationPipeline ) return RealPagingScope( pager = pager, - operationManager = operationManager, dispatcher = dispatcher, updatingItemProvider = updatingItemProvider ) diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt index 6525b0e..27ba8c7 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/RealPagingScope.kt @@ -1,14 +1,12 @@ package org.mobilenativefoundation.storex.paging.runtime.internal.pagingScope.impl import org.mobilenativefoundation.storex.paging.runtime.Dispatcher -import org.mobilenativefoundation.storex.paging.runtime.OperationManager import org.mobilenativefoundation.storex.paging.runtime.Pager import org.mobilenativefoundation.storex.paging.runtime.PagingScope import org.mobilenativefoundation.storex.paging.runtime.UpdatingItemProvider class RealPagingScope( private val pager: Pager, - private val operationManager: OperationManager, private val dispatcher: Dispatcher, private val updatingItemProvider: UpdatingItemProvider ) : PagingScope { @@ -16,10 +14,6 @@ class RealPagingScope( return pager } - override fun getOperationManager(): OperationManager { - return operationManager - } - override fun getDispatcher(): Dispatcher { return dispatcher } From 59590a7c2fbe07e7cd38139cdbc8504130f47324 Mon Sep 17 00:00:00 2001 From: matt-ramotar Date: Sun, 21 Jul 2024 10:12:49 -0400 Subject: [PATCH 3/3] Update README.md Signed-off-by: matt-ramotar --- README.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ec0cfa2..7bc68bd 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,12 @@ class TimelineScreenPresenter( ### Adding Sorting and Filtering -1. Implement the `Operation` interface for customized sorting and filtering: +1. Implement the `Operation` abstract class for customized sorting and filtering: ```kotlin class SortForTimeRange(private val timeRange: TimeRange) : - Operation { - override fun shouldApply( + Operation() { + internal override fun shouldApply( key: TimelineRequest?, pagingState: PagingState, fetchingState: FetchingState @@ -134,7 +134,7 @@ class SortForTimeRange(private val timeRange: TimeRange) : return true } - override fun apply( + internal override fun apply( snapshot: ItemSnapshotList, key: TimelineRequest?, pagingState: PagingState, @@ -181,7 +181,7 @@ class TimelineScreenPresenter(...) : Presenter { val operation = when (sortingMethod) { is Top -> SortForTimeRange(operation.timeRange) } - operationPipeline.clear().add(operation) + dispatcher.dispatch(Action.UpdateOperations(operation)) } return TimelineScreen.State( @@ -240,41 +240,41 @@ fun TimelinePostLoadedUi( object CursorComparator : Comparator { - override fun compare(a: Cursor, b: Cursor): Int { - val parsedCursorA = parseCursor(a) - val parsedCursorB = parseCursor(b) - return parsedCursorA.first.compareTo(parsedCursorB.first) - } + override fun compare(a: Cursor, b: Cursor): Int { + val parsedCursorA = parseCursor(a) + val parsedCursorB = parseCursor(b) + return parsedCursorA.first.compareTo(parsedCursorB.first) + } - override fun distance(a: Cursor, b: Cursor): Int { - val parsedCursorA = parseCursor(a) - val parsedCursorB = parseCursor(b) + override fun distance(a: Cursor, b: Cursor): Int { + val parsedCursorA = parseCursor(a) + val parsedCursorB = parseCursor(b) - // Compare timestamps - val timeDiff = parsedCursorA.first - parsedCursorB.first + // Compare timestamps + val timeDiff = parsedCursorA.first - parsedCursorB.first - return when { - // If timestamps are different, use their difference - // Coercing to Int range to avoid overflow issues - timeDiff != 0L -> timeDiff.coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() + return when { + // If timestamps are different, use their difference + // Coercing to Int range to avoid overflow issues + timeDiff != 0L -> timeDiff.coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() - // If timestamps are the same, compare the unique parts lexicographically - // This ensures a consistent, deterministic ordering - else -> parsedCursorA.second.compareTo(parsedCursorB.second) + // If timestamps are the same, compare the unique parts lexicographically + // This ensures a consistent, deterministic ordering + else -> parsedCursorA.second.compareTo(parsedCursorB.second) + } } - } - private fun parseCursor(cursor: Cursor): Pair { - // Parsing the cursor string into its components - val parts = cursor.split('-') - require(parts.size == 2) { "Invalid cursor format. Expected 'timestamp-uniqueId'" } + private fun parseCursor(cursor: Cursor): Pair { + // Parsing the cursor string into its components + val parts = cursor.split('-') + require(parts.size == 2) { "Invalid cursor format. Expected 'timestamp-uniqueId'" } - // Converting the timestamp string to a Long for numerical comparison - val timestamp = parts[0].toLongOrNull() ?: throw IllegalArgumentException("Invalid timestamp in cursor") - val uniqueId = parts[1] + // Converting the timestamp string to a Long for numerical comparison + val timestamp = parts[0].toLongOrNull() ?: throw IllegalArgumentException("Invalid timestamp in cursor") + val uniqueId = parts[1] - return timestamp to uniqueId - } + return timestamp to uniqueId + } } ```