forked from External/mage
Network upgrade and new reconnection mode (#11527)
Network upgrade and new reconnection mode: * users can disconnect or close app without game progress loose now; * disconnect dialog will show active tables stats and additional options; * all active tables will be restored on reconnect (tables, tourneys, games, drafts, sideboarding, constructing); * user must use same server and username on next connection; * there are few minutes for reconnect until server kick off a disconnected player from all player's tables (concede/loose); * now you can safety reconnect after IP change (after proxy/vpn/wifi/router restart); Other improvements and fixes: * gui: main menu - improved switch panel button, added stats about current tables/panels; * gui: improved data sync and updates (fixes many use cases with empty battlefield, not started games/drafts/tourneys, not updatable drafts, etc); * gui: improved stability on game updates (fixes some random errors related to wrong threads); * server: fixed miss messages about player's disconnection problems for other players in the chat; * refactor: simplified and improved connection and network related code, deleted outdated code, added docs; * tests: improved load test to support lands only set for more stable performance/network testing (set TEST_AI_RANDOM_DECK_SETS = PELP and run test_TwoAIPlayGame_Multiple);
This commit is contained in:
parent
7f0558ff3c
commit
960e896903
71 changed files with 1274 additions and 802 deletions
|
|
@ -124,6 +124,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
private static int startPort = -1;
|
||||
private static boolean debugMode = false;
|
||||
|
||||
private JToggleButton switchPanelsButton = null; // from main menu
|
||||
private static String SWITCH_PANELS_BUTTON_NAME = "Switch panels";
|
||||
|
||||
private static final Map<UUID, ChatPanelBasic> CHATS = new HashMap<>();
|
||||
private static final Map<UUID, GamePanel> GAMES = new HashMap<>();
|
||||
private static final Map<UUID, DraftPanel> DRAFTS = new HashMap<>();
|
||||
|
|
@ -189,9 +192,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new form MageFrame
|
||||
*/
|
||||
public MageFrame() throws MageException {
|
||||
File cacertsFile = new File(System.getProperty("user.dir") + "/release/cacerts").getAbsoluteFile();
|
||||
if (!cacertsFile.exists()) { // When running from the jar file the contents of the /release folder will have been expanded into the home folder as part of packaging
|
||||
|
|
@ -283,6 +283,23 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
|
||||
initComponents();
|
||||
|
||||
// auto-update switch panels button with actual stats
|
||||
desktopPane.addContainerListener(new ContainerAdapter() {
|
||||
@Override
|
||||
public void componentAdded(ContainerEvent e) {
|
||||
if (desktopPane.getLayer(e.getComponent()) == JLayeredPane.DEFAULT_LAYER) {
|
||||
updateSwitchPanelsButton();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentRemoved(ContainerEvent e) {
|
||||
if (desktopPane.getLayer(e.getComponent()) == JLayeredPane.DEFAULT_LAYER) {
|
||||
updateSwitchPanelsButton();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
desktopPane.setDesktopManager(new MageDesktopManager());
|
||||
|
||||
setSize(1024, 768);
|
||||
|
|
@ -303,8 +320,12 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
|
||||
updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel);
|
||||
|
||||
// create default server lobby and hide it until connect
|
||||
tablesPane = new TablesPane();
|
||||
desktopPane.add(tablesPane, javax.swing.JLayeredPane.DEFAULT_LAYER);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
this.hideServerLobby();
|
||||
});
|
||||
|
||||
addTooltipContainer();
|
||||
setBackground();
|
||||
|
|
@ -565,15 +586,27 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
|
||||
private AbstractButton createSwitchPanelsButton() {
|
||||
final JToggleButton switchPanelsButton = new JToggleButton("Switch panels");
|
||||
switchPanelsButton.addItemListener(e -> {
|
||||
this.switchPanelsButton = new JToggleButton(SWITCH_PANELS_BUTTON_NAME);
|
||||
this.switchPanelsButton.addItemListener(e -> {
|
||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||
createAndShowSwitchPanelsMenu((JComponent) e.getSource(), switchPanelsButton);
|
||||
createAndShowSwitchPanelsMenu((JComponent) e.getSource(), this.switchPanelsButton);
|
||||
}
|
||||
});
|
||||
switchPanelsButton.setFocusable(false);
|
||||
switchPanelsButton.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
return switchPanelsButton;
|
||||
this.switchPanelsButton.setFocusable(false);
|
||||
this.switchPanelsButton.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
return this.switchPanelsButton;
|
||||
}
|
||||
|
||||
private void updateSwitchPanelsButton() {
|
||||
if (this.switchPanelsButton != null) {
|
||||
int totalCount = getPanelsCount(false);
|
||||
int activeCount = getPanelsCount(true);
|
||||
this.switchPanelsButton.setText(SWITCH_PANELS_BUTTON_NAME + String.format(" (%d)", totalCount));
|
||||
this.switchPanelsButton.setToolTipText(String.format("Click to switch between panels (active panels: %d of %d)",
|
||||
activeCount,
|
||||
totalCount
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void createAndShowSwitchPanelsMenu(final JComponent component, final AbstractButton windowButton) {
|
||||
|
|
@ -762,17 +795,27 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
|
||||
public void showTournament(UUID tournamentId) {
|
||||
// existing tourney
|
||||
TournamentPane tournamentPane = null;
|
||||
for (Component component : desktopPane.getComponents()) {
|
||||
if (component instanceof TournamentPane
|
||||
&& ((TournamentPane) component).getTournamentId().equals(tournamentId)) {
|
||||
setActive((TournamentPane) component);
|
||||
return;
|
||||
tournamentPane = (TournamentPane) component;
|
||||
}
|
||||
}
|
||||
TournamentPane tournamentPane = new TournamentPane();
|
||||
desktopPane.add(tournamentPane, JLayeredPane.DEFAULT_LAYER);
|
||||
tournamentPane.setVisible(true);
|
||||
tournamentPane.showTournament(tournamentId);
|
||||
|
||||
// new tourney
|
||||
if (tournamentPane == null) {
|
||||
tournamentPane = new TournamentPane();
|
||||
desktopPane.add(tournamentPane, JLayeredPane.DEFAULT_LAYER);
|
||||
tournamentPane.setVisible(true);
|
||||
tournamentPane.showTournament(tournamentId);
|
||||
}
|
||||
|
||||
// if user connects on startup then there are possible multiple tables open, so keep only actual
|
||||
// priority: game > constructing > draft > tourney
|
||||
// TODO: activate panel by priority
|
||||
|
||||
setActive(tournamentPane);
|
||||
}
|
||||
|
||||
|
|
@ -843,7 +886,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
LOGGER.debug("connecting (auto): " + currentConnection.getProxyType().toString()
|
||||
+ ' ' + currentConnection.getProxyHost() + ' ' + currentConnection.getProxyPort() + ' ' + currentConnection.getProxyUsername());
|
||||
if (MageFrame.connect(currentConnection)) {
|
||||
prepareAndShowTablesPane();
|
||||
prepareAndShowServerLobby();
|
||||
return true;
|
||||
} else {
|
||||
showMessage("Unable connect to server: " + SessionHandler.getLastConnectError());
|
||||
|
|
@ -1075,10 +1118,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
|
||||
private void btnConnectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnConnectActionPerformed
|
||||
if (SessionHandler.isConnected()) {
|
||||
UserRequestMessage message = new UserRequestMessage("Confirm disconnect", "Are you sure you want to disconnect?");
|
||||
message.setButton1("No", null);
|
||||
message.setButton2("Yes", PlayerAction.CLIENT_DISCONNECT);
|
||||
showUserRequestDialog(message);
|
||||
tryDisconnectOrExit(false);
|
||||
} else {
|
||||
connectDialog.showDialog();
|
||||
setWindowTitle();
|
||||
|
|
@ -1148,15 +1188,34 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
|
||||
public void exitApp() {
|
||||
tryDisconnectOrExit(true);
|
||||
}
|
||||
|
||||
private void tryDisconnectOrExit(Boolean needExit) {
|
||||
String actionName = needExit ? "exit" : "disconnect";
|
||||
PlayerAction actionFull = needExit ? PlayerAction.CLIENT_EXIT_FULL : PlayerAction.CLIENT_DISCONNECT_FULL;
|
||||
PlayerAction actionKeepTables = needExit ? PlayerAction.CLIENT_EXIT_KEEP_GAMES : PlayerAction.CLIENT_DISCONNECT_KEEP_GAMES;
|
||||
double windowSizeRatio = 1.3;
|
||||
if (SessionHandler.isConnected()) {
|
||||
UserRequestMessage message = new UserRequestMessage("Confirm disconnect", "You are currently connected. Are you sure you want to disconnect?");
|
||||
message.setButton1("No", null);
|
||||
message.setButton2("Yes", PlayerAction.CLIENT_EXIT);
|
||||
int activeTables = MageFrame.getInstance().getPanelsCount(true);
|
||||
UserRequestMessage message = new UserRequestMessage(
|
||||
"Confirm " + actionName,
|
||||
"You are connected and has " + activeTables + " active table(s). You can quit from all your tables (concede) or ask server to wait a few minutes for reconnect. What to do?"
|
||||
);
|
||||
String totalInfo = (activeTables == 0 ? "" : String.format(" from %d table%s", activeTables, (activeTables > 1 ? "s" : "")));
|
||||
message.setButton1("Cancel", null);
|
||||
message.setButton2("Wait for me", actionKeepTables);
|
||||
message.setButton3("Quit" + totalInfo, actionFull);
|
||||
message.setWindowSizeRatio(windowSizeRatio);
|
||||
MageFrame.getInstance().showUserRequestDialog(message);
|
||||
} else {
|
||||
UserRequestMessage message = new UserRequestMessage("Confirm exit", "Are you sure you want to exit?");
|
||||
message.setButton1("No", null);
|
||||
message.setButton2("Yes", PlayerAction.CLIENT_EXIT);
|
||||
UserRequestMessage message = new UserRequestMessage(
|
||||
"Confirm " + actionName,
|
||||
"Are you sure you want to " + actionName + "?"
|
||||
);
|
||||
message.setButton1("Cancel", null);
|
||||
message.setButton2("Yes", actionFull);
|
||||
message.setWindowSizeRatio(windowSizeRatio);
|
||||
MageFrame.getInstance().showUserRequestDialog(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1171,17 +1230,18 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
btnDeckEditor.setEnabled(true);
|
||||
}
|
||||
|
||||
public void hideTables() {
|
||||
public void hideServerLobby() {
|
||||
this.tablesPane.hideTables();
|
||||
updateSwitchPanelsButton();
|
||||
}
|
||||
|
||||
public void setTableFilter() {
|
||||
public void setServerLobbyTablesFilter() {
|
||||
if (this.tablesPane != null) {
|
||||
this.tablesPane.setTableFilter();
|
||||
}
|
||||
}
|
||||
|
||||
public void prepareAndShowTablesPane() {
|
||||
public void prepareAndShowServerLobby() {
|
||||
// Update the tables pane with the new session
|
||||
this.tablesPane.showTables();
|
||||
|
||||
|
|
@ -1191,6 +1251,8 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
if (topPanebefore != null && topPanebefore != tablesPane) {
|
||||
setActive(topPanebefore);
|
||||
}
|
||||
|
||||
updateSwitchPanelsButton();
|
||||
}
|
||||
|
||||
public void hideGames() {
|
||||
|
|
@ -1253,14 +1315,18 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
|
||||
public void showUserRequestDialog(final UserRequestMessage userRequestMessage) {
|
||||
final UserRequestDialog userRequestDialog = new UserRequestDialog();
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
innerShowUserRequestDialog(userRequestMessage);
|
||||
} else {
|
||||
SwingUtilities.invokeLater(() -> innerShowUserRequestDialog(userRequestMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private void innerShowUserRequestDialog(final UserRequestMessage userRequestMessage) {
|
||||
UserRequestDialog userRequestDialog = new UserRequestDialog();
|
||||
userRequestDialog.setLocation(100, 100);
|
||||
desktopPane.add(userRequestDialog, JLayeredPane.MODAL_LAYER);
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
userRequestDialog.showDialog(userRequestMessage);
|
||||
} else {
|
||||
SwingUtilities.invokeLater(() -> userRequestDialog.showDialog(userRequestMessage));
|
||||
}
|
||||
userRequestDialog.showDialog(userRequestMessage);
|
||||
}
|
||||
|
||||
public void showErrorDialog(final String title, final String message) {
|
||||
|
|
@ -1478,50 +1544,55 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
DRAFTS.put(draftId, draftPanel);
|
||||
}
|
||||
|
||||
public BalloonTip getBalloonTip() {
|
||||
return balloonTip;
|
||||
/**
|
||||
* Return total number of panels/frames (game panel, deck editor panel, etc)
|
||||
*
|
||||
* @param onlyActive return only active panels (related to online like game panel, but not game viewer)
|
||||
* @return
|
||||
*/
|
||||
public int getPanelsCount(boolean onlyActive) {
|
||||
return (int) Arrays.stream(this.desktopPane.getComponentsInLayer(javax.swing.JLayeredPane.DEFAULT_LAYER))
|
||||
.filter(Component::isVisible)
|
||||
.filter(p -> p instanceof MagePane)
|
||||
.map(p -> (MagePane) p)
|
||||
.filter(p-> !onlyActive || p.isActiveTable())
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(final String message) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setConnectButtonText(message);
|
||||
enableButtons();
|
||||
} else {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setConnectButtonText(message);
|
||||
enableButtons();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(final boolean askToReconnect) {
|
||||
if (SwingUtilities.isEventDispatchThread()) { // Returns true if the current thread is an AWT event dispatching thread.
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
// REMOTE task, e.g. connecting
|
||||
LOGGER.info("Disconnected from remote task");
|
||||
LOGGER.info("Disconnected from server side");
|
||||
} else {
|
||||
// USER mode, e.g. user plays and got disconnect
|
||||
LOGGER.info("Disconnected from client side");
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// user already disconnected, can't do any online actions like quite chat
|
||||
// but try to keep session
|
||||
// TODO: why it ignore askToReconnect here, but use custom reconnect dialog later?! Need research
|
||||
SessionHandler.disconnect(false, true);
|
||||
setConnectButtonText(NOT_CONNECTED_BUTTON);
|
||||
disableButtons();
|
||||
hideGames();
|
||||
hideTables();
|
||||
} else {
|
||||
// USER mode, e.g. user plays and got disconnect
|
||||
LOGGER.info("Disconnected from user mode");
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
SessionHandler.disconnect(false); // user already disconnected, can't do any online actions like quite chat
|
||||
setConnectButtonText(NOT_CONNECTED_BUTTON);
|
||||
disableButtons();
|
||||
hideGames();
|
||||
hideTables();
|
||||
if (askToReconnect) {
|
||||
UserRequestMessage message = new UserRequestMessage("Connection lost", "The connection to server was lost. Reconnect to " + MagePreferences.getLastServerAddress() + "?");
|
||||
message.setButton1("No", null);
|
||||
message.setButton2("Yes", PlayerAction.CLIENT_RECONNECT);
|
||||
showUserRequestDialog(message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
hideServerLobby();
|
||||
if (askToReconnect) {
|
||||
UserRequestMessage message = new UserRequestMessage("Connection lost", "The connection to server was lost. Reconnect to " + MagePreferences.getLastServerAddress() + "?");
|
||||
message.setButton1("No", null);
|
||||
message.setButton2("Yes", PlayerAction.CLIENT_RECONNECT);
|
||||
showUserRequestDialog(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1539,8 +1610,13 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void processCallback(ClientCallback callback) {
|
||||
callbackClient.processCallback(callback);
|
||||
public void onCallback(ClientCallback callback) {
|
||||
callbackClient.onCallback(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewConnection() {
|
||||
callbackClient.onNewConnection();
|
||||
}
|
||||
|
||||
public void sendUserReplay(PlayerAction playerAction, UserRequestMessage userRequestMessage) {
|
||||
|
|
@ -1551,13 +1627,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
case CLIENT_DOWNLOAD_CARD_IMAGES:
|
||||
DownloadPicturesService.startDownload();
|
||||
break;
|
||||
case CLIENT_DISCONNECT:
|
||||
if (SessionHandler.isConnected()) {
|
||||
SessionHandler.disconnect(false);
|
||||
}
|
||||
tablesPane.clearChat();
|
||||
showMessage("You have disconnected");
|
||||
setWindowTitle();
|
||||
case CLIENT_DISCONNECT_FULL:
|
||||
doClientDisconnect(false, "You have disconnected");
|
||||
break;
|
||||
case CLIENT_DISCONNECT_KEEP_GAMES:
|
||||
doClientDisconnect(true, "You have disconnected and have few minutes to reconnect");
|
||||
break;
|
||||
case CLIENT_QUIT_TOURNAMENT:
|
||||
SessionHandler.quitTournament(userRequestMessage.getTournamentId());
|
||||
|
|
@ -1580,15 +1654,13 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
removeGame(userRequestMessage.getGameId());
|
||||
break;
|
||||
case CLIENT_EXIT:
|
||||
if (SessionHandler.isConnected()) {
|
||||
SessionHandler.disconnect(false);
|
||||
}
|
||||
CardRepository.instance.closeDB();
|
||||
tablesPane.cleanUp();
|
||||
Plugins.instance.shutdown();
|
||||
dispose();
|
||||
System.exit(0);
|
||||
case CLIENT_EXIT_FULL:
|
||||
doClientDisconnect(false, "");
|
||||
doClientShutdownAndExit();
|
||||
break;
|
||||
case CLIENT_EXIT_KEEP_GAMES:
|
||||
doClientDisconnect(true, "");
|
||||
doClientShutdownAndExit();
|
||||
break;
|
||||
case CLIENT_REMOVE_TABLE:
|
||||
SessionHandler.removeTable(userRequestMessage.getRoomId(), userRequestMessage.getTableId());
|
||||
|
|
@ -1609,6 +1681,26 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
}
|
||||
|
||||
private void doClientDisconnect(boolean keepMySessionActive, String afterMessage) {
|
||||
if (SessionHandler.isConnected()) {
|
||||
SessionHandler.disconnect(false, keepMySessionActive);
|
||||
}
|
||||
tablesPane.clearChat();
|
||||
setWindowTitle();
|
||||
|
||||
if (!afterMessage.isEmpty()) {
|
||||
showMessage(afterMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void doClientShutdownAndExit() {
|
||||
tablesPane.cleanUp();
|
||||
CardRepository.instance.closeDB();
|
||||
Plugins.instance.shutdown();
|
||||
dispose();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private void endTables() {
|
||||
for (UUID gameId : GAMES.keySet()) {
|
||||
SessionHandler.quitMatch(gameId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue