crossmate

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

commit ab88c0554ac31b8a6d8606428f62c21d2b222b16
parent 6fb82447f6f854d642d3d4d409e40f2fdd125f99
Author: Michael Camilleri <[email protected]>
Date:   Fri, 29 May 2026 16:42:59 +0900

Add remote-only active-puzzle debounce

Diffstat:
MCrossmate/Services/AppServices.swift | 36+++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/Crossmate/Services/AppServices.swift b/Crossmate/Services/AppServices.swift @@ -139,6 +139,8 @@ final class AppServices { /// arrived after the last successful freshen. private let gameListFreshenCooldown: TimeInterval = 300 private var fresheningPuzzleGridKeys: Set<String> = [] + private var lastRemotePuzzleGridFreshenAt: [String: Date] = [:] + private let remotePuzzleGridFreshenDebounce: TimeInterval = 5 private var isGameListVisible = false private var latestLocalSelections: [UUID: PlayerSelection] = [:] private var scheduledEngagementEndTasks: [UUID: Task<Void, Never>] = [:] @@ -1363,10 +1365,23 @@ final class AppServices { await movesUpdater.flush() guard await ensureICloudSyncStarted() else { return } let label = reason.diagnosticLabel + if reason == .remote, + shouldSkipRecentRemotePuzzleGridFreshen( + gameID: gameID, + scope: scope, + label: label + ) { + return + } guard beginPuzzleGridFreshen(gameID: gameID, scope: scope, reason: label) else { return } - defer { endPuzzleGridFreshen(gameID: gameID, scope: scope) } + defer { + endPuzzleGridFreshen(gameID: gameID, scope: scope) + if reason == .remote { + noteRemotePuzzleGridFreshenCompleted(gameID: gameID, scope: scope) + } + } await syncMonitor.run("freshen puzzle grid \(label)") { let handled = try await syncEngine.fetchGameDirect( @@ -1400,6 +1415,25 @@ final class AppServices { fresheningPuzzleGridKeys.remove(puzzleGridFreshenKey(gameID: gameID, scope: scope)) } + private func shouldSkipRecentRemotePuzzleGridFreshen( + gameID: UUID, + scope: CKDatabase.Scope, + label: String + ) -> Bool { + let key = puzzleGridFreshenKey(gameID: gameID, scope: scope) + guard let last = lastRemotePuzzleGridFreshenAt[key] else { return false } + let elapsed = Date().timeIntervalSince(last) + guard elapsed < remotePuzzleGridFreshenDebounce else { return false } + syncMonitor.note( + "freshen puzzle grid \(label): \(scopeLabel(scope)) \(gameID.uuidString.prefix(8)) skipped (recent remote refresh \(Int(elapsed.rounded()))s ago)" + ) + return true + } + + private func noteRemotePuzzleGridFreshenCompleted(gameID: UUID, scope: CKDatabase.Scope) { + lastRemotePuzzleGridFreshenAt[puzzleGridFreshenKey(gameID: gameID, scope: scope)] = Date() + } + private func puzzleGridFreshenKey(gameID: UUID, scope: CKDatabase.Scope) -> String { "\(scopeLabel(scope)):\(gameID.uuidString)" }