refactor(cache): P3 simplifications — remove dead code, reduce abstractions
- Replace SubscriptionHealth map + data class with single lastEventAt Long? timestamp (only consumer was RelayHealthIndicator taking max()) - Replace consumer HashMap registry with when block (safe casts, compiler-checked, 9 kinds doesn't benefit from O(1) lookup) - Remove dead loadReactionsForNotes() (superseded by requestInteractions) - Remove unused BoundedLargeCache methods: containsKey, isEmpty, mapNotNull, forEach (zero external callers) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -736,7 +736,7 @@ fun MainContent(
|
|||||||
Row(Modifier.fillMaxSize().weight(1f)) {
|
Row(Modifier.fillMaxSize().weight(1f)) {
|
||||||
when (layoutMode) {
|
when (layoutMode) {
|
||||||
LayoutMode.SINGLE_PANE -> {
|
LayoutMode.SINGLE_PANE -> {
|
||||||
val healthMap by subscriptionsCoordinator.subscriptionHealth.collectAsState()
|
val lastRelayEvent by subscriptionsCoordinator.lastEventAt.collectAsState()
|
||||||
SinglePaneLayout(
|
SinglePaneLayout(
|
||||||
relayManager = relayManager,
|
relayManager = relayManager,
|
||||||
localCache = localCache,
|
localCache = localCache,
|
||||||
@@ -753,7 +753,7 @@ fun MainContent(
|
|||||||
onZapFeedback = onZapFeedback,
|
onZapFeedback = onZapFeedback,
|
||||||
signerConnectionState = signerConnectionState,
|
signerConnectionState = signerConnectionState,
|
||||||
lastPingTimeSec = lastPingTimeSec,
|
lastPingTimeSec = lastPingTimeSec,
|
||||||
subscriptionHealth = healthMap,
|
lastRelayEventAt = lastRelayEvent,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-8
@@ -72,12 +72,8 @@ class BoundedLargeCache<K : Comparable<K>, V>(
|
|||||||
return removed
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
fun containsKey(key: K): Boolean = inner.containsKey(key)
|
|
||||||
|
|
||||||
fun size(): Int = sizeCounter.get()
|
fun size(): Int = sizeCounter.get()
|
||||||
|
|
||||||
fun isEmpty(): Boolean = sizeCounter.get() == 0
|
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
inner.clear()
|
inner.clear()
|
||||||
sizeCounter.set(0)
|
sizeCounter.set(0)
|
||||||
@@ -89,10 +85,6 @@ class BoundedLargeCache<K : Comparable<K>, V>(
|
|||||||
|
|
||||||
fun filterIntoSet(consumer: CacheCollectors.BiFilter<K, V>): Set<V> = inner.filterIntoSet(consumer)
|
fun filterIntoSet(consumer: CacheCollectors.BiFilter<K, V>): Set<V> = inner.filterIntoSet(consumer)
|
||||||
|
|
||||||
fun <R> mapNotNull(consumer: CacheCollectors.BiMapper<K, V, R?>): List<R> = inner.mapNotNull(consumer)
|
|
||||||
|
|
||||||
fun forEach(consumer: java.util.function.BiConsumer<K, V>) = inner.forEach(consumer)
|
|
||||||
|
|
||||||
fun count(consumer: CacheCollectors.BiFilter<K, V>): Int = inner.count(consumer)
|
fun count(consumer: CacheCollectors.BiFilter<K, V>): Int = inner.count(consumer)
|
||||||
|
|
||||||
private fun enforceSize() {
|
private fun enforceSize() {
|
||||||
|
|||||||
Vendored
+46
-23
@@ -82,25 +82,6 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
|
|
||||||
val paymentTracker = NwcPaymentTracker()
|
val paymentTracker = NwcPaymentTracker()
|
||||||
|
|
||||||
// ----- Kind-based event consumer registry -----
|
|
||||||
|
|
||||||
private val consumers = HashMap<Int, (Event, NormalizedRelayUrl?) -> Boolean>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
consumers[MetadataEvent.KIND] = { e, _ ->
|
|
||||||
consumeMetadata(e as MetadataEvent)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
consumers[TextNoteEvent.KIND] = { e, r -> consumeTextNote(e as TextNoteEvent, r) }
|
|
||||||
consumers[ReactionEvent.KIND] = { e, r -> consumeReaction(e as ReactionEvent, r) }
|
|
||||||
consumers[LnZapRequestEvent.KIND] = { e, r -> consumeZapRequest(e as LnZapRequestEvent, r) }
|
|
||||||
consumers[LnZapEvent.KIND] = { e, r -> consumeZap(e as LnZapEvent, r) }
|
|
||||||
consumers[RepostEvent.KIND] = { e, r -> consumeRepost(e as RepostEvent, r) }
|
|
||||||
consumers[ContactListEvent.KIND] = { e, _ -> consumeContactList(e as ContactListEvent) }
|
|
||||||
consumers[LongTextNoteEvent.KIND] = { e, r -> consumeLongTextNote(e as LongTextNoteEvent, r) }
|
|
||||||
consumers[BookmarkListEvent.KIND] = { e, _ -> consumeBookmarkList(e as BookmarkListEvent) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- User operations -----
|
// ----- User operations -----
|
||||||
|
|
||||||
override fun getUserIfExists(pubkey: HexKey): User? = users.get(pubkey)
|
override fun getUserIfExists(pubkey: HexKey): User? = users.get(pubkey)
|
||||||
@@ -170,16 +151,58 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Event consumption (kind-based registry) -----
|
// ----- Event consumption -----
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes an event to the appropriate consume method via kind-based registry.
|
* Routes an event to the appropriate consume method.
|
||||||
* O(1) dispatch. Returns true if the event was consumed (new), false if already seen.
|
* Returns true if the event was consumed (new), false if already seen.
|
||||||
*/
|
*/
|
||||||
fun consume(
|
fun consume(
|
||||||
event: Event,
|
event: Event,
|
||||||
relay: NormalizedRelayUrl?,
|
relay: NormalizedRelayUrl?,
|
||||||
): Boolean = consumers[event.kind]?.invoke(event, relay) ?: false
|
): Boolean =
|
||||||
|
when (event) {
|
||||||
|
is MetadataEvent -> {
|
||||||
|
consumeMetadata(event)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is TextNoteEvent -> {
|
||||||
|
consumeTextNote(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReactionEvent -> {
|
||||||
|
consumeReaction(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
is LnZapRequestEvent -> {
|
||||||
|
consumeZapRequest(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
is LnZapEvent -> {
|
||||||
|
consumeZap(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
is RepostEvent -> {
|
||||||
|
consumeRepost(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ContactListEvent -> {
|
||||||
|
consumeContactList(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
is LongTextNoteEvent -> {
|
||||||
|
consumeLongTextNote(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookmarkListEvent -> {
|
||||||
|
consumeBookmarkList(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes a kind 1 text note event.
|
* Consumes a kind 1 text note event.
|
||||||
|
|||||||
+5
-43
@@ -40,7 +40,6 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@@ -68,11 +67,6 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
data class SubscriptionHealth(
|
|
||||||
val lastEventReceivedAt: Long? = null,
|
|
||||||
val eoseReceived: Boolean = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
class DesktopRelaySubscriptionsCoordinator(
|
class DesktopRelaySubscriptionsCoordinator(
|
||||||
private val client: INostrClient,
|
private val client: INostrClient,
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
@@ -112,26 +106,9 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
// Screen-triggered subscription Jobs — keyed by subId for proper cancellation
|
// Screen-triggered subscription Jobs — keyed by subId for proper cancellation
|
||||||
private val screenSubscriptions = ConcurrentHashMap<String, Job>()
|
private val screenSubscriptions = ConcurrentHashMap<String, Job>()
|
||||||
|
|
||||||
// Subscription health tracking
|
// Last event received from any subscription — drives RelayHealthIndicator
|
||||||
private val _subscriptionHealth = MutableStateFlow<Map<String, SubscriptionHealth>>(emptyMap())
|
private val _lastEventAt = MutableStateFlow<Long?>(null)
|
||||||
val subscriptionHealth: StateFlow<Map<String, SubscriptionHealth>> = _subscriptionHealth.asStateFlow()
|
val lastEventAt: StateFlow<Long?> = _lastEventAt.asStateFlow()
|
||||||
|
|
||||||
private fun updateHealth(
|
|
||||||
subId: String,
|
|
||||||
lastEventReceivedAt: Long? = null,
|
|
||||||
eoseReceived: Boolean? = null,
|
|
||||||
) {
|
|
||||||
_subscriptionHealth.update { current ->
|
|
||||||
current.toMutableMap().apply {
|
|
||||||
val existing = this[subId] ?: SubscriptionHealth()
|
|
||||||
this[subId] =
|
|
||||||
existing.copy(
|
|
||||||
lastEventReceivedAt = lastEventReceivedAt ?: existing.lastEventReceivedAt,
|
|
||||||
eoseReceived = eoseReceived ?: existing.eoseReceived,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central event router — consumes an event into the cache and emits to event stream.
|
* Central event router — consumes an event into the cache and emits to event stream.
|
||||||
@@ -146,6 +123,7 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
try {
|
try {
|
||||||
val consumed = localCache.consume(event, relay)
|
val consumed = localCache.consume(event, relay)
|
||||||
if (consumed) {
|
if (consumed) {
|
||||||
|
_lastEventAt.value = System.currentTimeMillis()
|
||||||
val note = localCache.getNoteIfExists(event.id) ?: return@launch
|
val note = localCache.getNoteIfExists(event.id) ?: return@launch
|
||||||
eventBundler.invalidateList(note) { batch ->
|
eventBundler.invalidateList(note) { batch ->
|
||||||
localCache.eventStream.emitNewNotes(batch)
|
localCache.eventStream.emitNewNotes(batch)
|
||||||
@@ -202,14 +180,6 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
forFilters: List<Filter>?,
|
forFilters: List<Filter>?,
|
||||||
) {
|
) {
|
||||||
consumeEvent(event, relay)
|
consumeEvent(event, relay)
|
||||||
updateHealth(subId, lastEventReceivedAt = System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEose(
|
|
||||||
relay: NormalizedRelayUrl,
|
|
||||||
forFilters: List<Filter>?,
|
|
||||||
) {
|
|
||||||
updateHealth(subId, eoseReceived = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +202,6 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
fun releaseInteractions(subId: String) {
|
fun releaseInteractions(subId: String) {
|
||||||
screenSubscriptions.remove(subId)?.cancel()
|
screenSubscriptions.remove(subId)?.cancel()
|
||||||
client.close(subId)
|
client.close(subId)
|
||||||
_subscriptionHealth.update { it - subId }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,13 +245,6 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
feedMetadata.loadMetadataForPubkeys(pubkeys)
|
feedMetadata.loadMetadataForPubkeys(pubkeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load reactions for specific notes.
|
|
||||||
*/
|
|
||||||
fun loadReactionsForNotes(noteIds: List<HexKey>) {
|
|
||||||
feedMetadata.loadReactionsForNotes(noteIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- DM Subscription Support --
|
// -- DM Subscription Support --
|
||||||
|
|
||||||
/** Active DM subscription IDs for cleanup */
|
/** Active DM subscription IDs for cleanup */
|
||||||
@@ -389,7 +351,7 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
client.close(subId)
|
client.close(subId)
|
||||||
}
|
}
|
||||||
screenSubscriptions.clear()
|
screenSubscriptions.clear()
|
||||||
_subscriptionHealth.value = emptyMap()
|
_lastEventAt.value = null
|
||||||
|
|
||||||
unsubscribeFromDms()
|
unsubscribeFromDms()
|
||||||
feedMetadata.clear()
|
feedMetadata.clear()
|
||||||
|
|||||||
+2
-7
@@ -65,7 +65,6 @@ import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
|
|||||||
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
|
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
|
||||||
import com.vitorpamplona.amethyst.desktop.service.highlights.DesktopHighlightStore
|
import com.vitorpamplona.amethyst.desktop.service.highlights.DesktopHighlightStore
|
||||||
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
|
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
|
||||||
import com.vitorpamplona.amethyst.desktop.subscriptions.SubscriptionHealth
|
|
||||||
import com.vitorpamplona.amethyst.desktop.ui.ZapFeedback
|
import com.vitorpamplona.amethyst.desktop.ui.ZapFeedback
|
||||||
import com.vitorpamplona.amethyst.desktop.ui.components.RelayHealthIndicator
|
import com.vitorpamplona.amethyst.desktop.ui.components.RelayHealthIndicator
|
||||||
import com.vitorpamplona.amethyst.desktop.ui.media.LocalIsImmersiveFullscreen
|
import com.vitorpamplona.amethyst.desktop.ui.media.LocalIsImmersiveFullscreen
|
||||||
@@ -110,7 +109,7 @@ fun SinglePaneLayout(
|
|||||||
onZapFeedback: (ZapFeedback) -> Unit,
|
onZapFeedback: (ZapFeedback) -> Unit,
|
||||||
signerConnectionState: SignerConnectionState,
|
signerConnectionState: SignerConnectionState,
|
||||||
lastPingTimeSec: Long?,
|
lastPingTimeSec: Long?,
|
||||||
subscriptionHealth: Map<String, SubscriptionHealth> = emptyMap(),
|
lastRelayEventAt: Long? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var currentColumnType by remember { mutableStateOf<DeckColumnType>(DeckColumnType.HomeFeed) }
|
var currentColumnType by remember { mutableStateOf<DeckColumnType>(DeckColumnType.HomeFeed) }
|
||||||
@@ -154,12 +153,8 @@ fun SinglePaneLayout(
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
// Relay health — shows elapsed time since last event (hidden when <30s)
|
// Relay health — shows elapsed time since last event (hidden when <30s)
|
||||||
val latestEvent =
|
|
||||||
subscriptionHealth.values
|
|
||||||
.mapNotNull { it.lastEventReceivedAt }
|
|
||||||
.maxOrNull()
|
|
||||||
RelayHealthIndicator(
|
RelayHealthIndicator(
|
||||||
lastEventReceivedAt = latestEvent,
|
lastEventReceivedAt = lastRelayEventAt,
|
||||||
modifier = Modifier.padding(bottom = 4.dp),
|
modifier = Modifier.padding(bottom = 4.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user