问题
I am building an iOS game. Each game room is constructed from two users. After two users get matched, I'm displaying a "waiting for respond" timer on both devices: they need to click am "I'm Ready" button within 8 second or both of them will get kicked from the room + deleted from Firebase.
The correct state of the users (before each of them clicked on the "I'm Ready Button"):
Parent
Matches
User 1
opponent : User2
state : "NotReady"
User 2
opponent : User1
state : "NotReady"
Critical note - The timer on both devices time difference is +-2 seconds, In other words - once device timer is going to end before the other one
When a user press the "I'm Ready" button i'm updating the state : "userReady"
,and check if the other user is ready too (observing its state
value).
If both users are userReady - game on.
PROBLEM
so we have already cleared that in 100% of the cases i have a small time difference between both devices. BUT, if for instance ,
User1
clicked I'm Ready
button. So now User2
got an ChildUpdate
event, and as far as he knows - User2
is completely ready to play.
User1 timer
is ending first(fact), so when his timer will finish, User2 timer
will remain 1
seconds. NOW, on User1
time just reached zero, so he get kicked out of the room, and send a removeValue
event on each of the Users nodes. While this is happening, at this very small "gap",(between the zero time of User1 timer
ending,User2
clock show's 1 sec(fact) - and he press the ready button. than he thinks that User1
is ready to play, and he as well - than the game starts playing.
Final Results -
Both players got deleted from Firebase
User1 is out of the room
User2 starts the game(and he think he is an opponent)
How can i solve this end-case scenario?, I have already tried calling startGame
function only when the "UpdateChild state" is done, but it still gets in, maybe because the order for updateChild,and removeValue"?
Any suggestions? And BIG thank you Firebase team for reaching how all the time!!!
回答1:
It doesn't make sense that User 1 would accept and then expire anyway. User 2 should be the one expiring if the time limit is reached and he hasn't accepted yet.
To prevent this, you're looking for transactions. When you would "expire" a user, use a transaction to do so, so that there are no data conflicts.
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
ref.child('Parent/Matches').child(user1).transaction(function(currentValue) {
if( currentValue && currentValue.state === 'NotReady' ) {
return null; // delete the user
}
else {
return undefined; // abort the transaction; status changed while we were attempting to remove it
}
});
Possibly correct in swift:
var ref = Firebase(url: "https://<YOUR-FIREBASE-APP>.firebaseio.com/Parent/Matches/<user id>")
upvotesRef.runTransactionBlock({
(currentData:FMutableData!) in
if currentData && currentData.value["state"] != "NotReady" {
return FTransactionResult.successWithValue(NSNull)
}
return FTransactionResult.abort();
});
Furthermore, you could simplify things and reduce the opportunity for chaos by not deleting/expiring the records until both users have reached their accept time limit, and then doing both at once in a transaction.
来源:https://stackoverflow.com/questions/34778914/data-consistency-on-very-close-events-in-firebase