crossmate

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

commit cae1b44919c366db6a3cf9e9336f35431aa14909
parent 3b48d55d61110ac1e82d95a5a9e06c5234d5358f
Author: Michael Camilleri <[email protected]>
Date:   Wed, 13 May 2026 05:31:21 +0900

Skip the cell-cache rewrite when refreshing the current game

When a remote Moves record lands, the SyncEngine inbound path already runs
replayCellCache(for:in:) on its background context and saves the CellEntity
rows atomically with the inbound MovesEntity. The main context auto-merges
those changes via automaticallyMergesChangesFromParent, so by the time
refreshCurrentGame's downstream restore() reaches its updateCellCache step, the
CellEntity rows it would write are already the values it would write — same
merge, same input, same output.

Core Data's snapshot tracking elides the no-op save, but restore() still pays
for a full grid walk plus the save check on the main thread on every remote
keystroke during a co-solve. That cost is small but unnecessary. restore() now
takes an updateCache flag (default true so loadGame and the sample-seed path
are unchanged); refreshCurrentGame passes false. The in-memory game.squares
update and recomputeCompletionCache still run — those are what drives the UI
redraw — but the redundant main-context cell-cache pass is gone.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

Diffstat:
MCrossmate/Persistence/GameStore.swift | 13++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/Crossmate/Persistence/GameStore.swift b/Crossmate/Persistence/GameStore.swift @@ -256,7 +256,12 @@ final class GameStore { /// been written into Core Data by the sync engine. func refreshCurrentGame() { guard let game = currentGame, let entity = currentEntity else { return } - restore(game: game, from: entity) + // On this path the SyncEngine's inbound fetch has already replayed + // the CellEntity cache atomically with the inbound MovesEntity + // (see SyncEngine.replayCellCache); rewriting it here would do the + // same work against the main context, so we skip it to keep the + // main thread free during co-solve bursts. + restore(game: game, from: entity, updateCache: false) } /// Merges every device's `MovesEntity` rows for each game ID and updates @@ -615,7 +620,7 @@ final class GameStore { return Puzzle(xd: try XD.parse(source)) } - private func restore(game: Game, from entity: GameEntity) { + private func restore(game: Game, from entity: GameEntity, updateCache: Bool = true) { let movesRequest = NSFetchRequest<MovesEntity>(entityName: "MovesEntity") movesRequest.predicate = NSPredicate(format: "game == %@", entity) let movesEntities = (try? context.fetch(movesRequest)) ?? [] @@ -632,7 +637,9 @@ final class GameStore { } game.recomputeCompletionCache() - updateCellCache(for: entity, from: grid) + if updateCache { + updateCellCache(for: entity, from: grid) + } } private func updateCellCache(for gameEntity: GameEntity, from grid: GridState) {