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:
Oleg Agafonov 2023-12-07 19:56:52 +03:00 committed by GitHub
parent 7f0558ff3c
commit 960e896903
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 1274 additions and 802 deletions

View file

@ -4,7 +4,6 @@ import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.constants.Constants;
import mage.game.Game;
import mage.server.exceptions.UserNotFoundException;
import mage.server.game.GameController;
import mage.server.managers.ChatManager;
import mage.server.managers.ManagerFactory;
@ -56,19 +55,13 @@ public class ChatManagerImpl implements ChatManager {
} else {
logger.trace("Chat to join not found - chatId: " + chatId + " userId: " + userId);
}
}
@Override
public void clearUserMessageStorage() {
lastUserMessages.clear();
}
@Override
public void leaveChat(UUID chatId, UUID userId) {
ChatSession chatSession = chatSessions.get(chatId);
if (chatSession != null && chatSession.hasUser(userId)) {
chatSession.kill(userId, DisconnectReason.CleaningUp);
if (chatSession != null && chatSession.hasUser(userId, false)) {
chatSession.disconnectUser(userId, DisconnectReason.DisconnectedByUser);
}
}
@ -321,41 +314,16 @@ public class ChatManagerImpl implements ChatManager {
return false;
}
/**
* use mainly for announcing that a user connection was lost or that a user
* has reconnected
*
* @param userId
* @param message
* @param color
* @throws mage.server.exceptions.UserNotFoundException
*/
@Override
public void broadcast(UUID userId, String message, MessageColor color) throws UserNotFoundException {
managerFactory.userManager().getUser(userId).ifPresent(user -> {
getChatSessions()
.stream()
.filter(chat -> chat.hasUser(userId))
.forEach(session -> session.broadcast(user.getName(), message, color, true, null, MessageType.TALK, null));
});
}
@Override
public void sendReconnectMessage(UUID userId) {
managerFactory.userManager().getUser(userId).ifPresent(user
-> getChatSessions()
.stream()
.filter(chat -> chat.hasUser(userId))
.filter(chat -> chat.hasUser(userId, true))
.forEach(chatSession -> chatSession.broadcast(null, user.getName() + " has reconnected", MessageColor.BLUE, true, null, MessageType.STATUS, null)));
}
@Override
public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) {
managerFactory.userManager().getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage()));
}
/**
* Send message to all active waiting/tourney/game chats (but not in main lobby)
*
@ -367,11 +335,11 @@ public class ChatManagerImpl implements ChatManager {
managerFactory.userManager().getUser(userId).ifPresent(user -> {
List<ChatSession> chatSessions = getChatSessions().stream()
.filter(chat -> !chat.getChatId().equals(managerFactory.gamesRoomManager().getMainChatId())) // ignore main lobby
.filter(chat -> chat.hasUser(userId))
.filter(chat -> chat.hasUser(userId, true))
.collect(Collectors.toList());
if (chatSessions.size() > 0) {
logger.info("INFORM OPPONENTS by " + user.getName() + ": " + message);
logger.debug("INFORM OPPONENTS by " + user.getName() + ": " + message);
chatSessions.forEach(chatSession -> chatSession.broadcast(null, message, MessageColor.BLUE, true, null, MessageType.STATUS, null));
}
});
@ -380,8 +348,8 @@ public class ChatManagerImpl implements ChatManager {
@Override
public void removeUser(UUID userId, DisconnectReason reason) {
for (ChatSession chatSession : getChatSessions()) {
if (chatSession.hasUser(userId)) {
chatSession.kill(userId, reason);
if (chatSession.hasUser(userId, false)) {
chatSession.disconnectUser(userId, reason);
}
}
}
@ -397,4 +365,25 @@ public class ChatManagerImpl implements ChatManager {
}
}
private void clearUserMessageStorage() {
lastUserMessages.clear();
}
@Override
public void checkHealth() {
//logger.info("cheching chats...");
// TODO: add broken chats check and report (with non existing userId)
/*
logger.info("total chats: " + chatSessions.size());
chatSessions.values().forEach(c -> {
logger.info("chat " + c.getInfo() + ", " + c.getChatId());
c.getClients().forEach((userId, userName) -> {
logger.info(" - " + userName + ", " + userId);
});
});
*/
clearUserMessageStorage();
}
}