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 87128a75b..d1b9636a4 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 @@ -85,8 +85,18 @@ class DesktopLocalCache : ICacheProvider { companion object { } + /** Index of notes by author pubkey — for fast metadata invalidation */ + private val notesByAuthor = ConcurrentHashMap>() + val paymentTracker = NwcPaymentTracker() + private fun trackNoteAuthor( + note: Note, + authorPubkey: HexKey, + ) { + notesByAuthor.getOrPut(authorPubkey) { ConcurrentHashMap.newKeySet() }.add(note) + } + // ----- User operations ----- override fun getUserIfExists(pubkey: HexKey): User? = users.get(pubkey) @@ -156,10 +166,9 @@ class DesktopLocalCache : ICacheProvider { if (newUserMetadata != null) { user.updateUserInfo(newUserMetadata, event) _metadataVersion.value++ - // Invalidate metadata flows on notes by this author that have observers - // so QuotedNoteEmbed/FeedNoteCard recompose with updated avatar/name - notes.forEach { _, note -> - if (note.author?.pubkeyHex == event.pubKey && note.flowSet?.metadata?.hasObservers() == true) { + // Invalidate metadata flows on notes by this author (O(K) via index) + notesByAuthor[event.pubKey]?.forEach { note -> + if (note.flowSet?.metadata?.hasObservers() == true) { note.flowSet?.metadata?.invalidateData() } } @@ -245,6 +254,7 @@ class DesktopLocalCache : ICacheProvider { val author = getOrCreateUser(event.pubKey) val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) } note.loadEvent(event, author, repliesTo) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } repliesTo.forEach { it.addReply(note) } return true @@ -263,6 +273,7 @@ class DesktopLocalCache : ICacheProvider { val author = getOrCreateUser(event.pubKey) val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) } note.loadEvent(event, author, repliesTo) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } repliesTo.forEach { it.addReply(note) } return true @@ -283,6 +294,7 @@ class DesktopLocalCache : ICacheProvider { event.originalPost().mapNotNull { getNoteIfExists(it) } + event.taggedAddresses().mapNotNull { addressableNotes.get(it.toValue()) } note.loadEvent(event, author, reactedTo) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } reactedTo.forEach { it.addReaction(note) } return true @@ -300,6 +312,7 @@ class DesktopLocalCache : ICacheProvider { if (note.event != null) return false val author = getOrCreateUser(event.pubKey) note.loadEvent(event, author, emptyList()) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } return true } @@ -331,6 +344,7 @@ class DesktopLocalCache : ICacheProvider { event.taggedAddresses().mapNotNull { addressableNotes.get(it.toValue()) } note.loadEvent(event, author, zappedNotes) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } // Link zap to target notes @@ -358,6 +372,7 @@ class DesktopLocalCache : ICacheProvider { val boostedNote = boostedId?.let { getOrCreateNote(it) } val repliesTo = listOfNotNull(boostedNote) note.loadEvent(event, author, repliesTo) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } boostedNote?.addBoost(note) return true @@ -377,6 +392,7 @@ class DesktopLocalCache : ICacheProvider { val boostedNote = event.boostedEventId()?.let { getOrCreateNote(it) } val repliesTo = listOfNotNull(boostedNote) note.loadEvent(event, author, repliesTo) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } boostedNote?.addBoost(note) return true @@ -408,6 +424,7 @@ class DesktopLocalCache : ICacheProvider { if (note.event != null) return false val author = getOrCreateUser(event.pubKey) note.loadEvent(event, author, emptyList()) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } return true } @@ -467,6 +484,7 @@ class DesktopLocalCache : ICacheProvider { if (note.event != null) return false note.loadEvent(event, author, emptyList()) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } zappedNote?.addZapPayment(note, null) @@ -498,6 +516,7 @@ class DesktopLocalCache : ICacheProvider { if (note.event != null) return false note.loadEvent(event, author, emptyList()) + trackNoteAuthor(note, event.pubKey) relay?.let { note.addRelay(it) } // Link response to zapped note via request @@ -617,6 +636,7 @@ class DesktopLocalCache : ICacheProvider { _followedUsers.value = emptySet() followerCounts.clear() followingCounts.clear() + notesByAuthor.clear() lastContactListCreatedAt = 0L } }