Skip to content

Commit

Permalink
Add immutable and mutable operation pipeline
Browse files Browse the repository at this point in the history
Signed-off-by: matt-ramotar <[email protected]>
  • Loading branch information
matt-ramotar committed Jul 21, 2024
1 parent 204fd3b commit 2431a7e
Show file tree
Hide file tree
Showing 23 changed files with 174 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemId : Any, PageRequestKey : Any> {
interface FetchingStrategy<ItemId : Any, PageRequestKey : Any, ItemValue : Any> {

/**
* Determines whether to fetch more data in the forward direction based on the current state of the pager.
Expand All @@ -29,7 +29,7 @@ interface FetchingStrategy<ItemId : Any, PageRequestKey : Any> {
*/
fun shouldFetchForward(
params: PagingSource.LoadParams<PageRequestKey>,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>,
): Boolean

Expand All @@ -43,7 +43,7 @@ interface FetchingStrategy<ItemId : Any, PageRequestKey : Any> {
*/
fun shouldFetchBackward(
params: PagingSource.LoadParams<PageRequestKey>,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>,
): Boolean
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
package org.mobilenativefoundation.storex.paging.runtime

sealed class Action<out PageRequestKey : Any> {
import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline

sealed class Action<out ItemId: Any, out PageRequestKey : Any, out ItemValue: Any> {

data class ProcessQueue internal constructor(
val direction: LoadDirection
) : Action<Nothing>()
) : Action<Nothing, Nothing, Nothing>()

data class SkipQueue<K : Any> internal constructor(
val key: K,
val direction: LoadDirection,
val strategy: LoadStrategy,
) : Action<K>()
) : Action<Nothing, K, Nothing>()

data class Enqueue<K : Any> internal constructor(
val key: K,
val direction: LoadDirection,
val strategy: LoadStrategy,
val jump: Boolean
) : Action<K>()
) : Action<Nothing, K, Nothing>()

data object Invalidate : Action<Nothing, Nothing, Nothing>()

data class AddOperation<ItemId: Any, PageRequestKey: Any, ItemValue: Any>(
val operation: Operation<ItemId, PageRequestKey, ItemValue>
) : Action<ItemId, PageRequestKey, ItemValue>()

data class RemoveOperation<ItemId: Any, PageRequestKey: Any, ItemValue: Any>(
val operation: Operation<ItemId, PageRequestKey, ItemValue>
) : Action<ItemId, PageRequestKey, ItemValue>()

data object Invalidate : Action<Nothing>()
data object ClearOperations : Action<Nothing, Nothing, Nothing>()

companion object {
fun <K : Any> skipQueue(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package org.mobilenativefoundation.storex.paging.runtime

interface Dispatcher<PageRequestKey: Any> {
suspend fun dispatch(action: Action<PageRequestKey>)
interface Dispatcher<ItemId : Any, PageRequestKey : Any, ItemValue : Any> {
suspend fun dispatch(action: Action<ItemId, PageRequestKey, ItemValue>)
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemId: Any, PageRequestKey: Any, ItemValue: Any> {
abstract class Operation<ItemId: Any, PageRequestKey: Any, ItemValue: Any> {
/**
* Determines whether this operation should be applied based on the current state.
*
Expand All @@ -16,7 +16,7 @@ interface Operation<ItemId: Any, PageRequestKey: Any, ItemValue: Any> {
* @param fetchingState The current fetching state.
* @return True if the operation should be applied, false otherwise.
*/
fun shouldApply(key: PageRequestKey?, pagingState: PagingState<ItemId>, fetchingState: FetchingState<ItemId, PageRequestKey>): Boolean
internal abstract fun shouldApply(key: PageRequestKey?, pagingState: PagingState<ItemId, PageRequestKey, ItemValue>, fetchingState: FetchingState<ItemId, PageRequestKey>): Boolean

/**
* Applies the operation to the given snapshot.
Expand All @@ -27,10 +27,10 @@ interface Operation<ItemId: Any, PageRequestKey: Any, ItemValue: Any> {
* @param fetchingState The current fetching state.
* @return The transformed snapshot after applying the operation.
*/
fun apply(
internal abstract fun apply(
snapshot: ItemSnapshotList<ItemId, ItemValue>,
key: PageRequestKey?,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>
): ItemSnapshotList<ItemId, ItemValue>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.mobilenativefoundation.storex.paging.runtime

interface OperationPipeline<ItemId: Any, PageRequestKey: Any, ItemValue: Any>: List<Operation<ItemId, PageRequestKey, ItemValue>>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package org.mobilenativefoundation.storex.paging.runtime

import kotlinx.coroutines.flow.StateFlow

interface Pager<ItemId : Any> {
val state: StateFlow<PagingState<ItemId>>
interface Pager<ItemId : Any, PageRequestKey : Any, ItemValue : Any> {
val state: StateFlow<PagingState<ItemId, PageRequestKey, ItemValue>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemId: Any, PageRequestKey: Any, ItemValue: Any> {
fun getPager(): Pager<ItemId>
interface PagingScope<ItemId : Any, PageRequestKey : Any, ItemValue : Any> {
fun getPager(): Pager<ItemId, PageRequestKey, ItemValue>
fun getOperationManager(): OperationManager<ItemId, PageRequestKey, ItemValue>
fun getDispatcher(): Dispatcher<PageRequestKey>
fun getDispatcher(): Dispatcher<ItemId, PageRequestKey, ItemValue>
fun getUpdatingItemProvider(): UpdatingItemProvider<ItemId, ItemValue>

interface Builder<Id: Any, K: Any, V: Any> {
fun setInitialState(state: PagingState<Id>): Builder<Id, K, V>
fun setInitialLoadParams(params: PagingSource.LoadParams<K>): Builder<Id, K, V>
fun setPagingSource(source: PagingSource<Id, K, V>): Builder<Id, K, V>
fun setCoroutineDispatcher(dispatcher: CoroutineDispatcher): Builder<Id, K, V>
fun addLaunchEffect(effect: LaunchEffect): Builder<Id, K, V>
fun addSideEffect(effect: SideEffect<Id, V>): Builder<Id, K, V>
fun addMiddleware(mw: Middleware<K>): Builder<Id, K, V>
fun setInitialFetchingState(state: FetchingState<Id, K>): Builder<Id, K, V>
fun setFetchingStrategy(strategy: FetchingStrategy<Id, K>): Builder<Id, K, V>
fun setErrorHandlingStrategy(strategy: ErrorHandlingStrategy): Builder<Id, K, V>
fun setItemMemoryCache(cache: MutableMap<Id, V>): Builder<Id, K, V>
fun setPageMemoryCache(cache: MutableMap<K, PagingSource.LoadResult.Data<Id, K, V>>): Builder<Id, K, V>
fun setItemPersistence(persistence: ItemPersistence<Id, K, V>): Builder<Id, K, V>
fun setPagePersistence(persistence: PagePersistence<Id, K, V>): Builder<Id, K, V>
fun setItemUpdater(updater: Updater<Id, V, *>): Builder<Id, K, V>
fun setPlaceholderFactory(placeholderFactory: PlaceholderFactory<Id, K, V>): Builder<Id, K, V>
fun build(): PagingScope<Id, K, V>
interface Builder<ItemId : Any, PageRequestKey : Any, ItemValue : Any> {
fun setInitialState(state: PagingState<ItemId, PageRequestKey, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setInitialLoadParams(params: PagingSource.LoadParams<PageRequestKey>): Builder<ItemId, PageRequestKey, ItemValue>
fun setPagingSource(source: PagingSource<ItemId, PageRequestKey, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setCoroutineDispatcher(dispatcher: CoroutineDispatcher): Builder<ItemId, PageRequestKey, ItemValue>
fun addLaunchEffect(effect: LaunchEffect): Builder<ItemId, PageRequestKey, ItemValue>
fun addSideEffect(effect: SideEffect<ItemId, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun addMiddleware(mw: Middleware<PageRequestKey>): Builder<ItemId, PageRequestKey, ItemValue>
fun setInitialFetchingState(state: FetchingState<ItemId, PageRequestKey>): Builder<ItemId, PageRequestKey, ItemValue>
fun setFetchingStrategy(strategy: FetchingStrategy<ItemId, PageRequestKey, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setErrorHandlingStrategy(strategy: ErrorHandlingStrategy): Builder<ItemId, PageRequestKey, ItemValue>
fun setItemMemoryCache(cache: MutableMap<ItemId, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setPageMemoryCache(cache: MutableMap<PageRequestKey, PagingSource.LoadResult.Data<ItemId, PageRequestKey, ItemValue>>): Builder<ItemId, PageRequestKey, ItemValue>
fun setItemPersistence(persistence: ItemPersistence<ItemId, PageRequestKey, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setPagePersistence(persistence: PagePersistence<ItemId, PageRequestKey, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setItemUpdater(updater: Updater<ItemId, ItemValue, *>): Builder<ItemId, PageRequestKey, ItemValue>
fun setPlaceholderFactory(placeholderFactory: PlaceholderFactory<ItemId, PageRequestKey, ItemValue>): Builder<ItemId, PageRequestKey, ItemValue>
fun setInitialOperations(operations: List<Operation<ItemId, PageRequestKey, ItemValue>>): Builder<ItemId, PageRequestKey, ItemValue>
fun build(): PagingScope<ItemId, PageRequestKey, ItemValue>
}

companion object {
fun <Id: Any, K: Any, V: Any> builder(
fun <Id : Any, K : Any, V : Any> builder(
pagingConfig: PagingConfig<Id, K>
): Builder<Id, K, V> = PagingScopeBuilder(pagingConfig)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package org.mobilenativefoundation.storex.paging.runtime

import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.MutableOperationPipeline

data class PagingState<ItemId : Any>(

data class PagingState<ItemId : Any, PageRequestKey : Any, ItemValue : Any>(
val ids: List<ItemId?>,
val loadStates: CombinedLoadStates,
val mutableOperationPipeline: MutableOperationPipeline<ItemId, PageRequestKey, ItemValue>
) {
companion object {
fun <ItemId: Any> initial() = PagingState<ItemId>(
fun <ItemId : Any, PageRequestKey : Any, ItemValue : Any> initial() = PagingState<ItemId, PageRequestKey, ItemValue>(
emptyList(),
CombinedLoadStates.initial()
CombinedLoadStates.initial(),
MutableOperationPipeline.empty()
)
}
}
Original file line number Diff line number Diff line change
@@ -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<ItemId : Any, PageRequestKey : Any, ItemValue : Any> :
OperationPipeline<ItemId, PageRequestKey, ItemValue>, MutableList<Operation<ItemId, PageRequestKey, ItemValue>> {

companion object {
fun <ItemId : Any, PageRequestKey : Any, ItemValue : Any> empty(): MutableOperationPipeline<ItemId, PageRequestKey, ItemValue> {
return RealMutableOperationPipeline(mutableListOf())
}
}
}



Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal interface OperationApplier<ItemId: Any, PageRequestKey: Any, ItemValue:
suspend fun applyOperations(
snapshot: ItemSnapshotList<ItemId, ItemValue>,
key: PageRequestKey?,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>
): ItemSnapshotList<ItemId, ItemValue>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import org.mobilenativefoundation.storex.paging.runtime.PagingState
/**
* Manages the paging state and provides methods to update it.
*/
internal interface PagingStateManager<ItemId: Any> {
val pagingState: StateFlow<PagingState<ItemId>>
internal interface PagingStateManager<ItemId : Any, PageRequestKey : Any, ItemValue : Any> {
val pagingState: StateFlow<PagingState<ItemId, PageRequestKey, ItemValue>>
fun updateWithAppendData(ids: List<ItemId?>, endOfPaginationReached: Boolean)
fun updateWithPrependData(ids: List<ItemId?>, endOfPaginationReached: Boolean)
fun updateWithAppendError(error: Throwable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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<ItemId: Any, PageRequestKey: Any, ItemValue: Any>(
private val operationManager: OperationManager<ItemId, PageRequestKey, ItemValue>
class ConcurrentOperationApplier<ItemId : Any, PageRequestKey : Any, ItemValue : Any>(
private val operationManager: OperationManager<ItemId, PageRequestKey, ItemValue>,
private val mutableOperationPipeline: MutableOperationPipeline<ItemId, PageRequestKey, ItemValue>
) : OperationApplier<ItemId, PageRequestKey, ItemValue> {

// Mutex for ensuring thread-safe access to shared resources
Expand All @@ -44,7 +46,7 @@ class ConcurrentOperationApplier<ItemId: Any, PageRequestKey: Any, ItemValue: An
override suspend fun applyOperations(
snapshot: ItemSnapshotList<ItemId, ItemValue>,
key: PageRequestKey?,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>
): ItemSnapshotList<ItemId, ItemValue> = mutex.withLock {
operationManager.get().fold(snapshot) { acc, operation ->
Expand All @@ -68,12 +70,12 @@ class ConcurrentOperationApplier<ItemId: Any, PageRequestKey: Any, ItemValue: An
* @param pagingState The paging state.
* @param fetchingState The fetching state.
*/
private data class CacheKey<Id: Any, K: Any, V: Any>(
val operation: Operation<Id, K, V>,
val snapshot: ItemSnapshotList<Id, V>,
val key: K?,
val pagingState: PagingState<Id>,
val fetchingState: FetchingState<Id, K>
private data class CacheKey<ItemId : Any, PageRequestKey : Any, ItemValue : Any>(
val operation: Operation<ItemId, PageRequestKey, ItemValue>,
val snapshot: ItemSnapshotList<ItemId, ItemValue>,
val key: PageRequestKey?,
val pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
val fetchingState: FetchingState<ItemId, PageRequestKey>
)
}

Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemId : Any, PageRequestKey : Any>(
class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any, ItemValue: Any>(
private val pagingConfig: PagingConfig<ItemId, PageRequestKey>,
private val logger: PagingLogger,
private val listSortAnalyzer: ListSortAnalyzer<ItemId>,
private val itemIdComparator: Comparator<ItemId>
) : FetchingStrategy<ItemId, PageRequestKey> {
) : FetchingStrategy<ItemId, PageRequestKey, ItemValue> {

// Thread-safe caching of the last analyzed list and its sort order
private data class CachedOrder<Id>(val ids: List<Id?>, val order: Order)
Expand All @@ -38,7 +38,7 @@ class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any>(
*/
override fun shouldFetchForward(
params: PagingSource.LoadParams<PageRequestKey>,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>
): Boolean {
return shouldFetch(pagingState, fetchingState, FetchDirection.FORWARD)
Expand All @@ -49,7 +49,7 @@ class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any>(
*/
override fun shouldFetchBackward(
params: PagingSource.LoadParams<PageRequestKey>,
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>
): Boolean {
return shouldFetch(pagingState, fetchingState, FetchDirection.BACKWARD)
Expand All @@ -60,7 +60,7 @@ class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any>(
* This method is optimized for performance and thread safety.
*/
private fun shouldFetch(
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
fetchingState: FetchingState<ItemId, PageRequestKey>,
fetchDirection: FetchDirection
): Boolean {
Expand Down Expand Up @@ -129,7 +129,7 @@ class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any>(
* Inlined for performance in high-frequency calls.
*/
private inline fun checkFetchCondition(
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
itemLoadedSoFar: ItemId?,
itemAccessedSoFar: ItemId?,
sortOrder: Order
Expand All @@ -150,11 +150,11 @@ class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any>(
* 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<ItemId>): Int {
private fun getDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState<ItemId, PageRequestKey, ItemValue>): Int {
return itemIdComparator.distance(itemLoaded, itemAccessed) ?: calculateIndexDistance(itemLoaded, itemAccessed, pagingState)
}

private fun calculateIndexDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState<ItemId>): Int {
private fun calculateIndexDistance(itemLoaded: ItemId, itemAccessed: ItemId, pagingState: PagingState<ItemId, PageRequestKey, ItemValue>): Int {
var loadedIndex = -1
var accessedIndex = -1

Expand All @@ -181,7 +181,7 @@ class DefaultFetchingStrategy<ItemId : Any, PageRequestKey : Any>(
* Inlined for performance in high-frequency calls.
*/
private inline fun isUnderPrefetchLimit(
pagingState: PagingState<ItemId>,
pagingState: PagingState<ItemId, PageRequestKey, ItemValue>,
itemLoadedSoFar: ItemId,
sortOrder: Order
): Boolean {
Expand Down
Loading

0 comments on commit 2431a7e

Please sign in to comment.