mirror of
https://github.com/correl/mage.git
synced 2025-01-12 19:25:44 +00:00
* Match handling - some changes to solve/workaround the draw games that cause matches not to end. Changed disconnect handling to not block the client (needs tests if works correctly).
This commit is contained in:
parent
8567c1cd38
commit
3c12b23928
9 changed files with 100 additions and 58 deletions
|
@ -521,12 +521,15 @@ public class TableController {
|
|||
if (user != null) {
|
||||
if (!user.isConnected()) {
|
||||
// if the user is not connected but exits, the user is currently disconnected. So it's neccessary
|
||||
// to join the user to the game here, so he can join the game, if he reconnects in time.
|
||||
// to join the user to the game here (instead the client does it) , so he can join the game, if he reconnects in time.
|
||||
// remove an existing constructing for the player if it exists
|
||||
user.removeConstructing(match.getPlayer(entry.getValue()).getPlayer().getId());
|
||||
GameManager.getInstance().joinGame(match.getGame().getId(), user.getId());
|
||||
logger.debug("Joined currently not connected user " + user.getName() + " matchId: " + match.getId());
|
||||
} else {
|
||||
user.gameStarted(match.getGame().getId(), entry.getValue());
|
||||
}
|
||||
user.gameStarted(match.getGame().getId(), entry.getValue());
|
||||
|
||||
if (creator == null) {
|
||||
creator = user.getName();
|
||||
} else {
|
||||
|
@ -543,6 +546,9 @@ public class TableController {
|
|||
matchPlayer.setQuit(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Match player has already quit
|
||||
throw new MageException("Can't start game - user already quit userId " + entry.getKey());
|
||||
}
|
||||
}
|
||||
// Append AI opponents to the log file
|
||||
|
|
|
@ -44,6 +44,7 @@ import mage.MageException;
|
|||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckCardLists;
|
||||
import mage.constants.TableState;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameException;
|
||||
import mage.game.Table;
|
||||
import mage.game.draft.Draft;
|
||||
|
@ -55,6 +56,7 @@ import mage.players.Player;
|
|||
import mage.server.game.GameController;
|
||||
import mage.server.game.GameManager;
|
||||
import mage.server.game.GamesRoomManager;
|
||||
import mage.server.util.ThreadExecutor;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
|
@ -65,6 +67,8 @@ public class TableManager {
|
|||
|
||||
protected static ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
// protected static ScheduledExecutorService expireExecutor = ThreadExecutor.getInstance().getExpireExecutor();
|
||||
|
||||
private static final TableManager INSTANCE = new TableManager();
|
||||
private static final Logger logger = Logger.getLogger(TableManager.class);
|
||||
private static final DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
|
||||
|
@ -77,7 +81,7 @@ public class TableManager {
|
|||
*
|
||||
* In minutes.
|
||||
*/
|
||||
private static final int EXPIRE_CHECK_PERIOD = 5;
|
||||
private static final int EXPIRE_CHECK_PERIOD = 1;
|
||||
|
||||
public static TableManager getInstance() {
|
||||
return INSTANCE;
|
||||
|
@ -340,14 +344,23 @@ public class TableManager {
|
|||
|
||||
Table table = tables.get(tableId);
|
||||
tables.remove(tableId);
|
||||
Match match = table.getMatch();
|
||||
Game game = null;
|
||||
if (match != null) {
|
||||
game = match.getGame();
|
||||
if (game != null && !game.hasEnded()) {
|
||||
game.end();
|
||||
}
|
||||
}
|
||||
|
||||
// If table is not finished, the table has to be removed completly because it's not a normal state (if finished it will be removed in GamesRoomImpl.Update())
|
||||
if (!table.getState().equals(TableState.FINISHED)) {
|
||||
if (game != null) {
|
||||
GameManager.getInstance().removeGame(game.getId());
|
||||
}
|
||||
GamesRoomManager.getInstance().removeTable(tableId);
|
||||
}
|
||||
if (table.getMatch() != null && table.getMatch().getGame() != null) {
|
||||
table.getMatch().getGame().end();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,6 +377,7 @@ public class TableManager {
|
|||
logger.debug(chatSession.getChatId() + " " +formatter.format(chatSession.getCreateTime()) +" " + chatSession.getInfo()+ " "+ chatSession.getClients().values().toString());
|
||||
}
|
||||
logger.debug("------- Games: " + GameManager.getInstance().getNumberActiveGames() + " --------------------------------------------");
|
||||
logger.debug(" Active Game Worker: " + ThreadExecutor.getInstance().getActiveThreads(ThreadExecutor.getInstance().getGameExecutor()));
|
||||
for (Entry<UUID, GameController> entry: GameManager.getInstance().getGameController().entrySet()) {
|
||||
logger.debug(entry.getKey() + entry.getValue().getPlayerNameList());
|
||||
}
|
||||
|
@ -404,6 +418,7 @@ public class TableManager {
|
|||
}
|
||||
for (UUID tableId : toRemove) {
|
||||
try {
|
||||
logger.warn("Removing unhealthy tableId " + tableId);
|
||||
removeTable(tableId);
|
||||
} catch (Exception e) {
|
||||
logger.error(e);
|
||||
|
|
|
@ -54,6 +54,7 @@ public class UserManager {
|
|||
private static final Logger logger = Logger.getLogger(UserManager.class);
|
||||
|
||||
private final ConcurrentHashMap<UUID, User> users = new ConcurrentHashMap<>();
|
||||
|
||||
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
|
||||
|
||||
private static final UserManager INSTANCE = new UserManager();
|
||||
|
@ -131,18 +132,29 @@ public class UserManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
public void removeUser(UUID userId, DisconnectReason reason) {
|
||||
public void removeUser(final UUID userId, final DisconnectReason reason) {
|
||||
if (userId != null) {
|
||||
User user = users.get(userId);
|
||||
final User user = users.get(userId);
|
||||
if (user != null) {
|
||||
logger.debug("User " + user.getName() + " will be removed (" + reason.toString() + ") userId: " + userId);
|
||||
user.remove(reason);
|
||||
users.remove(userId);
|
||||
logger.debug("User " + user.getName() + " removed");
|
||||
callExecutor.execute(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
logger.debug("User " + user.getName() + " will be removed (" + reason.toString() + ") userId: " + userId);
|
||||
user.remove(reason);
|
||||
users.remove(userId);
|
||||
logger.debug("User " + user.getName() + " removed");
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
logger.warn(new StringBuilder("Trying to remove userId: ").append(userId).append(" but it does not exist."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean extendUserSession(UUID userId, String pingInfo) {
|
||||
|
@ -160,34 +172,16 @@ public class UserManager {
|
|||
* Is the connection lost for more than 3 minutes, the user will be removed (within 3 minutes the user can reconnect)
|
||||
*/
|
||||
private void checkExpired() {
|
||||
// calling this with executer saves the sceduled job to be dying becuase of exception.
|
||||
// Also exceptions were not reported as now with this handling
|
||||
try {
|
||||
callExecutor.execute(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Calendar expired = Calendar.getInstance();
|
||||
expired.add(Calendar.MINUTE, -3);
|
||||
List<User> usersToCheck = new ArrayList<>();
|
||||
usersToCheck.addAll(users.values());
|
||||
for (User user : usersToCheck) {
|
||||
if (user.isExpired(expired.getTime())) {
|
||||
logger.info(new StringBuilder(user.getName()).append(": session expired userId: ").append(user.getId())
|
||||
.append(" Host: ").append(user.getHost()));
|
||||
removeUser(user.getId(), DisconnectReason.SessionExpired);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
Calendar expired = Calendar.getInstance();
|
||||
expired.add(Calendar.MINUTE, -3);
|
||||
List<User> usersToCheck = new ArrayList<>();
|
||||
usersToCheck.addAll(users.values());
|
||||
for (User user : usersToCheck) {
|
||||
if (user.isExpired(expired.getTime())) {
|
||||
logger.info(new StringBuilder(user.getName()).append(": session expired userId: ").append(user.getId())
|
||||
.append(" Host: ").append(user.getHost()));
|
||||
removeUser(user.getId(), DisconnectReason.SessionExpired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -302,6 +302,7 @@ public class GameController implements GameCallback {
|
|||
if (!entry.getValue().init()) {
|
||||
logger.fatal("Unable to initialize client");
|
||||
//TODO: generate client error message
|
||||
GameManager.getInstance().removeGame(game.getId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ public class GameWorker implements Callable {
|
|||
@Override
|
||||
public Object call() {
|
||||
try {
|
||||
logger.debug("GameWorker started gameId "+ game.getId());
|
||||
game.start(choosingPlayerId);
|
||||
game.fireUpdatePlayersEvent();
|
||||
result.gameResult(game.getWinner());
|
||||
|
|
|
@ -59,10 +59,10 @@ public class ThreadExecutor {
|
|||
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("CALL"));
|
||||
((ThreadPoolExecutor)gameExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
|
||||
((ThreadPoolExecutor)gameExecutor).allowCoreThreadTimeOut(true);
|
||||
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("GAME"));
|
||||
((ThreadPoolExecutor)gameExecutor).setThreadFactory(new XMageThreadFactory("GAME"));
|
||||
((ThreadPoolExecutor)timeoutExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
|
||||
((ThreadPoolExecutor)timeoutExecutor).allowCoreThreadTimeOut(true);
|
||||
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("TIME"));
|
||||
((ThreadPoolExecutor)timeoutExecutor).setThreadFactory(new XMageThreadFactory("TIME"));
|
||||
}
|
||||
|
||||
private static final ThreadExecutor INSTANCE = new ThreadExecutor();
|
||||
|
@ -73,10 +73,17 @@ public class ThreadExecutor {
|
|||
|
||||
private ThreadExecutor() {}
|
||||
|
||||
public int getActiveThreads(ExecutorService executerService) {
|
||||
if (executerService instanceof ThreadPoolExecutor) {
|
||||
return ((ThreadPoolExecutor)executerService).getActiveCount();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public ExecutorService getCallExecutor() {
|
||||
return callExecutor;
|
||||
}
|
||||
|
||||
|
||||
public ExecutorService getGameExecutor() {
|
||||
return gameExecutor;
|
||||
}
|
||||
|
@ -84,7 +91,7 @@ public class ThreadExecutor {
|
|||
public ScheduledExecutorService getTimeoutExecutor() {
|
||||
return timeoutExecutor;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class XMageThreadFactory implements ThreadFactory {
|
||||
|
|
|
@ -38,6 +38,7 @@ import mage.game.events.TableEventSource;
|
|||
import mage.players.Player;
|
||||
|
||||
import java.util.*;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -46,6 +47,8 @@ import java.util.*;
|
|||
*/
|
||||
public abstract class MatchImpl implements Match {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MatchImpl.class);
|
||||
|
||||
protected UUID id = UUID.randomUUID();
|
||||
protected List<MatchPlayer> players = new ArrayList<>();
|
||||
protected List<Game> games = new ArrayList<>();
|
||||
|
@ -128,6 +131,19 @@ public abstract class MatchImpl implements Match {
|
|||
|
||||
@Override
|
||||
public boolean hasEnded() {
|
||||
// Some workarounds to end match if for unknown reason the match was not ended regularly
|
||||
if (getGame() == null && isDoneSideboarding()) {
|
||||
checkIfMatchEnds();
|
||||
}
|
||||
if (getGame() != null && getGame().hasEnded()) {
|
||||
for (MatchPlayer matchPlayer:players) {
|
||||
if (matchPlayer.getPlayer().hasQuit() && !matchPlayer.hasQuit()) {
|
||||
logger.warn("MatchPlayer was not set to quit matchId " + this.getId()+ " - " + matchPlayer.getName());
|
||||
matchPlayer.setQuit(true);
|
||||
}
|
||||
}
|
||||
checkIfMatchEnds();
|
||||
}
|
||||
return endTime != null;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public class MatchPlayer {
|
|||
private final String name;
|
||||
|
||||
private boolean quit;
|
||||
private boolean timerTimeout;
|
||||
private final boolean timerTimeout;
|
||||
private boolean doneSideboarding;
|
||||
private int priorityTimeLeft;
|
||||
|
||||
|
|
|
@ -72,18 +72,20 @@ public class Round {
|
|||
public boolean isRoundOver() {
|
||||
boolean roundIsOver = true;
|
||||
for (TournamentPairing pair: pairs) {
|
||||
if (pair.getMatch() != null && !pair.getMatch().hasEnded()) {
|
||||
roundIsOver = false;
|
||||
} else {
|
||||
if (!pair.isAlreadyPublished()) {
|
||||
tournament.updateResults();
|
||||
pair.setAlreadyPublished(true);
|
||||
if (tournament instanceof TournamentSingleElimination) {
|
||||
pair.eliminatePlayers();
|
||||
}
|
||||
// if it's the last round, finish all players for the tournament if their match is finished
|
||||
if (getRoundNumber() == tournament.getNumberRounds()) {
|
||||
pair.finishPlayersThatPlayedLastRound();
|
||||
if (pair.getMatch() != null) {
|
||||
if (!pair.getMatch().hasEnded()) {
|
||||
roundIsOver = false;
|
||||
} else {
|
||||
if (!pair.isAlreadyPublished()) {
|
||||
tournament.updateResults();
|
||||
pair.setAlreadyPublished(true);
|
||||
if (tournament instanceof TournamentSingleElimination) {
|
||||
pair.eliminatePlayers();
|
||||
}
|
||||
// if it's the last round, finish all players for the tournament if their match is finished
|
||||
if (getRoundNumber() == tournament.getNumberRounds()) {
|
||||
pair.finishPlayersThatPlayedLastRound();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue