Sunday, May 9, 2010

Oh hey, detecting and updating user presence is ridiculous. ( = pre-submission goals: update #2)

An annoying behind-the-scenes bug turned out, as I've learned after many hours of struggle today, to be far more frustrating and complicated than I could have imagined.

In my Users table in the database, I simply store a boolean, 'logged_in', by which I intend to record which players are currently using the app.  This does not mean whether they are logged into Facebook, since that status is likely unrelated to my app.  So, I define my logged_in boolean to mean whether the user is on either the index (home) page or the playgame.php page.

It's simple enough to ensure this boolean is marked true when the user loads either of these pages.  Detecting their leaving a page, however -- with the added requirements of updating all contingent information -- is unbelievably far more difficult.

Concerning the latter note, if a user leaves a game while it's in progress...
  • They should be marked 'logged_in' = false, since if they're navigating to one of the app pages, that page will restore that boolean to true.
  • Their 'current_game_id' and 'current_pid' variables must be set to null, to indicate they are no longer in that game.
  • If they were the last remaining player in that game, all turns pertaining to that game as well as the game's entry itself must be cleared from the Turns and Games tables, respectively.
  • If there are any other players left in that game, the 'num_players' value for that game must be set to the new correctly decremented value (and I don't want to just subtract 1, in case of race conditions, so I always want to check with how many players are still marked as involved in the game based on 'current_game_id' in the Users table).
  • The other players in the game should be checking for whether their fellow players have left, and must be informed of such dropouts.
 My original solution to detecting a player leaving the game page was to register the window.onunload event to make an AJAX POST to a php script that would update all appropriate things (see the list above).  This worked well enough in Firefox; I checked the results in the database, and Firebug showed me that the POST was indeed going through, despite the fact that the browser was navigating to another page.  However, I ended up discovering that the window.onunload event, or perhaps the POST, wasn't going through fully in Safari.  This led me to realize I should try an entirely different approach that would not rely on a browser event that may not have solid implementation in all browsers...

Many thanks are due here to John for helping me form new ideas for how to make this work.  Without going into the painful details and resulting unfortunate spaghetti-fication of my code (err..), the basic solution architecture is in three components:
  • The Users table now has a field called 'activity_time'.  Every 30 seconds, whether a user is on the home page or the gameplay page, an AJAX POST calls a php script to set this field to the MySQL CURRENT_TIMESTAMP.  So this is saying "user x is still here".
  • While on the gameplay page, the same script additionally checks for (a) any users marked with that 'current_game_id' but whose 'activity_time' is more than 30 seconds old, and (b) any users with one of the 'fb_id's registered (in a Javascript object stored by the page) as involved in that game, but with 'current_game_id' set to null.  [Note: (a) condition = those who have left the app by going to another page outside of it or closing the browser tab/window, and the cron job has not yet cleared up their data; see next bullet...  (b) condition = those who moved back to the app home page, which clears up their game data immediately, or those who have been cleared up already by the cron job.]  The id's of these "dropout" users are sent back to the page; the display of players in the game is updated to remove the dropouts, and a message is given to the user to advise them to return to the home page and start another game...(see below *).
  • There is now a cron job set up on the server to run every 5 minutes.  It calls another php script which retrieves all users whose activity time is more than 50 seconds old, removes any current game data they have, marks them as logged out, and either decreases the number of players in the game they had been in, or deletes that game entirely if it's now empty of players.
(*) Right now, there is no handling for updating the game state as it's stored in the ActionScript within the Flash app instance, nor does the page stop polling for the dropout players' turns.  As I see it, there are two options for where to go from here, regarding this issue:
  • Don't bother fixing the AS game state.  Just redirect the user automatically back to the home page.  This would make sense anyway for games that only had 2 players to start with, since playing alone is not as much fun.
  • Use the ExternalInterface class in AS to receive a notification from the page (via JS); update the game state accordingly; allow play to continue without the lost player(s).
Given how much trouble logout status has given me for many hours today, I'm inclined to go with the first option (or just let the user figure out that it doesn't work, and that they need to manually go back to the home page or leave the app).  It might turn out to be pretty quick to implement the second option, as long as there is still at least one player in the game, but I would rather turn my attention and remaining time towards a couple of the other goals on my pre-submission list, since I do think they are still important.

Finally, finally:


(I wonder how many Pandora hours I've used, out of my 40 hours given for the month, just from working today...)

No comments:

Post a Comment