GKTurnBasedMatch saveCurrentTurnWithMatchData returning an error on every other call

30 Views Asked by At

The player takes multiple actions before completing a turn. After each action, I call saveCurrentTurnWIthMatchData, with the match data updated.

[gameMatch saveCurrentTurnWithMatchData: matchData completionHandler: ^(NSError *error){
             if (error) {
                 NSLog(@"Error updating match = %@",error);
             }
     }];

On every other call I get "Error Domain=GKServerErrorDomain Code=5002 "status = 5002, Unexpected game state version expectedGameStateVersion='null'"

The GKTurnBasedMatch.state = 3 (GKTurnBasedMatchStatusMatching) in every call. I'm not changing this, I just check before the call. I have no idea if this is relevant.

Any suggestion what to try?

1

There are 1 best solutions below

1
Fault On

the "Unexpected game state version" error happens irregularly and is hard to reproduce -- although i can often reproduce it by calling saveCurrentTurn several times in rapid succession. it would be useful to have clarity from Apple on this since it appears to be server side (but i'm not sure). i wrote a unit test that does stress testing on GKTurnBasedMatch.saveCurrentTurn. it fails irregularly but often up to 20% of the time.

i have no full solution only a partial one. to partially mitigate the problem, you can wrap your saveCurrentTurn calls in a task queue, that way they wait for the previous one to finish. not a solution, but helps.

let dqt:DispatchQueueTask = {
    gkTurnBasedMatch.saveCurrentTurn(withMatch:payload) { error in
        //handle error
        TaskQueue.completion() //step to next task
    }
}
TaskQueue.add(task:dqt)

and here is the TaskQueue class i use

import Foundation

/*
 Uses the DispatchQueue to execute network commands in series
 useful for server commands like GKTurnBasedMatch.saveCurrentTurn(...)

 Usage:
     let doSomethingThatTakesTime:DispatchQueueTask = {
         ...
         TaskQueue.completion()
     }
 
     TaskQueue.add(task: doSomethingThatTakesTime)
 */

typealias DispatchQueueTask = () -> ()
let DispatchQueue_serial = DispatchQueue(label: "org.my.queue.serial")

class TaskQueue {
    static var isRunning:Bool = false
    static var tasks:[DispatchQueueTask] = []
    
    static func add(task:@escaping DispatchQueueTask) {
        tasks.append(task)
        run()
    }
        
    static func run() {
        guard !isRunning else { return }
        guard tasks.count > 0 else { return }
        let task = tasks.removeFirst()
        DispatchQueue_serial.async {
            TaskQueue.isRunning = true
            task()
        }
    }

    static func completion() {
        TaskQueue.isRunning = false
        TaskQueue.run()
    }
}