I wrote a class that creates two ScheduledFuture<?> tasks and runs them one after another if players are in a game. The one timer waits 10 seconds, runs the game logic, and then waits for a few seconds for the players to see the results. If all players submit their choice before the 10 second timer runs out, it cancels the timer and runs the logic immediately. If the player
To help prevent race conditions, I have the users only set their own UserVars when they submit their choice. However, I want to make sure that I'm not incorrectly pausing the thread and if my timers are threadsafe.
Below is my current code. Please let me know if you think synchronized is the best choice in this scenario or if I should instead opt for a ReentrantLock. Any other thoughts or suggestions you have are very welcome:
Code: Select all
package sfs2x.extension.test.signup;
import com.smartfoxserver.v2.SmartFoxServer;
import com.smartfoxserver.v2.entities.Room;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.entities.variables.RoomVariable;
import com.smartfoxserver.v2.entities.variables.SFSRoomVariable;
import com.smartfoxserver.v2.entities.variables.SFSUserVariable;
import com.smartfoxserver.v2.entities.variables.UserVariable;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class GameHandler extends BaseClientRequestHandler {
@Override
public void handleClientRequest(User user, ISFSObject params)
{
Room room = user.getJoinedRooms().get(0);
ScheduledFuture<?> taskHandle; // Get game logic execution task
// Don't evaluate request if in results stage
if(resultsStage(room))
{
trace("Request rejected.");
return;
}
// Run one request from a user at a time
synchronized(user)
{
if(room.getProperty("turnTimer") != null)
taskHandle = (ScheduledFuture<?>) room.getProperty("turnTimer");
else
taskHandle = null;
// Get submission data
UserVariable choice; // User choice
if(params.containsKey("c"))
choice = SFSUserVariable.newPrivateVariable("ch", params.getInt("c"));
else
choice = SFSUserVariable.newPrivateVariable("ch", 0);
choice.setHidden(true); // Suppress client update
getApi().setUserVariables(user, Arrays.asList(choice));
//Check if all users have submitted choices
boolean evaluate = true;
for(User u : room.getUserList())
{
//No valid choice received
if(u.getVariable("ch").getIntValue() == -1)
{
evaluate = false;
break;
}
}
// Ready to evaluate game round choices
if(evaluate)
{
// Cancel task if one is running
if((taskHandle != null) && !taskHandle.isDone())
{
// Run logic if task hasn't executed
trace("Task canceled.");
// Cancel the currently running task
taskHandle.cancel(true);
// Run logic
gameLogic(room);
}
else
gameLogic(room); // Run logic
}
}
}
private void gameLogic(Room room)
{
//Runs game logic and send results to users
trace("Run game logic for "+room.getName());
boolean complete;
...
// Run results timer if match is still running
if(complete)
{
// Game complete; clear the turn counter
turn = new SFSRoomVariable("turn", 0);
trace("Game complete!");
}
else
{
turn = new SFSRoomVariable("turn", room.getVariable("turn").getIntValue()+1);
setResultsTimer(room); // Wait for clients to see results of turn
}
}
public class TurnRunner implements Runnable
{
// Reference to the current room and task
Room gameRoom;
ScheduledFuture<?> taskHandle;
TurnRunner(Room room)
{
gameRoom = room;
}
@Override
public void run()
{
// Run round logic if timer has run out
try
{
// Run gameLogic for room
gameLogic(gameRoom);
}
catch (Exception e)
{
trace("Turn task failed: " + e.getMessage());
}
}
}
public class ResultsRunner implements Runnable
{
// Reference to the current room and task
Room gameRoom;
ScheduledFuture<?> taskHandle;
ResultsRunner(Room room)
{
gameRoom = room;
}
@Override
public void run()
{
// Wait for results duration and then start turn timer
try
{
setTurnTimer(gameRoom);
}
catch (Exception e)
{
trace("Results task failed: " + e.getMessage());
}
}
}
private boolean resultsStage(Room room)
{
if(room.getProperty("resultsTimer") == null)
return false;
else
{
ScheduledFuture<?> taskHandle = (ScheduledFuture<?>) room.getProperty("resultsTimer");
return !taskHandle.isDone();
}
}
private void setTurnTimer(Room room)
{
trace("Running turn timer in 10 seconds...");
// Start turn task that lasts 10 seconds
SmartFoxServer sfs = SmartFoxServer.getInstance();
// Allow players 10 seconds to make a decision
ScheduledFuture<?> taskHandle = sfs.getTaskScheduler().schedule(new TurnRunner(room), 10, TimeUnit.SECONDS);
room.setProperty("turnTimer", taskHandle);
}
private void setResultsTimer(Room room)
{
SmartFoxServer sfs = SmartFoxServer.getInstance();
int numPlayers = room.getSize().getTotalUsers();
trace("Running results timer in "+(numPlayers * 2)+" seconds...");
// Show results for 2 seconds per player. Ex) A room of three people would wait for 6 seconds.
ScheduledFuture<?> taskHandle = sfs.getTaskScheduler().schedule(new ResultsRunner(room), (numPlayers * 2), TimeUnit.SECONDS);
room.setProperty("resultsTimer", taskHandle);
}
}