From 0621e8f7c2383ed2100621e696c601b3defe5f4a Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Mon, 23 Mar 2026 12:07:22 +0200 Subject: [PATCH] =?UTF-8?q?refactor(cache):=20P3=20simplifications=20?= =?UTF-8?q?=E2=80=94=20remove=20dead=20code,=20reduce=20abstractions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .../vitorpamplona/amethyst/desktop/Main.kt | 4 +- .../desktop/cache/BoundedLargeCache.kt | 8 --- .../desktop/cache/DesktopLocalCache.kt | 69 ++++++++++++------- .../DesktopRelaySubscriptionsCoordinator.kt | 48 ++----------- .../desktop/ui/deck/SinglePaneLayout.kt | 9 +-- 5 files changed, 55 insertions(+), 83 deletions(-) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt index f8a9bd57b..0dfaa05c0 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt @@ -736,7 +736,7 @@ fun MainContent( Row(Modifier.fillMaxSize().weight(1f)) { when (layoutMode) { LayoutMode.SINGLE_PANE -> { - val healthMap by subscriptionsCoordinator.subscriptionHealth.collectAsState() + val lastRelayEvent by subscriptionsCoordinator.lastEventAt.collectAsState() SinglePaneLayout( relayManager = relayManager, localCache = localCache, @@ -753,7 +753,7 @@ fun MainContent( onZapFeedback = onZapFeedback, signerConnectionState = signerConnectionState, lastPingTimeSec = lastPingTimeSec, - subscriptionHealth = healthMap, + lastRelayEventAt = lastRelayEvent, modifier = Modifier.weight(1f), ) } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/BoundedLargeCache.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/BoundedLargeCache.kt index 46b3acc1c..4b71abe10 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/BoundedLargeCache.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/BoundedLargeCache.kt @@ -72,12 +72,8 @@ class BoundedLargeCache, V>( return removed } - fun containsKey(key: K): Boolean = inner.containsKey(key) - fun size(): Int = sizeCounter.get() - fun isEmpty(): Boolean = sizeCounter.get() == 0 - fun clear() { inner.clear() sizeCounter.set(0) @@ -89,10 +85,6 @@ class BoundedLargeCache, V>( fun filterIntoSet(consumer: CacheCollectors.BiFilter): Set = inner.filterIntoSet(consumer) - fun mapNotNull(consumer: CacheCollectors.BiMapper): List = inner.mapNotNull(consumer) - - fun forEach(consumer: java.util.function.BiConsumer) = inner.forEach(consumer) - fun count(consumer: CacheCollectors.BiFilter): Int = inner.count(consumer) private fun enforceSize() { diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt index a83a864cd..78d7f2e7e 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt @@ -82,25 +82,6 @@ class DesktopLocalCache : ICacheProvider { val paymentTracker = NwcPaymentTracker() - // ----- Kind-based event consumer registry ----- - - private val consumers = HashMap 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 ----- 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. - * O(1) dispatch. Returns true if the event was consumed (new), false if already seen. + * Routes an event to the appropriate consume method. + * Returns true if the event was consumed (new), false if already seen. */ fun consume( event: Event, 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. diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/DesktopRelaySubscriptionsCoordinator.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/DesktopRelaySubscriptionsCoordinator.kt index 70e5dceee..29121e4b1 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/DesktopRelaySubscriptionsCoordinator.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/DesktopRelaySubscriptionsCoordinator.kt @@ -40,7 +40,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch 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( private val client: INostrClient, private val scope: CoroutineScope, @@ -112,26 +106,9 @@ class DesktopRelaySubscriptionsCoordinator( // Screen-triggered subscription Jobs — keyed by subId for proper cancellation private val screenSubscriptions = ConcurrentHashMap() - // Subscription health tracking - private val _subscriptionHealth = MutableStateFlow>(emptyMap()) - val subscriptionHealth: StateFlow> = _subscriptionHealth.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, - ) - } - } - } + // Last event received from any subscription — drives RelayHealthIndicator + private val _lastEventAt = MutableStateFlow(null) + val lastEventAt: StateFlow = _lastEventAt.asStateFlow() /** * Central event router — consumes an event into the cache and emits to event stream. @@ -146,6 +123,7 @@ class DesktopRelaySubscriptionsCoordinator( try { val consumed = localCache.consume(event, relay) if (consumed) { + _lastEventAt.value = System.currentTimeMillis() val note = localCache.getNoteIfExists(event.id) ?: return@launch eventBundler.invalidateList(note) { batch -> localCache.eventStream.emitNewNotes(batch) @@ -202,14 +180,6 @@ class DesktopRelaySubscriptionsCoordinator( forFilters: List?, ) { consumeEvent(event, relay) - updateHealth(subId, lastEventReceivedAt = System.currentTimeMillis()) - } - - override fun onEose( - relay: NormalizedRelayUrl, - forFilters: List?, - ) { - updateHealth(subId, eoseReceived = true) } } @@ -232,7 +202,6 @@ class DesktopRelaySubscriptionsCoordinator( fun releaseInteractions(subId: String) { screenSubscriptions.remove(subId)?.cancel() client.close(subId) - _subscriptionHealth.update { it - subId } } /** @@ -276,13 +245,6 @@ class DesktopRelaySubscriptionsCoordinator( feedMetadata.loadMetadataForPubkeys(pubkeys) } - /** - * Load reactions for specific notes. - */ - fun loadReactionsForNotes(noteIds: List) { - feedMetadata.loadReactionsForNotes(noteIds) - } - // -- DM Subscription Support -- /** Active DM subscription IDs for cleanup */ @@ -389,7 +351,7 @@ class DesktopRelaySubscriptionsCoordinator( client.close(subId) } screenSubscriptions.clear() - _subscriptionHealth.value = emptyMap() + _lastEventAt.value = null unsubscribeFromDms() feedMetadata.clear() diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/SinglePaneLayout.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/SinglePaneLayout.kt index 489645088..63adf3f56 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/SinglePaneLayout.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/SinglePaneLayout.kt @@ -65,7 +65,6 @@ import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager import com.vitorpamplona.amethyst.desktop.service.highlights.DesktopHighlightStore 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.components.RelayHealthIndicator import com.vitorpamplona.amethyst.desktop.ui.media.LocalIsImmersiveFullscreen @@ -110,7 +109,7 @@ fun SinglePaneLayout( onZapFeedback: (ZapFeedback) -> Unit, signerConnectionState: SignerConnectionState, lastPingTimeSec: Long?, - subscriptionHealth: Map = emptyMap(), + lastRelayEventAt: Long? = null, modifier: Modifier = Modifier, ) { var currentColumnType by remember { mutableStateOf(DeckColumnType.HomeFeed) } @@ -154,12 +153,8 @@ fun SinglePaneLayout( Spacer(Modifier.weight(1f)) // Relay health — shows elapsed time since last event (hidden when <30s) - val latestEvent = - subscriptionHealth.values - .mapNotNull { it.lastEventReceivedAt } - .maxOrNull() RelayHealthIndicator( - lastEventReceivedAt = latestEvent, + lastEventReceivedAt = lastRelayEventAt, modifier = Modifier.padding(bottom = 4.dp), )