From f6ffb87e20c4bfc508cb2992a888d8c7d602c3c7 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 5 Mar 2024 16:09:50 -0500 Subject: [PATCH] Avoids allocating Strings for the JSON conversion when checking if a filter has changed. --- .../amethyst/service/NostrDataSource.kt | 6 +- .../amethyst/service/relays/Subscription.kt | 43 +++++++----- .../amethyst/service/relays/TypedFilter.kt | 68 +------------------ .../amethyst/ui/screen/FeedView.kt | 3 +- 4 files changed, 32 insertions(+), 88 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 47cb0af5d..0a1623e01 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -221,7 +221,7 @@ abstract class NostrDataSource(val debugName: String) { // saves the channels that are currently active val activeSubscriptions = subscriptions.values.filter { it.typedFilters != null } // saves the current content to only update if it changes - val currentFilters = activeSubscriptions.associate { it.id to it.toJson() } + val currentFilters = activeSubscriptions.associate { it.id to it.typedFilters } changingFilters.getAndSet(true) @@ -245,7 +245,7 @@ abstract class NostrDataSource(val debugName: String) { Client.close(updatedSubscription.id) } else { // was active and is still active, check if it has changed. - if (updatedSubscription.toJson() != currentFilters[updatedSubscription.id]) { + if (updatedSubscription.hasChangedFiltersFrom(currentFilters[updatedSubscription.id])) { Client.close(updatedSubscription.id) if (active) { Client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) @@ -265,7 +265,7 @@ abstract class NostrDataSource(val debugName: String) { // was not active and is still not active, does nothing } else { // was not active and becomes active, sends the filter. - if (updatedSubscription.toJson() != currentFilters[updatedSubscription.id]) { + if (updatedSubscription.hasChangedFiltersFrom(currentFilters[updatedSubscription.id])) { if (active) { Log.d( this@NostrDataSource.javaClass.simpleName, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Subscription.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Subscription.kt index 9de8de758..ebd0ce388 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Subscription.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Subscription.kt @@ -20,8 +20,6 @@ */ package com.vitorpamplona.amethyst.service.relays -import com.fasterxml.jackson.databind.JsonNode -import com.vitorpamplona.quartz.events.Event import java.util.UUID data class Subscription( @@ -37,23 +35,36 @@ data class Subscription( onEOSE?.let { it(time, relay) } } - fun toJson(): String { - return Event.mapper.writeValueAsString(toJsonObject()) - } + fun hasChangedFiltersFrom(otherFilters: List?): Boolean { + if (typedFilters == null && otherFilters == null) return false + if (typedFilters?.size != otherFilters?.size) return true - fun toJsonObject(): JsonNode { - val factory = Event.mapper.nodeFactory + typedFilters?.forEachIndexed { index, typedFilter -> + val otherFilter = otherFilters?.getOrNull(index) ?: return true - return factory.objectNode().apply { - put("id", id) - typedFilters?.also { filters -> - replace( - "typedFilters", - factory.arrayNode(filters.size).apply { - filters.forEach { filter -> add(filter.toJsonObject()) } - }, - ) + // Does not check SINCE on purpose. Avoids replacing the filter if SINCE was all that changed. + // fast check + if (typedFilter.filter.authors?.size != otherFilter.filter.authors?.size || + typedFilter.filter.ids?.size != otherFilter.filter.ids?.size || + typedFilter.filter.tags?.size != otherFilter.filter.tags?.size || + typedFilter.filter.kinds?.size != otherFilter.filter.kinds?.size || + typedFilter.filter.limit != otherFilter.filter.limit || + typedFilter.filter.search?.length != otherFilter.filter.search?.length || + typedFilter.filter.until != otherFilter.filter.until + ) { + return true + } + + // deep check + if (typedFilter.filter.ids != otherFilter.filter.ids || + typedFilter.filter.authors != otherFilter.filter.authors || + typedFilter.filter.tags != otherFilter.filter.tags || + typedFilter.filter.kinds != otherFilter.filter.kinds || + typedFilter.filter.search != otherFilter.filter.search + ) { + return true } } + return false } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt index ec3c9e05c..4817e7b30 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt @@ -20,73 +20,7 @@ */ package com.vitorpamplona.amethyst.service.relays -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.ArrayNode -import com.vitorpamplona.quartz.events.Event - class TypedFilter( val types: Set, val filter: JsonFilter, -) { - fun toJson(): String { - return Event.mapper.writeValueAsString(toJsonObject()) - } - - fun toJsonObject(): JsonNode { - val factory = Event.mapper.nodeFactory - - return factory.objectNode().apply { - replace("types", typesToJson(types)) - replace("filter", filterToJson(filter)) - } - } - - fun typesToJson(types: Set): ArrayNode { - val factory = Event.mapper.nodeFactory - return factory.arrayNode(types.size).apply { types.forEach { add(it.name.lowercase()) } } - } - - fun filterToJson(filter: JsonFilter): JsonNode { - val factory = Event.mapper.nodeFactory - return factory.objectNode().apply { - filter.ids?.run { - replace( - "ids", - factory.arrayNode(filter.ids.size).apply { filter.ids.forEach { add(it) } }, - ) - } - filter.authors?.run { - replace( - "authors", - factory.arrayNode(filter.authors.size).apply { filter.authors.forEach { add(it) } }, - ) - } - filter.kinds?.run { - replace( - "kinds", - factory.arrayNode(filter.kinds.size).apply { filter.kinds.forEach { add(it) } }, - ) - } - filter.tags?.run { - entries.forEach { kv -> - replace( - "#${kv.key}", - factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } }, - ) - } - } - /* - Does not include since in the json comparison - filter.since?.run { - val jsonObjectSince = JsonObject() - entries.forEach { sincePairs -> - jsonObjectSince.addProperty(sincePairs.key, "${sincePairs.value}") - } - jsonObject.add("since", jsonObjectSince) - }*/ - filter.until?.run { put("until", filter.until) } - filter.limit?.run { put("limit", filter.limit) } - filter.search?.run { put("search", filter.search) } - } - } -} +) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 920977642..72e389c3a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -58,7 +58,6 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer -import kotlin.time.ExperimentalTime @Composable fun RefresheableFeedView( @@ -205,7 +204,7 @@ private fun WatchScrollToTop( } } -@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class) +@OptIn(ExperimentalFoundationApi::class) @Composable private fun FeedLoaded( state: FeedState.Loaded,