forked from External/mage
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);
80 lines
2.8 KiB
Java
80 lines
2.8 KiB
Java
package mage.util;
|
|
|
|
import com.google.common.base.Throwables;
|
|
|
|
import javax.swing.*;
|
|
import java.util.concurrent.CancellationException;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.Future;
|
|
|
|
/**
|
|
* Util method to work with threads.
|
|
*
|
|
* @author ayrat, JayDi85
|
|
*/
|
|
public final class ThreadUtils {
|
|
|
|
public static void sleep(int millis) {
|
|
try {
|
|
Thread.sleep(millis);
|
|
} catch (InterruptedException ignored) {
|
|
}
|
|
}
|
|
|
|
public static void wait(Object lock) {
|
|
synchronized (lock) {
|
|
try {
|
|
lock.wait();
|
|
} catch (InterruptedException ex) {
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find real exception object after thread task completed. Can be used in afterExecute
|
|
*/
|
|
public static Throwable findRunnableException(Runnable r, Throwable t) {
|
|
// executer.submit - return exception in result
|
|
// executer.execute - return exception in t
|
|
if (t == null && r instanceof Future<?>) {
|
|
try {
|
|
Object result = ((Future<?>) r).get();
|
|
} catch (CancellationException ce) {
|
|
t = ce;
|
|
} catch (ExecutionException ee) {
|
|
t = ee.getCause();
|
|
} catch (InterruptedException ie) {
|
|
Thread.currentThread().interrupt(); // ignore/reset
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
public static Throwable findRootException(Throwable t) {
|
|
return Throwables.getRootCause(t);
|
|
}
|
|
|
|
public static void ensureRunInGameThread() {
|
|
String name = Thread.currentThread().getName();
|
|
if (!name.startsWith("GAME")) {
|
|
// how-to fix: use signal logic to inform a game about new command to execute instead direct execute (see example with WantConcede)
|
|
// reason: user responses/commands are received by network/call thread, but must be processed by game thread
|
|
throw new IllegalArgumentException("Wrong code usage: game related code must run in GAME thread, but it used in " + name, new Throwable());
|
|
}
|
|
}
|
|
|
|
public static void ensureRunInCallThread() {
|
|
String name = Thread.currentThread().getName();
|
|
if (!name.startsWith("CALL")) {
|
|
// how-to fix: something wrong in your code logic
|
|
throw new IllegalArgumentException("Wrong code usage: client commands code must run in CALL threads, but used in " + name, new Throwable());
|
|
}
|
|
}
|
|
|
|
public static void ensureRunInGUISwingThread() {
|
|
if (!SwingUtilities.isEventDispatchThread()) {
|
|
// hot-to fix: run GUI changeable code by SwingUtilities.invokeLater(() -> {xxx})
|
|
throw new IllegalArgumentException("Wrong code usage: GUI related code must run in SWING thread by SwingUtilities.invokeLater", new Throwable());
|
|
}
|
|
}
|
|
}
|