diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 45cf83d32ad..74242345ac1 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -47,6 +47,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPOutputStream; import mage.MageException; @@ -100,6 +101,9 @@ public class GameController implements GameCallback { protected ScheduledExecutorService joinWaitingExecutor = Executors.newSingleThreadScheduledExecutor(); + private ScheduledFuture futureTimeout; + protected static ScheduledExecutorService timeoutIdleExecutor = ThreadExecutor.getInstance().getTimeoutIdleExecutor(); + private ConcurrentHashMap gameSessions = new ConcurrentHashMap<>(); private ConcurrentHashMap watchers = new ConcurrentHashMap<>(); private ConcurrentHashMap timers = new ConcurrentHashMap<>(); @@ -123,7 +127,7 @@ public class GameController implements GameCallback { this.choosingPlayerId = choosingPlayerId; for (Player player: game.getPlayers().values()) { if (!player.isHuman()) { - useTimeout = false; // no timeout because of beeing idle if playing against AI + useTimeout = false; // no timeout for AI players because of beeing idle break; } } @@ -132,6 +136,10 @@ public class GameController implements GameCallback { } public void cleanUp() { + cancelTimeout(); + for (GameSessionPlayer gameSessionPlayer: gameSessions.values()) { + gameSessionPlayer.CleanUp(); + } ChatManager.getInstance().destroyChatSession(chatId); } @@ -318,7 +326,7 @@ public class GameController implements GameCallback { GameSessionPlayer gameSession = gameSessions.get(playerId); String joinType; if (gameSession == null) { - gameSession = new GameSessionPlayer(game, userId, playerId, useTimeout); + gameSession = new GameSessionPlayer(game, userId, playerId); gameSessions.put(playerId, gameSession); gameSession.setUserData(user.getUserData()); joinType = "joined"; @@ -568,13 +576,14 @@ public class GameController implements GameCallback { } } - public void timeout(UUID userId) { - if (userPlayerMap.containsKey(userId)) { - String sb = game.getPlayer(userPlayerMap.get(userId)).getName() + + public void idleTimeout(UUID playerId) { + Player player = game.getPlayer(playerId); + if (player != null) { + String sb = player.getName() + " has timed out (player had priority and was not active for " + ConfigSettings.getInstance().getMaxSecondsIdle() + " seconds ) - Auto concede."; ChatManager.getInstance().broadcast(chatId, "", sb, MessageColor.BLACK, true, MessageType.STATUS); - game.idleTimeout(getPlayerId(userId)); + game.idleTimeout(playerId); } } @@ -600,7 +609,7 @@ public class GameController implements GameCallback { public void sendPlayerUUID(UUID userId, final UUID data) { sendMessage(userId, new Command() { @Override - public void execute(UUID playerId) { + public void execute(UUID playerId) { getGameSession(playerId).sendPlayerUUID(data); } }); @@ -670,16 +679,13 @@ public class GameController implements GameCallback { } } } - // TODO: inform watchers -// for (final GameWatcher gameWatcher: watchers.values()) { -// gameWatcher.update(); -// } + // TODO: inform watchers about game end and who won } private synchronized void ask(UUID playerId, final String question) throws MageException { perform(playerId, new Command() { @Override - public void execute(UUID playerId) { + public void execute(UUID playerId) { getGameSession(playerId).ask(question); } }); @@ -893,13 +899,20 @@ public class GameController implements GameCallback { SystemUtil.addCardsForTesting(game); } + /** + * Performas a request to a player + * @param playerId + * @param command + * @throws MageException + */ private void perform(UUID playerId, Command command) throws MageException { perform(playerId, command, true); } private void perform(UUID playerId, Command command, boolean informOthers) throws MageException { - if (game.getPlayer(playerId).isGameUnderControl()) { + if (game.getPlayer(playerId).isGameUnderControl()) { // is the player controlling it's own turn if (gameSessions.containsKey(playerId)) { + setupTimeout(playerId); command.execute(playerId); } if (informOthers) { @@ -909,6 +922,7 @@ public class GameController implements GameCallback { List players = Splitter.split(game, playerId); for (UUID uuid : players) { if (gameSessions.containsKey(uuid)) { + setupTimeout(uuid); command.execute(uuid); } } @@ -918,6 +932,11 @@ public class GameController implements GameCallback { } } + /** + * A player has send an answer / request + * @param userId + * @param command + */ private void sendMessage(UUID userId, Command command) { final UUID playerId = userPlayerMap.get(userId); // player has game under control (is not cotrolled by other player) @@ -926,6 +945,7 @@ public class GameController implements GameCallback { // then execute only your action if (game.getPriorityPlayerId() == null || game.getPriorityPlayerId().equals(playerId)) { if (gameSessions.containsKey(playerId)) { + cancelTimeout(); command.execute(playerId); } } else { @@ -934,6 +954,7 @@ public class GameController implements GameCallback { if (player != null) { for (UUID controlled : player.getPlayersUnderYourControl()) { if (gameSessions.containsKey(controlled) && game.getPriorityPlayerId().equals(controlled)) { + cancelTimeout(); command.execute(controlled); } } @@ -941,11 +962,35 @@ public class GameController implements GameCallback { // else player has no priority to do something, so ignore the command // e.g. you click at one of your cards, but you can't play something at that moment } + } else { // ignore - no control over the turn } } + private synchronized void setupTimeout(final UUID playerId) { + if (!useTimeout) { + return; + } + cancelTimeout(); + futureTimeout = timeoutIdleExecutor.schedule( + new Runnable() { + @Override + public void run() { + idleTimeout(playerId); + } + }, + Main.isTestMode() ? 3600 :ConfigSettings.getInstance().getMaxSecondsIdle(), + TimeUnit.SECONDS + ); + } + + private synchronized void cancelTimeout() { + if (futureTimeout != null) { + futureTimeout.cancel(false); + } + } + interface Command { void execute(UUID player); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameManager.java b/Mage.Server/src/main/java/mage/server/game/GameManager.java index 2b2a06807f2..b1fde3adfcc 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GameManager.java @@ -163,13 +163,6 @@ public class GameManager { return false; } - public void timeout(UUID gameId, UUID userId) { - GameController gameController = gameControllers.get(gameId); - if (gameController != null) { - gameController.timeout(userId); - } - } - public void removeGame(UUID gameId) { GameController gameController = gameControllers.get(gameId); if (gameController != null) { diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index 262d1092065..8a2bd7d2347 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -37,9 +37,6 @@ import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import mage.cards.Cards; import mage.choices.Choice; import mage.constants.ManaType; @@ -49,10 +46,8 @@ import mage.game.Table; import mage.interfaces.callback.ClientCallback; import mage.players.Player; import mage.players.net.UserData; -import mage.server.Main; import mage.server.User; import mage.server.UserManager; -import mage.server.util.ConfigSettings; import mage.server.util.ThreadExecutor; import mage.view.AbilityPickerView; import mage.view.CardsView; @@ -72,23 +67,23 @@ public class GameSessionPlayer extends GameSessionWatcher { private static final Logger logger = Logger.getLogger(GameSessionPlayer.class); private final UUID playerId; - private final boolean useTimeout; - private ScheduledFuture futureTimeout; - protected static ScheduledExecutorService timeoutExecutor = ThreadExecutor.getInstance().getTimeoutExecutor(); private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor(); private UserData userData; - public GameSessionPlayer(Game game, UUID userId, UUID playerId, boolean useTimeout) { + public GameSessionPlayer(Game game, UUID userId, UUID playerId) { super(userId, game, true); this.playerId = playerId; - this.useTimeout = useTimeout; + } + + @Override + public void CleanUp() { + super.CleanUp(); } public void ask(final String question) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameAsk", game.getId(), new GameClientMessage(getGameView(), question))); @@ -98,7 +93,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void target(final String question, final CardsView cardView, final Set targets, final boolean required, final Map options) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameTarget", game.getId(), new GameClientMessage(getGameView(), question, cardView, targets, required, options))); @@ -108,7 +102,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void select(final String message, final Map options) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameSelect", game.getId(), new GameClientMessage(getGameView(), message, options))); @@ -118,7 +111,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void chooseAbility(final AbilityPickerView abilities) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameChooseAbility", game.getId(), abilities)); @@ -128,7 +120,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void choosePile(final String message, final CardsView pile1, final CardsView pile2) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameChoosePile", game.getId(), new GameClientMessage(message, pile1, pile2))); @@ -138,7 +129,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void chooseChoice(final Choice choice) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameChooseChoice", game.getId(), new GameClientMessage(choice))); @@ -148,7 +138,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void playMana(final String message) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gamePlayMana", game.getId(), new GameClientMessage(getGameView(), message))); @@ -158,7 +147,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void playXMana(final String message) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gamePlayXMana", game.getId(), new GameClientMessage(getGameView(), message))); @@ -168,7 +156,6 @@ public class GameSessionPlayer extends GameSessionWatcher { public void getAmount(final String message, final int min, final int max) { if (!killed) { - setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { user.fireCallback(new ClientCallback("gameSelectAmount", game.getId(), new GameClientMessage(message, min, max))); @@ -204,64 +191,23 @@ public class GameSessionPlayer extends GameSessionWatcher { } } - /** - * Reset the timeout counter after priority in game changed - * - */ - public void signalPriorityChange() { - setupTimeout(); - } - - private synchronized void setupTimeout() { - if (!useTimeout) { - return; - } - cancelTimeout(); - futureTimeout = timeoutExecutor.schedule( - new Runnable() { - @Override - public void run() { - // if player has no priority, he does not get timeout - if(game.getPriorityPlayerId().equals(playerId)) { - GameManager.getInstance().timeout(game.getId(), userId); - } else { - setupTimeout(); - } - } - }, - Main.isTestMode() ? 3600 :ConfigSettings.getInstance().getMaxSecondsIdle(), - TimeUnit.SECONDS - ); - } - - private synchronized void cancelTimeout() { - if (futureTimeout != null) { - futureTimeout.cancel(false); - } - } - public void sendPlayerUUID(UUID data) { - cancelTimeout(); game.getPlayer(playerId).setResponseUUID(data); } public void sendPlayerString(String data) { - cancelTimeout(); game.getPlayer(playerId).setResponseString(data); } public void sendPlayerManaType(ManaType manaType, UUID manaTypePlayerId) { - cancelTimeout(); game.getPlayer(playerId).setResponseManaType(manaTypePlayerId, manaType); } public void sendPlayerBoolean(Boolean data) { - cancelTimeout(); game.getPlayer(playerId).setResponseBoolean(data); } public void sendPlayerInteger(Integer data) { - cancelTimeout(); game.getPlayer(playerId).setResponseInteger(data); } @@ -338,7 +284,6 @@ public class GameSessionPlayer extends GameSessionWatcher { } else { logger.debug("- ex: " + ex.toString()); } - ex.printStackTrace(); }else { logger.fatal("Game session game quit exception - null gameId:" + game.getId() +" playerId: " + playerId); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java index 2464f19068a..f269b87f0e3 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java @@ -110,6 +110,14 @@ public class GameSessionWatcher { } } + /** + * Cleanup if Session ends + * + */ + public void CleanUp() { + + } + public void gameError(final String message) { if (!killed) { User user = UserManager.getInstance().getUser(userId); diff --git a/Mage.Server/src/main/java/mage/server/util/Splitter.java b/Mage.Server/src/main/java/mage/server/util/Splitter.java index efa43682c09..44315297d43 100644 --- a/Mage.Server/src/main/java/mage/server/util/Splitter.java +++ b/Mage.Server/src/main/java/mage/server/util/Splitter.java @@ -1,11 +1,10 @@ package mage.server.util; -import mage.game.Game; -import mage.players.Player; - import java.util.ArrayList; import java.util.List; import java.util.UUID; +import mage.game.Game; +import mage.players.Player; /** * @author nantuko @@ -13,7 +12,7 @@ import java.util.UUID; public class Splitter { public static List split(Game game, UUID playerId) { - List players = new ArrayList(); + List players = new ArrayList<>(); //players.add(playerId); // add original player Player player = game.getPlayer(playerId); if (player != null && player.getTurnControlledBy() != null) { diff --git a/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java b/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java index 420775d57b3..9b39664d464 100644 --- a/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java +++ b/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java @@ -43,7 +43,8 @@ public class ThreadExecutor { private static final ExecutorService callExecutor = Executors.newCachedThreadPool(); private static final ExecutorService gameExecutor = Executors.newFixedThreadPool(ConfigSettings.getInstance().getMaxGameThreads()); - private static final ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(5); + private static final ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(4); + private static final ScheduledExecutorService timeoutIdleExecutor = Executors.newScheduledThreadPool(4); /** * noxx: what the settings below do is setting the ability to keep OS threads for new games for 60 seconds @@ -62,7 +63,10 @@ public class ThreadExecutor { ((ThreadPoolExecutor)gameExecutor).setThreadFactory(new XMageThreadFactory("GAME")); ((ThreadPoolExecutor)timeoutExecutor).setKeepAliveTime(60, TimeUnit.SECONDS); ((ThreadPoolExecutor)timeoutExecutor).allowCoreThreadTimeOut(true); - ((ThreadPoolExecutor)timeoutExecutor).setThreadFactory(new XMageThreadFactory("TIME")); + ((ThreadPoolExecutor)timeoutExecutor).setThreadFactory(new XMageThreadFactory("TIMEOUT")); + ((ThreadPoolExecutor)timeoutIdleExecutor).setKeepAliveTime(60, TimeUnit.SECONDS); + ((ThreadPoolExecutor)timeoutIdleExecutor).allowCoreThreadTimeOut(true); + ((ThreadPoolExecutor)timeoutIdleExecutor).setThreadFactory(new XMageThreadFactory("TIMEOUT_IDLE")); } private static final ThreadExecutor INSTANCE = new ThreadExecutor(); @@ -91,6 +95,10 @@ public class ThreadExecutor { public ScheduledExecutorService getTimeoutExecutor() { return timeoutExecutor; } + + public ScheduledExecutorService getTimeoutIdleExecutor() { + return timeoutIdleExecutor; + } } diff --git a/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java b/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java index 192c09c3f4f..f760ea1c188 100644 --- a/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java +++ b/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java @@ -110,7 +110,7 @@ public class MageBase { server.ack("gameInit", sessionId); } else if (callback.getMethod().equals("gameAsk")) { GameClientMessage message = (GameClientMessage) callback.getData(); - logger.info("ASK >> " + message.getMessage()); + logger.log(Level.INFO, "ASK >> {0}", message.getMessage()); if (message.getMessage().equals("Do you want to take a mulligan?")) { server.sendPlayerBoolean(gameId, sessionId, false); }