I faced with a very old problem that has been discussed here many times. Despite talking the problem much time I found no acceptable solution so I decided to raise the issue once again.
So, the problem. I'm trying to test a turn-based match. I use two real devices for this. I make a turn on the first device, and match data is updated with no error (I know it for sure), but sometimes the second device doesn't receive any notification and it seems that the first device still in a turn. Sometimes it works as expected.
In another words player(_:receivedTurnEventFor:didBecomeActive)
method isn't called sometimes. But if I close the app on the second device, reopen it and join existing match everything works fine.
As I understood this is very well known Game Center Sandbox issue, but it makes me crazy while I'm trying to test the app.
Does anybody know the way how to workaround? Or maybe there is a best practice how to live and test apps with this strange sandbox behavior?
UPDATE.
The method proposed by Thunk is a solution. I rewrote it in Swift and modified to be in line with my game logic.
FIrst, I defined global variable
var gcBugTimer: Timer
In endTurn(withNextParticipants:turnTimeOut:match:completionHandler:)
completion handler:
let interval = 3.0
self.gcBugTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(self.isMatchActive), userInfo: nil, repeats: true)
self.gcBugTimer.tolerance = 1.0
Code above also should be called in case when a player is joying to a new match and other player in a turn.
Then timer method:
func isMatchActive() {
// currentMatch - global variable contains information about current match
GKTurnBasedMatch.load(withID: currentMatch.matchID!) { (match, error) in
if match != nil {
let participant = match?.currentParticipant
let localPlayer = GKLocalPlayer.localPlayer()
if localPlayer.playerID == participant?.player?.playerID {
self.player(localPlayer, receivedTurnEventFor: match!, didBecomeActive: false)
}
} else {
print(error?.localizedDescription ?? "")
}
}
}
And I add following code at the very beginning of player(_:receivedTurnEventFor:didBecomeActive)
:
if gcBugTimer != nil && gcBugTimer.isValid {
gcBugTimer.invalidate()
}
The only solution I found that worked reliably was to manually recheck my status while waiting for my turn. In the completion handler for endTurnWithNextParticipants
I set a timer to continuously reload the match data. I checked if localPlayer
had become the active player. If so, then I called receivedTurnForEvent
myself, otherwise, I repeated the timer. Like so:
In the endTurnWithNextParticipants
completion handler:
float dTime = 60.0; //messages sometimes fail in IOS8.4
if (SYSTEM_VERSION_EQUAL_TO(@"8.3") )
{
dTime = 5.0; //messages always fail in IOS8.3
}
IOS8BugTimer = [NSTimer scheduledTimerWithTimeInterval:dTime
target:gameKitHelper
selector:@selector(isMatchActive:)
userInfo:theMatch.matchID
repeats:NO];
and in gameKitHelper:isMatchActive:
-(void)isMatchActive:(NSTimer *)timer
{
NSString *matchID = (NSString *)timer.userInfo;
[GKTurnBasedMatch loadMatchWithID:matchID withCompletionHandler:^(GKTurnBasedMatch *match, NSError *error)
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
GKTurnBasedParticipant *currentParticipant = match.currentParticipant;
if ([localPlayer.playerID isEqualToString:currentParticipant.player.playerID])
{
//we have become active. Call the event handler like it's supposed to be called
[self player:localPlayer receivedTurnEventForMatch:match didBecomeActive:false];
}
else
{
//we are still waiting to become active. Check back soon
float dTime = 60.0;
if (SYSTEM_VERSION_EQUAL_TO(@"8.3") )
{
dTime = 5.0;
}
gameController.IOS8BugTimer = [NSTimer scheduledTimerWithTimeInterval:dTime
target:self
selector:@selector(isMatchActive:)
userInfo:matchID
repeats:NO];
}
}];
}