From 50bc55e84bae8938a34867a83252ee323eb62562 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 9 Feb 2023 17:52:18 -0500 Subject: [PATCH] More Performant UpdateFollows --- .../com/vitorpamplona/amethyst/model/Note.kt | 13 ++-- .../com/vitorpamplona/amethyst/model/User.kt | 72 ++++++++++++++----- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 0494b7719..43e5579eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import com.vitorpamplona.amethyst.service.relays.Relay import java.util.Date +import java.util.concurrent.atomic.AtomicBoolean import java.util.regex.Matcher import java.util.regex.Pattern import nostr.postr.events.Event @@ -194,17 +195,19 @@ class Note(val idHex: String) { class NoteLiveData(val note: Note): LiveData(NoteState(note)) { // Refreshes observers in batches. - var handlerWaiting = false + var handlerWaiting = AtomicBoolean() + @Synchronized fun invalidateData() { - if (handlerWaiting) return + if (!hasActiveObservers()) return + if (handlerWaiting.getAndSet(true)) return - handlerWaiting = true - val scope = CoroutineScope(Job() + Dispatchers.Default) + handlerWaiting.set(true) + val scope = CoroutineScope(Job() + Dispatchers.Main) scope.launch { delay(100) refresh() - handlerWaiting = false + handlerWaiting.set(false) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 9faf41648..bb5fc3e9a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -10,6 +10,9 @@ import com.vitorpamplona.amethyst.ui.note.toShortenHex import fr.acinq.secp256k1.Hex import java.util.Collections import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -81,7 +84,11 @@ class User(val pubkeyHex: String) { liveFollows.invalidateData() user.liveFollows.invalidateData() - listeners.forEach { + updateSubscribers { + it.onFollowsChange() + } + + user.updateSubscribers { it.onFollowsChange() } } @@ -95,6 +102,40 @@ class User(val pubkeyHex: String) { updateSubscribers { it.onFollowsChange() } + + user.updateSubscribers { + it.onFollowsChange() + } + } + + fun follow(users: Set, followedAt: Long) { + follows = follows + users + users.forEach { + it.followers = it.followers + this + it.liveFollows.invalidateData() + it.updateSubscribers { + it.onFollowsChange() + } + } + + liveFollows.invalidateData() + updateSubscribers { + it.onFollowsChange() + } + } + fun unfollow(users: Set) { + follows = follows - users + users.forEach { + it.followers = it.followers - this + it.liveFollows.invalidateData() + it.updateSubscribers { + it.onFollowsChange() + } + } + liveFollows.invalidateData() + updateSubscribers { + it.onFollowsChange() + } } fun addTaggedPost(note: Note) { @@ -164,17 +205,13 @@ class User(val pubkeyHex: String) { updateSubscribers { it.onNewRelayInfo() } liveRelayInfo.invalidateData() } - + fun updateFollows(newFollows: Set, updateAt: Long) { val toBeAdded = newFollows - follows val toBeRemoved = follows - newFollows - toBeAdded.forEach { - follow(it, updateAt) - } - toBeRemoved.forEach { - unfollow(it) - } + follow(toBeAdded, updateAt) + unfollow(toBeRemoved) updatedFollowsAt = updateAt } @@ -239,19 +276,20 @@ class User(val pubkeyHex: String) { } // Refreshes observers in batches. - var modelHandlerWaiting = false + var modelHandlerWaiting = AtomicBoolean() + @Synchronized fun updateSubscribers(on: (Listener) -> Unit) { - if (modelHandlerWaiting) return + if (modelHandlerWaiting.getAndSet(true)) return - modelHandlerWaiting = true + modelHandlerWaiting.set(true) val scope = CoroutineScope(Job() + Dispatchers.Default) scope.launch { delay(100) listeners.forEach { on(it) } - modelHandlerWaiting = false + modelHandlerWaiting.set(false) } } @@ -292,17 +330,19 @@ class UserMetadata { class UserLiveData(val user: User): LiveData(UserState(user)) { // Refreshes observers in batches. - var handlerWaiting = false + var handlerWaiting = AtomicBoolean() + @Synchronized fun invalidateData() { - if (handlerWaiting) return + if (!hasActiveObservers()) return + if (handlerWaiting.getAndSet(true)) return - handlerWaiting = true + handlerWaiting.set(true) val scope = CoroutineScope(Job() + Dispatchers.Main) scope.launch { delay(100) refresh() - handlerWaiting = false + handlerWaiting.set(false) } }