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
|
|
@ -10,7 +10,6 @@ import mage.view.ChatMessage.MessageType;
|
|||
import mage.view.ChatMessage.SoundToPlay;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
|
@ -19,89 +18,86 @@ import java.util.concurrent.locks.ReadWriteLock;
|
|||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public class ChatSession {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ChatSession.class);
|
||||
private static final DateFormat timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT);
|
||||
|
||||
private final ManagerFactory managerFactory;
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock(); // TODO: no needs due ConcurrentHashMap usage?
|
||||
|
||||
private final ConcurrentMap<UUID, String> clients = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<UUID, String> users = new ConcurrentHashMap<>(); // active users
|
||||
private final Set<UUID> usersHistory = new HashSet<>(); // all users that was here (need for system messages like connection problem)
|
||||
private final UUID chatId;
|
||||
private final Date createTime;
|
||||
private final String info;
|
||||
|
||||
public ChatSession(ManagerFactory managerFactory, String info) {
|
||||
this.managerFactory = managerFactory;
|
||||
chatId = UUID.randomUUID();
|
||||
this.chatId = UUID.randomUUID();
|
||||
this.createTime = new Date();
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void join(UUID userId) {
|
||||
managerFactory.userManager().getUser(userId).ifPresent(user -> {
|
||||
if (!clients.containsKey(userId)) {
|
||||
if (!users.containsKey(userId)) {
|
||||
String userName = user.getName();
|
||||
final Lock w = lock.writeLock();
|
||||
w.lock();
|
||||
try {
|
||||
clients.put(userId, userName);
|
||||
users.put(userId, userName);
|
||||
usersHistory.add(userId);
|
||||
} finally {
|
||||
w.unlock();
|
||||
}
|
||||
broadcast(null, userName + " has joined (" + user.getClientVersion() + ')', MessageColor.BLUE, true, null, MessageType.STATUS, null);
|
||||
logger.trace(userName + " joined chat " + chatId);
|
||||
broadcast(null, userName + " has joined", MessageColor.BLUE, true, null, MessageType.STATUS, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void kill(UUID userId, DisconnectReason reason) {
|
||||
public void disconnectUser(UUID userId, DisconnectReason reason) {
|
||||
// user will reconnect to all chats, so no needs to keep it
|
||||
// if you kill session then kill all chats too
|
||||
|
||||
try {
|
||||
if (reason == null) {
|
||||
logger.fatal("User kill without disconnect reason userId: " + userId);
|
||||
reason = DisconnectReason.Undefined;
|
||||
String userName = users.getOrDefault(userId, null);
|
||||
if (userName == null) {
|
||||
return;
|
||||
}
|
||||
if (userId != null && clients.containsKey(userId)) {
|
||||
String userName = clients.get(userId);
|
||||
if (reason != DisconnectReason.LostConnection) { // for lost connection the user will be reconnected or session expire so no removeUserFromAllTablesAndChat of chat yet
|
||||
final Lock w = lock.writeLock();
|
||||
w.lock();
|
||||
try {
|
||||
clients.remove(userId);
|
||||
} finally {
|
||||
w.unlock();
|
||||
}
|
||||
logger.debug(userName + '(' + reason.toString() + ')' + " removed from chatId " + chatId);
|
||||
}
|
||||
String message = reason.getMessage();
|
||||
|
||||
if (!message.isEmpty()) {
|
||||
broadcast(null, userName + message, MessageColor.BLUE, true, null, MessageType.STATUS, null);
|
||||
}
|
||||
// remove from chat
|
||||
final Lock w = lock.writeLock();
|
||||
w.lock();
|
||||
try {
|
||||
users.remove(userId);
|
||||
} finally {
|
||||
w.unlock();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.fatal("exception: " + ex.toString());
|
||||
logger.debug(userName + " (" + reason + ')' + " removed from chatId " + chatId);
|
||||
|
||||
// inform other users about disconnect (lobby, system tab)
|
||||
if (!reason.messageForUser.isEmpty()) {
|
||||
broadcast(null, userName + reason.messageForUser, MessageColor.BLUE, true, null, MessageType.STATUS, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.fatal("Chat: disconnecting user catch error: " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean broadcastInfoToUser(User toUser, String message) {
|
||||
if (clients.containsKey(toUser.getId())) {
|
||||
public void broadcastInfoToUser(User toUser, String message) {
|
||||
if (users.containsKey(toUser.getId())) {
|
||||
toUser.fireCallback(new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId,
|
||||
new ChatMessage(null, message, new Date(), null, MessageColor.BLUE, MessageType.USER_INFO, null)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean broadcastWhisperToUser(User fromUser, User toUser, String message) {
|
||||
if (clients.containsKey(toUser.getId())) {
|
||||
if (users.containsKey(toUser.getId())) {
|
||||
toUser.fireCallback(new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId,
|
||||
new ChatMessage(fromUser.getName(), message, new Date(), null, MessageColor.YELLOW, MessageType.WHISPER_FROM, SoundToPlay.PlayerWhispered)));
|
||||
if (clients.containsKey(fromUser.getId())) {
|
||||
if (users.containsKey(fromUser.getId())) {
|
||||
fromUser.fireCallback(new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId,
|
||||
new ChatMessage(toUser.getName(), message, new Date(), null, MessageColor.YELLOW, MessageType.WHISPER_TO, null)));
|
||||
return true;
|
||||
|
|
@ -111,6 +107,10 @@ public class ChatSession {
|
|||
}
|
||||
|
||||
public void broadcast(String userName, String message, MessageColor color, boolean withTime, Game game, MessageType messageType, SoundToPlay soundToPlay) {
|
||||
// TODO: is it called by single thread for all users?
|
||||
// TODO: is it freeze on someone's connection fail/freeze?
|
||||
// TODO: is it freeze on someone's connection fail/freeze with play multiple games/chats/lobby?
|
||||
// TODO: send messages in another thread?!
|
||||
if (!message.isEmpty()) {
|
||||
Set<UUID> clientsToRemove = new HashSet<>();
|
||||
ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId,
|
||||
|
|
@ -119,7 +119,7 @@ public class ChatSession {
|
|||
final Lock r = lock.readLock();
|
||||
r.lock();
|
||||
try {
|
||||
chatUserIds.addAll(clients.keySet());
|
||||
chatUserIds.addAll(users.keySet());
|
||||
} finally {
|
||||
r.unlock();
|
||||
}
|
||||
|
|
@ -135,12 +135,11 @@ public class ChatSession {
|
|||
final Lock w = lock.readLock();
|
||||
w.lock();
|
||||
try {
|
||||
clients.keySet().removeAll(clientsToRemove);
|
||||
users.keySet().removeAll(clientsToRemove);
|
||||
} finally {
|
||||
w.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,12 +150,12 @@ public class ChatSession {
|
|||
return chatId;
|
||||
}
|
||||
|
||||
public boolean hasUser(UUID userId) {
|
||||
return clients.containsKey(userId);
|
||||
public boolean hasUser(UUID userId, boolean useHistory) {
|
||||
return useHistory ? usersHistory.contains(userId) : users.containsKey(userId);
|
||||
}
|
||||
|
||||
public ConcurrentMap<UUID, String> getClients() {
|
||||
return clients;
|
||||
public ConcurrentMap<UUID, String> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue