Moves java.util.SortedSet to an arrayList.
This commit is contained in:
+5
-21
@@ -32,6 +32,7 @@ import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpirationBefore
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip62RequestToVanish.RequestToVanishEvent
|
||||
import com.vitorpamplona.quartz.utils.SortedList
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -39,23 +40,6 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.yield
|
||||
|
||||
/**
|
||||
* Lifecycle state of an [ObservableEventStore.project] flow.
|
||||
*
|
||||
* - [Loading] is the initial state before the seed query completes.
|
||||
* The UI should show a spinner / skeleton here.
|
||||
* - [Loaded] holds the current deduped, sorted list of slots. Every
|
||||
* membership change publishes a fresh [Loaded] instance; in-place
|
||||
* addressable updates do *not* publish (the slots' own flows do).
|
||||
*/
|
||||
sealed interface ProjectionState<out T : Event> {
|
||||
data object Loading : ProjectionState<Nothing>
|
||||
|
||||
data class Loaded<T : Event>(
|
||||
val items: List<MutableStateFlow<T>>,
|
||||
) : ProjectionState<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* State machine that maintains a reactive view over an
|
||||
* [ObservableEventStore] for a fixed set of [filters]. Pure logic, no
|
||||
@@ -106,8 +90,8 @@ class EventStoreProjection<T : Event>(
|
||||
* least one of these sets. Identity-keyed — [Filter] is `@Stable`
|
||||
* but not a data class.
|
||||
*/
|
||||
private val perFilter: Map<Filter, java.util.SortedSet<Slot<T>>> =
|
||||
filters.associateWith { sortedSetOf(slotComparator()) }
|
||||
private val perFilter: Map<Filter, SortedList<Slot<T>>> =
|
||||
filters.associateWith { SortedList(slotComparator<T>()) }
|
||||
|
||||
/**
|
||||
* Run the seed query against the store and populate the indexes.
|
||||
@@ -158,7 +142,7 @@ class EventStoreProjection<T : Event>(
|
||||
if (perFilter.size == 1) {
|
||||
return ProjectionState.Loaded(perFilter.values.first().map { it.flow })
|
||||
}
|
||||
val union = sortedSetOf(slotComparator<T>())
|
||||
val union = SortedList(slotComparator<T>())
|
||||
for (set in perFilter.values) union.addAll(set)
|
||||
return ProjectionState.Loaded(union.map { it.flow })
|
||||
}
|
||||
@@ -260,7 +244,7 @@ class EventStoreProjection<T : Event>(
|
||||
private fun admit(
|
||||
slot: Slot<T>,
|
||||
f: Filter,
|
||||
set: java.util.SortedSet<Slot<T>>,
|
||||
set: SortedList<Slot<T>>,
|
||||
): Boolean {
|
||||
set.add(slot)
|
||||
val cap = f.limit ?: return false
|
||||
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.cache.projection
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Lifecycle state of an [ObservableEventStore.project] flow.
|
||||
*
|
||||
* - [Loading] is the initial state before the seed query completes.
|
||||
* The UI should show a spinner / skeleton here.
|
||||
* - [Loaded] holds the current deduped, sorted list of slots. Every
|
||||
* membership change publishes a fresh [Loaded] instance; in-place
|
||||
* addressable updates do *not* publish (the slots' own flows do).
|
||||
*/
|
||||
sealed interface ProjectionState<out T : Event> {
|
||||
data object Loading : ProjectionState<Nothing>
|
||||
|
||||
data class Loaded<T : Event>(
|
||||
val items: List<MutableStateFlow<T>>,
|
||||
) : ProjectionState<T>
|
||||
}
|
||||
|
||||
inline fun <T : Event> ProjectionState<T>.filterItems(predicate: (MutableStateFlow<T>) -> Boolean): ProjectionState<T> =
|
||||
when (this) {
|
||||
is ProjectionState.Loading -> this
|
||||
is ProjectionState.Loaded -> ProjectionState.Loaded(items.filter(predicate))
|
||||
}
|
||||
|
||||
inline fun <T : Event, R : Event> ProjectionState<T>.mapItems(transform: (MutableStateFlow<T>) -> MutableStateFlow<R>): ProjectionState<R> =
|
||||
when (this) {
|
||||
is ProjectionState.Loading -> ProjectionState.Loading
|
||||
is ProjectionState.Loaded -> ProjectionState.Loaded(items.map(transform))
|
||||
}
|
||||
|
||||
inline fun <T : Event> Flow<ProjectionState<T>>.filterItems(crossinline predicate: (MutableStateFlow<T>) -> Boolean): Flow<ProjectionState<T>> = map { it.filterItems(predicate) }
|
||||
|
||||
inline fun <T : Event> Flow<ProjectionState<T>>.mapItems(crossinline transform: (MutableStateFlow<T>) -> MutableStateFlow<T>): Flow<ProjectionState<T>> = map { it.mapItems(transform) }
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
/**
|
||||
* KMP substitute for `java.util.TreeSet`. Backed by a sorted [ArrayList];
|
||||
* O(log n) add/remove/contains via binary search, O(1) [last]/[size],
|
||||
* in-order iteration.
|
||||
*
|
||||
* The [cmp] must be total — distinct elements must not compare equal,
|
||||
* otherwise duplicates collapse silently. Optimised for small-to-medium
|
||||
* `n`; insert/remove in the middle is O(n) due to ArrayList shifting,
|
||||
* which is faster in practice than a tree for the bounded sizes typical
|
||||
* of per-filter caps.
|
||||
*/
|
||||
class SortedList<E>(
|
||||
private val cmp: Comparator<in E>,
|
||||
) : Iterable<E> {
|
||||
private val list = ArrayList<E>()
|
||||
|
||||
val size: Int get() = list.size
|
||||
|
||||
fun isEmpty(): Boolean = list.isEmpty()
|
||||
|
||||
fun add(element: E): Boolean {
|
||||
val idx = list.binarySearch(element, cmp)
|
||||
if (idx >= 0) return false
|
||||
list.add(-idx - 1, element)
|
||||
return true
|
||||
}
|
||||
|
||||
fun remove(element: E): Boolean {
|
||||
val idx = list.binarySearch(element, cmp)
|
||||
if (idx < 0) return false
|
||||
list.removeAt(idx)
|
||||
return true
|
||||
}
|
||||
|
||||
fun contains(element: E): Boolean = list.binarySearch(element, cmp) >= 0
|
||||
|
||||
fun last(): E = list.last()
|
||||
|
||||
fun addAll(other: Iterable<E>) {
|
||||
for (e in other) add(e)
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<E> = list.iterator()
|
||||
}
|
||||
Reference in New Issue
Block a user