crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 6fb82447f6f854d642d3d4d409e40f2fdd125f99
parent 6890a9ffc46dea73d83a204454285b81d144d8a6
Author: Michael Camilleri <[email protected]>
Date:   Fri, 29 May 2026 16:19:47 +0900

Coalesce overlapping game-list refreshes

Diffstat:
MCrossmate/Services/AppServices.swift | 25+++++++++++++++++++++++++
1 file changed, 25 insertions(+), 0 deletions(-)

diff --git a/Crossmate/Services/AppServices.swift b/Crossmate/Services/AppServices.swift @@ -121,6 +121,7 @@ final class AppServices { private var sharedPushCatchUpTask: Task<Void, Never>? private var isHandlingPrivateRemoteNotification = false private var isHandlingSharedRemoteNotification = false + private var gameListFreshenTask: Task<Void, Never>? private var isFresheningPrivateGameList = false private var isFresheningSharedGameList = false private var claimedPingRecordNames: Set<String> = [] @@ -1167,6 +1168,23 @@ final class AppServices { func freshenGameList(reason: FreshenReason) async { guard await ensureICloudSyncStarted() else { return } + if let task = gameListFreshenTask { + syncMonitor.note( + "freshen game list \(reason.diagnosticLabel): coalesced into in-flight freshen" + ) + await task.value + return + } + + let task = Task { @MainActor in + await self.runFreshenGameList(reason: reason) + } + gameListFreshenTask = task + await task.value + gameListFreshenTask = nil + } + + private func runFreshenGameList(reason: FreshenReason) async { // The game list is a foreground-visible freshness path, not the live // collaboration path. Keep the two database scopes serialized so list // appearance and foreground transitions do not create a read burst. @@ -1199,6 +1217,13 @@ final class AppServices { return } guard await ensureICloudSyncStarted() else { return } + if let task = gameListFreshenTask { + syncMonitor.note( + "freshen game list \(reason.diagnosticLabel): \(label) coalesced into in-flight freshen" + ) + await task.value + return + } await freshenGameListScope(scope, label: label, reason: reason) await refreshSnapshot() }