Synchronized or lock?

Post here your questions about SFS2X. Here we discuss all server-side matters. For client API questions see the dedicated forums.

Moderators: Lapo, Bax

User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Synchronized or lock?

Postby moccha » 03 Jun 2021, 20:28

Hi,

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);
    }
}
User avatar
Lapo
Site Admin
Posts: 23008
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Synchronized or lock?

Postby Lapo » 04 Jun 2021, 07:10

Hi,
the synchronized keyword and a Reentrant Lock work in similar ways, so either is fine. The latter can be useful if your code needs to be structured in ways that don't allow you to write all the code in one synchronized block.

For an example see the first answer to this question here:
https://stackoverflow.com/questions/118 ... onizedthis

Cheers
Lapo
--
gotoAndPlay()
...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Synchronized or lock?

Postby moccha » 04 Jun 2021, 15:21

Lapo wrote:Hi,
the synchronized keyword and a Reentrant Lock work in similar ways, so either is fine. The latter can be useful if your code needs to be structured in ways that don't allow you to write all the code in one synchronized block.

For an example see the first answer to this question here:
https://stackoverflow.com/questions/118 ... onizedthis

Cheers


OK, thank you Lapo! I will use synchronized since it's only in one class.

Return to “SFS2X Questions”

Who is online

Users browsing this forum: No registered users and 77 guests