perf(communities): stop rebuilding the card on every reaction/zap
RenderCommunitiesThumb was observing the full NoteState, which
recomposes on every reaction, zap, boost or metadata tick. Each
recomposition rebuilt the CommunityCard, re-parsing the definition
tags and calling moderatorKeys().toImmutableList() — producing a
brand-new ImmutableList instance. That new list identity then
invalidated LaunchedEffect(key = moderators) in LoadModerators,
re-running the expensive ParticipantListBuilder traversal (author +
all replies + boosts + zaps + reactions + public-chat notes) per
tick.
Fixes:
- Switch to observeNoteEvent<CommunityDefinitionEvent> so the
thumb only recomposes when the definition event itself changes
(including edits). Reactions/zaps are handled separately inside
LikeReaction/ZapReaction.
- remember(event.id) { CommunityCard(...) } so the card and its
moderator list identity are allocated once per event version.
- Stabilize LoadModerators' LaunchedEffect with (baseNote.idHex,
moderators). Convert the hosts list to a Set for O(1) filtering,
reuse a single ParticipantListBuilder instance, and skip the
redundant .minus(hosts) call on the new set.
This commit is contained in:
+30
-15
@@ -54,7 +54,7 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByPr
|
|||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
|
||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
|
||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
|
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
|
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
|
||||||
@@ -91,16 +91,26 @@ fun RenderCommunitiesThumb(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val noteState by observeNote(baseNote, accountViewModel)
|
// Narrow observation: we only care about the CommunityDefinitionEvent itself,
|
||||||
val noteEvent = noteState.note.event as? CommunityDefinitionEvent ?: return
|
// not every reaction/zap/boost that would otherwise recompose the whole card.
|
||||||
|
val definition by observeNoteEvent<CommunityDefinitionEvent>(baseNote, accountViewModel)
|
||||||
|
val event = definition ?: return
|
||||||
|
|
||||||
|
// Memoize card + moderator list identity so LaunchedEffect(moderators) in
|
||||||
|
// LoadModerators doesn't retrigger the ParticipantListBuilder traversal on
|
||||||
|
// every recomposition.
|
||||||
|
val card =
|
||||||
|
remember(event.id) {
|
||||||
|
CommunityCard(
|
||||||
|
name = event.name()?.ifBlank { null } ?: event.dTag(),
|
||||||
|
description = event.description(),
|
||||||
|
cover = event.image()?.imageUrl,
|
||||||
|
moderators = event.moderatorKeys().toImmutableList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
RenderCommunitiesThumb(
|
RenderCommunitiesThumb(
|
||||||
CommunityCard(
|
card,
|
||||||
name = noteEvent.name()?.ifBlank { null } ?: noteEvent.dTag(),
|
|
||||||
description = noteEvent.description(),
|
|
||||||
cover = noteEvent.image()?.imageUrl,
|
|
||||||
moderators = noteEvent.moderatorKeys().toImmutableList(),
|
|
||||||
),
|
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
@@ -193,16 +203,21 @@ fun LoadModerators(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = moderators) {
|
// Keying by the note id + moderator identity keeps this effect from
|
||||||
|
// restarting on every recomposition (reaction/zap updates would otherwise
|
||||||
|
// reallocate `moderators` and rerun the expensive traversal below).
|
||||||
|
LaunchedEffect(key1 = baseNote.idHex, key2 = moderators) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
|
val authorHex = baseNote.author?.pubkeyHex
|
||||||
val hosts =
|
val hosts =
|
||||||
moderators.mapNotNull { part ->
|
moderators.mapNotNull { part ->
|
||||||
if (part != baseNote.author?.pubkeyHex) {
|
if (part != authorHex) {
|
||||||
LocalCache.checkGetOrCreateUser(part)
|
LocalCache.checkGetOrCreateUser(part)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val hostSet = hosts.toSet()
|
||||||
|
|
||||||
val topFilter = accountViewModel.account.liveDiscoveryFollowLists.value
|
val topFilter = accountViewModel.account.liveDiscoveryFollowLists.value
|
||||||
val discoveryTopFilterAuthors =
|
val discoveryTopFilterAuthors =
|
||||||
@@ -217,15 +232,15 @@ fun LoadModerators(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
val followingKeySet = discoveryTopFilterAuthors
|
val builder = ParticipantListBuilder()
|
||||||
val allParticipants =
|
val allParticipants =
|
||||||
ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts)
|
builder.followsThatParticipateOn(baseNote, discoveryTopFilterAuthors) - hostSet
|
||||||
|
|
||||||
val newParticipantUsers =
|
val newParticipantUsers =
|
||||||
if (followingKeySet == null) {
|
if (discoveryTopFilterAuthors == null) {
|
||||||
val allFollows = accountViewModel.account.kind3FollowList.flow.value.authors
|
val allFollows = accountViewModel.account.kind3FollowList.flow.value.authors
|
||||||
val followingParticipants =
|
val followingParticipants =
|
||||||
ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts)
|
builder.followsThatParticipateOn(baseNote, allFollows) - hostSet
|
||||||
|
|
||||||
(hosts + followingParticipants + (allParticipants - followingParticipants))
|
(hosts + followingParticipants + (allParticipants - followingParticipants))
|
||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
|
|||||||
Reference in New Issue
Block a user