mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 04:52:07 -08:00
* Redesigned idle timeout handling to prevent selecting wrong player for timeout.
This commit is contained in:
parent
fba13b26ac
commit
dd8a11ba5c
7 changed files with 86 additions and 88 deletions
|
|
@ -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<UUID, GameSessionPlayer> gameSessions = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<UUID, GameSessionWatcher> watchers = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<UUID, PriorityTimer> 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<UUID> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<UUID> targets, final boolean required, final Map<String, Serializable> 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<String, Serializable> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<UUID> split(Game game, UUID playerId) {
|
||||
List<UUID> players = new ArrayList<UUID>();
|
||||
List<UUID> players = new ArrayList<>();
|
||||
//players.add(playerId); // add original player
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null && player.getTurnControlledBy() != null) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue