3
\$\begingroup\$

I have working code that needs some optimization for scalability and overall performance. This code basically creates a lobby where users wait for match to be created and link users for a multiplayer game match.

PlayerWait player = GameMatchEngine.getInstance().createMatch(userId);
if (player.getPlayer().getUserId() == userId) {    
    while (player.isAvailable()) {
        Thread.sleep(100);
    }

    if (player.getMatchBegan()) {
        if (!hasUser(userId)) {
            .... failed to find a match ....
        } else {
            multiplayer = getMultiplayer(userId);
        }//if
    }
}

The code above is called when a user request for a match. if (player.getPlayer().getUserId() == userId) happens when a first user requests a match, and there is no player in the lobby.

private Map<Integer, Multiplayer> users = 
          new ConcurrentHashMap<Integer, Multiplayer>();

/* removes user who was waiting and retrieves Multiplayer Session */
private boolean hasUser(Integer userId) {
    return this.users.containsKey(userId);
}

PlayerWait class:

private Player player;
private boolean availablility = true;
private boolean matchBegan = true;
Timer timer;

public PlayerWait(Player player) {
    setPlayer(player);
    waitTimeExpired(10);
}

private void waitTimeExpired(int seconds) {
    this.timer = new Timer();
    timer.schedule(new scheduledTask(), seconds * 1000);
}//waitTimeExpired()

class scheduledTask extends TimerTask {

    @Override
    public void run() {
        synchronized(this) {
            if(availablility) {
                setAvailable(false);
                if(matchBegan) {
                    GameMatchEngine.getInstance().
                            matchWithAI(getPlayerWait());
                }
            }//if
        }
        timer.cancel();
    }

}//class scheduledTask

public PlayerWait createMatch(int user_id) throws SQLException {
    String rank = getRank(user_id);

    PlayerWait playerFound = findOpponentUser(rank, user_id);

    if (playerFound == null) {
        playerFound = addUser(new Player(user_id, rank));
    } else {
        try {
            .... found an opponent; create a match in db .... 
        } catch (SQLException e) {
            LOGGER.error("Error in creating a match: ", e);
        }//try
    }       

    LOGGER.error("PlayerWait: " + playerFound);
    return playerFound;
}//createMatch()

private PlayerWait findOpponentUser(String rank, int userId) {
    boolean found = false;
    PlayerWait player = null; 

    if (hasUser(rank)) {
        player = getUser(rank);
        if (player.getPlayer().getUserId() != userId) {
            synchronized (player) {
                found = player.isAvailable();
                player.setAvailable(false);
            }//synchronized()
        } else {
            found = true;
        }

        if (found) {
            removeUser(rank);
        } else {
            player = null;
        }//if
    }//if

    return player;
}//findOpponentUser()

private PlayerWait addUser(Player player) {
    PlayerWait playerWait = null;
    String rank = player.getUserRank();

    if(!this.users.containsKey(rank)) {
        playerWait = new PlayerWait(player);
        this.users.put(rank,playerWait);
    }

    return playerWait;
}//addUser()

So when a user A requests a match and found no one, user A is put into the concurrent hash map "users" and waits for ten seconds before expiration. While user is in the wait list, request is held onto the server until being expired. When user B requests a match, user A is released from the concurrent hash map and matched against user B. Multiplayer Session is used for holding these two players' data.

Here's my concern:

  1. I believe the timer is a separate thread that runs on the background while request is also being held on the server. In addition, while user is waiting for the match, the Thread.sleep is called every 0.1 second. Is there any better way to implement/optimize this? There should be a scalability issue here.

  2. I think the overall performance soars as this is not really a real time match making method. Sharing any ideas on how to create matches with better performance would be greatly appreciated.

\$\endgroup\$
1
\$\begingroup\$

I believe what you describe fits the producer-consumer pattern: A player who wishes to be matched is a consumer of another player from the lobby. when players join the lobby, it should produce them to consumers. Therefore I think a better data structure for your requirements is BlockingQueue. The most advantageous feature is the blocking of consumers. this eliminates the need for the timer. you can have consumer wait forever for an item (another player) or have it timeout after a certain time. regarding rank, you can still use a Map with rank as key, and the queue as value. which brings up the question: in its current definition, the Map can hold only one game per rank?

However, as long as the solution is confined within one JVM, you will have a scalability concern. To fix that, you will need to externalize the queue and use a messaging system (like RabbitMQ) and then rank becomes a topic where consumer register for. This is a much more complex solution but it has the potential to grow to social-network scale.

One more comment regarding readability:

if (player.getPlayer().getUserId() == userId) happens when a first user requests a match, and there is no player in the lobby.

First of all, I do not understand how this may happen, since in findOpponentUser() you ask on unequality of user ids. anyway, at the very least, you should make a method like isOnlyPlayerInLobby().

\$\endgroup\$
  • \$\begingroup\$ First of all, great thanks to your comment. They seem to be great answers. Yes, it is producer-consumer pattern. How would you go about implementing this BlockingQueue instead PlayerWait class that I use? I am not really familiar with BlockingQueue, so before I dive into it, I just want to have a general idea of how to approach this. Can you elaborate more on the use of external messaging system? Every user has rank and I retrieve it from the database. What difference would this RabbitMQ make? How would I approach about using this? For the readability part, I totally agree with you on this. \$\endgroup\$ – John May 23 '18 at 14:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.