mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Improved stability and other related fixes:
* server: added limit to max chat message (related to #11285); * gui: fixed possible error after auto-submit deck; * gui: fixed possible error after end game dialog; * refactor: other code improves;
This commit is contained in:
parent
81f97c3b0e
commit
d1f9e9cc90
15 changed files with 86 additions and 48 deletions
|
|
@ -5,6 +5,7 @@ import mage.client.SessionHandler;
|
|||
import mage.client.cards.BigCard;
|
||||
import mage.client.dialog.PreferencesDialog;
|
||||
import mage.client.util.GUISizeHelper;
|
||||
import mage.constants.Constants;
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
import mage.view.ChatMessage.MessageType;
|
||||
import org.mage.card.arcane.ManaSymbols;
|
||||
|
|
@ -103,7 +104,6 @@ public class ChatPanelBasic extends javax.swing.JPanel {
|
|||
jScrollPaneTxt.getViewport().setBackground(new Color(0, 0, 0, CHAT_ALPHA));
|
||||
jScrollPaneTxt.setViewportBorder(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void cleanUp() {
|
||||
|
|
@ -398,6 +398,12 @@ public class ChatPanelBasic extends javax.swing.JPanel {
|
|||
|
||||
public void handleKeyTyped(java.awt.event.KeyEvent evt) {
|
||||
if (evt.getKeyChar() == KeyEvent.VK_ENTER) {
|
||||
|
||||
if (this.txtMessage.getText().length() > Constants.MAX_CHAT_MESSAGE_SIZE) {
|
||||
JOptionPane.showMessageDialog(null, "Can't send too long message", "Chat", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parentChatRef != null) {
|
||||
SessionHandler.sendChatMessage(parentChatRef.chatId, this.txtMessage.getText());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1497,13 +1497,13 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
timeToSubmit = 60;
|
||||
this.btnSubmitTimer.setEnabled(false);
|
||||
|
||||
ScheduledFuture scheduledFuture = scheduledExecutorService.schedule((Callable) () -> {
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
if (updateDeckTask != null) {
|
||||
updateDeckTask.cancel(true);
|
||||
}
|
||||
|
||||
if (SessionHandler.submitDeck(mode, tableId, deck.getDeckCardLists())) {
|
||||
removeDeckEditor();
|
||||
SwingUtilities.invokeLater(this::removeDeckEditor);
|
||||
}
|
||||
return null;
|
||||
}, 60, TimeUnit.SECONDS);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import mage.constants.PlayerAction;
|
|||
import mage.constants.TurnPhase;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.Serializable;
|
||||
|
|
@ -40,6 +41,7 @@ public class FeedbackPanel extends javax.swing.JPanel {
|
|||
private ChatPanelBasic connectedChatPanel;
|
||||
private Map<String, Serializable> lastOptions = new HashMap<>();
|
||||
|
||||
private static final int AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS = 8;
|
||||
private static final ScheduledExecutorService WORKER = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
/**
|
||||
|
|
@ -153,16 +155,18 @@ public class FeedbackPanel extends javax.swing.JPanel {
|
|||
*/
|
||||
private void endWithTimeout() {
|
||||
Runnable task = () -> {
|
||||
LOGGER.info("Ending game...");
|
||||
Component c = MageFrame.getGame(gameId);
|
||||
while (c != null && !(c instanceof GamePane)) {
|
||||
c = c.getParent();
|
||||
}
|
||||
if (c != null && c.isVisible()) { // check if GamePanel still visible
|
||||
FeedbackPanel.this.btnRight.doClick();
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
LOGGER.info("Ending game...");
|
||||
Component c = MageFrame.getGame(gameId);
|
||||
while (c != null && !(c instanceof GamePane)) {
|
||||
c = c.getParent();
|
||||
}
|
||||
if (c != null && c.isVisible()) { // check if GamePanel still visible
|
||||
FeedbackPanel.this.btnRight.doClick();
|
||||
}
|
||||
});
|
||||
};
|
||||
WORKER.schedule(task, 8, TimeUnit.SECONDS);
|
||||
WORKER.schedule(task, AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void updateOptions(Map<String, Serializable> options) {
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ public class MageActionCallback implements ActionCallback {
|
|||
|
||||
private Date enlargeredViewOpened;
|
||||
private volatile EnlargedWindowState enlargedWindowState = EnlargedWindowState.CLOSED;
|
||||
//private volatile boolean enlargedImageWindowOpen = false;
|
||||
// shows the alternative card the normal card or the alternative card (copy source, other flip side, other transformed side)
|
||||
private volatile EnlargeMode enlargeMode;
|
||||
|
||||
private static final ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(1);
|
||||
private ScheduledFuture<?> hideTimeout;
|
||||
private static final ScheduledExecutorService hideEnlargedCardWorker = Executors.newScheduledThreadPool(1);
|
||||
private ScheduledFuture<?> hideEnlagedCardTask;
|
||||
private static final int HIDE_ENLARGED_CARD_TIMEOUT_MS = 700;
|
||||
|
||||
private MageCard prevCardPanel;
|
||||
private boolean startedDragging;
|
||||
|
|
@ -472,7 +472,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
}
|
||||
|
||||
hideTooltipPopup();
|
||||
cancelTimeout();
|
||||
cancelHidingEnlagedCard();
|
||||
Component parentComponent = SwingUtilities.getRoot(cardPanel);
|
||||
if (parentComponent == null) {
|
||||
// virtual card (example: show card popup in non cards panel like PickChoiceDialog)
|
||||
|
|
@ -514,7 +514,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
popupTextWindowOpen = true;
|
||||
}
|
||||
if (enlargedWindowState != EnlargedWindowState.CLOSED) {
|
||||
cancelTimeout();
|
||||
cancelHidingEnlagedCard();
|
||||
displayEnlargedCard(cardPanel.getOriginal(), data);
|
||||
}
|
||||
}
|
||||
|
|
@ -552,7 +552,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
|
||||
public void hideAll(UUID gameId) {
|
||||
hideTooltipPopup();
|
||||
startHideTimeout();
|
||||
startHidingEnlagedCard();
|
||||
this.popupTextWindowOpen = false;
|
||||
if (gameId != null) {
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.TARGET);
|
||||
|
|
@ -740,14 +740,16 @@ public class MageActionCallback implements ActionCallback {
|
|||
return PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_ABILITY_TEXT_OVERLAY, "true").equals("true");
|
||||
}
|
||||
|
||||
private synchronized void startHideTimeout() {
|
||||
cancelTimeout();
|
||||
hideTimeout = timeoutExecutor.schedule(this::hideEnlargedCard, 700, TimeUnit.MILLISECONDS);
|
||||
private synchronized void startHidingEnlagedCard() {
|
||||
cancelHidingEnlagedCard();
|
||||
hideEnlagedCardTask = hideEnlargedCardWorker.schedule(
|
||||
() -> SwingUtilities.invokeLater(this::hideEnlargedCard), HIDE_ENLARGED_CARD_TIMEOUT_MS, TimeUnit.MILLISECONDS
|
||||
);
|
||||
}
|
||||
|
||||
private synchronized void cancelTimeout() {
|
||||
if (hideTimeout != null) {
|
||||
hideTimeout.cancel(false);
|
||||
private synchronized void cancelHidingEnlagedCard() {
|
||||
if (hideEnlagedCardTask != null) {
|
||||
hideEnlagedCardTask.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -647,7 +647,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
|||
while (!executor.isTerminated()) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException ie) {
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ public final class Constants {
|
|||
public static final int MAX_AVATAR_ID = 32;
|
||||
public static final int DEFAULT_AVATAR_ID = 10;
|
||||
|
||||
public static final int MAX_CHAT_MESSAGE_SIZE = 500; // ignore too big messages
|
||||
|
||||
/**
|
||||
* Time each player has during the game to play using his\her priority.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -45,16 +45,20 @@ public interface MageServer {
|
|||
Object serverGetPromotionMessages(String sessionId) throws MageException;
|
||||
|
||||
// sync cards send sets db
|
||||
// TODO: outdated, no more client/server sync, can be removed?
|
||||
@Deprecated // TODO: outdated, no more client/server sync, can be removed?
|
||||
List<ExpansionInfo> syncGetMissingExpansionData(List<String> codes);
|
||||
@Deprecated // TODO: outdated, no more client/server sync, can be removed?
|
||||
List<CardInfo> syncGetMissingCardsData(List<String> classNames);
|
||||
|
||||
ServerState getServerState() throws MageException; // TODO: need stable update process, so rename it after few releases
|
||||
|
||||
// TODO: miss session
|
||||
UUID serverGetMainRoomId() throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
List<RoomUsersView> roomGetUsers(UUID roomId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
List<MatchView> roomGetFinishedMatches(UUID roomId) throws MageException;
|
||||
|
||||
TableView roomCreateTable(String sessionId, UUID roomId, MatchOptions matchOptions) throws MageException;
|
||||
|
|
@ -81,22 +85,29 @@ public interface MageServer {
|
|||
|
||||
boolean tableIsOwner(String sessionId, UUID roomId, UUID tableId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
TableView roomGetTableById(UUID roomId, UUID tableId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
List<TableView> roomGetAllTables(UUID roomId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
void chatSendMessage(UUID chatId, String userName, String message) throws MageException;
|
||||
|
||||
void chatJoin(UUID chatId, String sessionId, String userName) throws MageException;
|
||||
|
||||
void chatLeave(UUID chatId, String sessionId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
UUID chatFindByGame(UUID gameId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
UUID chatFindByTable(UUID tableId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
UUID chatFindByTournament(UUID tournamentId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
UUID chatFindByRoom(UUID roomId) throws MageException;
|
||||
|
||||
boolean matchStart(String sessionId, UUID roomId, UUID tableId) throws MageException;
|
||||
|
|
@ -135,6 +146,7 @@ public interface MageServer {
|
|||
|
||||
void tournamentQuit(UUID tournamentId, String sessionId) throws MageException;
|
||||
|
||||
// TODO: miss session
|
||||
TournamentView tournamentFindById(UUID tournamentId) throws MageException;
|
||||
|
||||
void draftJoin(UUID draftId, String sessionId) throws MageException;
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ public class Connection {
|
|||
return userData;
|
||||
}
|
||||
|
||||
@Deprecated // TODO: server side cards do not supports now, so remove outdated code (db sync with server)
|
||||
public boolean isForceDBComparison() {
|
||||
return forceDBComparison;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -424,12 +424,8 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
|
|||
* @return
|
||||
*/
|
||||
protected Integer addActionsTimed() {
|
||||
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
return addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
// run new game simulation in parallel thread
|
||||
FutureTask<Integer> task = new FutureTask<>(() -> addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE));
|
||||
pool.execute(task);
|
||||
try {
|
||||
int maxSeconds = maxThink;
|
||||
|
|
@ -437,18 +433,19 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
|
|||
maxSeconds = 3600;
|
||||
}
|
||||
logger.debug("maxThink: " + maxSeconds + " seconds ");
|
||||
if (task.get(maxSeconds, TimeUnit.SECONDS) != null) {
|
||||
return task.get(maxSeconds, TimeUnit.SECONDS);
|
||||
Integer res = task.get(maxSeconds, TimeUnit.SECONDS);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
logger.info("simulating - timed out");
|
||||
task.cancel(true);
|
||||
} catch (ExecutionException e) {
|
||||
// exception error in simulated game
|
||||
e.printStackTrace();
|
||||
task.cancel(true);
|
||||
// real games: must catch
|
||||
// real games: must catch and log
|
||||
// unit tests: must raise again for test fail
|
||||
logger.error("AI simulation game catch error: " + e.getCause(), e);
|
||||
if (this.isTestsMode()) {
|
||||
throw new IllegalStateException("One of the simulated games raise the error: " + e.getCause());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import mage.cards.repository.CardInfo;
|
|||
import mage.cards.repository.CardRepository;
|
||||
import mage.cards.repository.ExpansionInfo;
|
||||
import mage.cards.repository.ExpansionRepository;
|
||||
import mage.constants.Constants;
|
||||
import mage.constants.ManaType;
|
||||
import mage.constants.PlayerAction;
|
||||
import mage.constants.TableState;
|
||||
|
|
@ -488,7 +489,13 @@ public class MageServerImpl implements MageServer {
|
|||
@Override
|
||||
//FIXME: why no sessionId here???
|
||||
public void chatSendMessage(final UUID chatId, final String userName, final String message) throws MageException {
|
||||
if (message.length() > Constants.MAX_CHAT_MESSAGE_SIZE) {
|
||||
logger.error("Chat message too big: " + message.length() + ", from user " + userName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: check and replace all usage of callExecutor.execute() by execute("actionName")
|
||||
callExecutor.execute(
|
||||
() -> managerFactory.chatManager().broadcast(chatId, userName, HtmlEscape.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null)
|
||||
);
|
||||
|
|
@ -953,8 +960,14 @@ public class MageServerImpl implements MageServer {
|
|||
}
|
||||
|
||||
@Override
|
||||
//TODO: check how often it is used
|
||||
public ServerState getServerState() throws MageException {
|
||||
// called one time per login, must work without auth and with diff versions
|
||||
try {
|
||||
// some ddos protection
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
|
||||
try {
|
||||
return new ServerState(
|
||||
GameFactory.instance.getGameTypes(),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|||
public class TableManagerImpl implements TableManager {
|
||||
protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
// protected static ScheduledExecutorService expireExecutor = ThreadExecutorImpl.getInstance().getExpireExecutor();
|
||||
private final ManagerFactory managerFactory;
|
||||
private final Logger logger = Logger.getLogger(TableManagerImpl.class);
|
||||
private final DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
|
||||
|
|
|
|||
|
|
@ -26,12 +26,15 @@ public class UserManagerImpl implements UserManager {
|
|||
private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status)
|
||||
private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list
|
||||
|
||||
private static final int SERVER_TIMEOUTS_USER_EXPIRE_CHECK_SECS = 60;
|
||||
private static final int SERVER_TIMEOUTS_USERS_LIST_UPDATE_SECS = 4; // server side updates (client use own timeouts to request users list)
|
||||
|
||||
private static final Logger logger = Logger.getLogger(UserManagerImpl.class);
|
||||
|
||||
protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
protected final ScheduledExecutorService userListExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private List<UserView> userInfoList = new ArrayList<>();
|
||||
private List<UserView> userInfoList = new ArrayList<>(); // all users list for main room/chat
|
||||
private final ManagerFactory managerFactory;
|
||||
|
||||
|
||||
|
|
@ -46,9 +49,8 @@ public class UserManagerImpl implements UserManager {
|
|||
|
||||
public void init() {
|
||||
USER_EXECUTOR = managerFactory.threadExecutor().getCallExecutor();
|
||||
expireExecutor.scheduleAtFixedRate(this::checkExpired, 60, 60, TimeUnit.SECONDS);
|
||||
|
||||
userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, 4, 4, TimeUnit.SECONDS);
|
||||
expireExecutor.scheduleAtFixedRate(this::checkExpired, SERVER_TIMEOUTS_USER_EXPIRE_CHECK_SECS, SERVER_TIMEOUTS_USER_EXPIRE_CHECK_SECS, TimeUnit.SECONDS);
|
||||
userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, SERVER_TIMEOUTS_USERS_LIST_UPDATE_SECS, SERVER_TIMEOUTS_USERS_LIST_UPDATE_SECS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -254,7 +256,7 @@ public class UserManagerImpl implements UserManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method recreated the user list that will be send to all clients
|
||||
* This method recreated the user list that will be sent to all clients
|
||||
*/
|
||||
private void updateUserInfoList() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ public class GameController implements GameCallback {
|
|||
gameFuture = gameExecutor.submit(worker);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
if (game.getState().getChoosingPlayerId() != null) {
|
||||
// start timer to force player to choose starting player otherwise loosing by being idle
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
|
|||
public GameSessionPlayer(ManagerFactory managerFactory, Game game, UUID userId, UUID playerId) {
|
||||
super(managerFactory.userManager(), userId, game, true);
|
||||
this.userManager = managerFactory.userManager();
|
||||
callExecutor = managerFactory.threadExecutor().getCallExecutor();
|
||||
this.callExecutor = managerFactory.threadExecutor().getCallExecutor();
|
||||
this.playerId = playerId;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,11 +45,11 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
UPDATE_EXECUTOR.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
update();
|
||||
} catch (Exception ex) {
|
||||
LOGGER.fatal("Games room update exception! " + ex.toString(), ex);
|
||||
} catch (Exception e) {
|
||||
LOGGER.fatal("Games room update error: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
}, 2, 2, TimeUnit.SECONDS);
|
||||
}, 2, 2, TimeUnit.SECONDS); // TODO: is it ok for performance?
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue