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:
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) {