improved error handling

main
Jonah Bauer 3 years ago
parent eef8f4ac40
commit 7649ff0b2c

@ -23,7 +23,7 @@ public class Client extends TimeoutContext<ClientState, Client> {
@Getter
@Setter
public boolean isError = false;
private boolean error = false;
public Client(WizardGame game) {
super(new Menu());

@ -5,56 +5,35 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
import eu.jonahbauer.wizard.client.libgdx.state.BaseState;
import eu.jonahbauer.wizard.client.libgdx.state.Lobby;
import eu.jonahbauer.wizard.client.libgdx.state.Session;
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
public class ErrorScreen extends MenuScreen {
private final String labelText;
private final String message;
private TextButton buttonBack;
private final ChangeListener listener = new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(BaseState.class, BaseState::dismissErrorScreen);
sfxClick();
}
}
};
public ErrorScreen(WizardGame game, Client client, NackMessage message) {
public ErrorScreen(WizardGame game, String message) {
super(game);
labelText = switch (message.getCode()) {
case NackMessage.GAME_ALREADY_STARTED -> messages.get("menu.error.game_already_started");
case NackMessage.SESSION_FULL -> messages.get("menu.error.session_full");
case NackMessage.SESSION_NOT_FOUND -> messages.get("menu.error.session_not_found");
case NackMessage.PLAYER_NAME_TAKEN -> messages.get("menu.error.player_name_taken");
case NackMessage.PLAYER_NAME_NOT_ALLOWED -> messages.get("menu.error.player_name_not_allowed");
case NackMessage.SESSION_NAME_TAKEN -> messages.get("menu.error.session_name_taken");
case NackMessage.SESSION_NAME_NOT_ALLOWED -> messages.get("menu.error.session_name_not_allowed");
case NackMessage.MALFORMED_MESSAGE -> messages.get("menu.error.malformed_message");
case NackMessage.UNEXPECTED_MESSAGE -> messages.get("menu.error.unexpected_message");
case NackMessage.ILLEGAL_ARGUMENT -> messages.get("menu.error.illegal_argument");
case NackMessage.NOT_FOUND -> messages.get("menu.error.not_found");
case NackMessage.ALREADY_CONNECTED -> messages.get("menu.error.already_connected");
case NackMessage.GAME_NOT_YET_STARTED -> messages.get("menu.error.game_not_yet_started");
case NackMessage.PLAYER_NOT_FOUND -> messages.get("menu.error.player_not_found");
case NackMessage.ILLEGAL_STATE -> messages.get("menu.error.illegal_state");
case NackMessage.BAD_REQUEST -> messages.get("menu.error.bad_request");
default -> "Something went terribly wrong :(";
};
this.message = message;
}
@Override
public void show() {
super.show();
var buttonBack = new TextButton(messages.get("menu.error.back"), skin);
var label = new Label(labelText, skin);
buttonBack = new TextButton(messages.get("menu.error.back"), skin);
var label = new Label(message, skin);
var content = new VerticalGroup();
content.setPosition(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT*0.5f);

@ -7,7 +7,6 @@ import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
import eu.jonahbauer.wizard.client.libgdx.listeners.ResetErrorListener;
import eu.jonahbauer.wizard.client.libgdx.state.Lobby;
import eu.jonahbauer.wizard.client.libgdx.state.Menu;
import eu.jonahbauer.wizard.common.messages.data.SessionData;
import lombok.extern.log4j.Log4j2;

@ -1,9 +1,9 @@
package eu.jonahbauer.wizard.client.libgdx.state;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import java.util.Optional;
@ -11,13 +11,9 @@ import java.util.Optional;
public abstract class Awaiting extends BaseState implements ClientState {
private static final int TIMEOUT_MILLIS = 10_000;
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
return unexpectedMessage(message);
}
@Override
@SneakyThrows
@MustBeInvokedByOverriders
public Optional<ClientState> onEnter(Client client) {
client.timeout(this, TIMEOUT_MILLIS);
return Optional.empty();

@ -2,6 +2,7 @@ package eu.jonahbauer.wizard.client.libgdx.state;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.screens.LoadingScreen;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import lombok.extern.log4j.Log4j2;
import java.util.Optional;
@ -12,22 +13,35 @@ public final class AwaitingConnection extends Awaiting {
@Override
public Optional<ClientState> onEnter(Client client) {
log.info("Awaiting connection...");
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.connecting"));
if (!client.isError()) showScreen(client);
return super.onEnter(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showScreen(client);
return Optional.empty();
}
@Override
public Optional<ClientState> onOpen(Client client) {
log.info("Connection established.");
return Optional.of(new AwaitingJoinLobby());
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
return unexpectedMessage(client, message);
}
@Override
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
// TODO user feedback
log.error("Connection could not be established. (code={}, reason={}, remote={})", code, reason, remote);
showErrorScreen(client, "Connection could not be established. (code=%d, reason=%s, remote=%b)".formatted(code, reason, remote));
return Optional.of(new Menu());
}
//public Optional<ClientState> showErrorScreen(Client client, )
private void showScreen(Client client) {
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.connecting"));
}
}

@ -32,11 +32,17 @@ public final class AwaitingGameLog extends BaseState {
@Override
public Optional<ClientState> onEnter(Client client) {
log.info("Waiting for game log...");
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.rejoining"));
if (!client.isError()) showScreen(client);
client.timeout(this, TIMEOUT_MILLIS);
return Optional.empty();
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showScreen(client);
return Optional.empty();
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
if (!started) {
@ -44,7 +50,7 @@ public final class AwaitingGameLog extends BaseState {
started = true;
return Optional.empty();
} else {
return unexpectedMessage(message);
return unexpectedMessage(client, message);
}
} else if (message instanceof GameMessage gameMessage) {
messages.add(gameMessage.getObserverMessage());
@ -54,7 +60,11 @@ public final class AwaitingGameLog extends BaseState {
game.init(messages);
return Optional.of(game);
} else {
return unexpectedMessage(message);
return unexpectedMessage(client, message);
}
}
private void showScreen(Client client) {
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.rejoining"));
}
}

@ -14,17 +14,27 @@ public final class AwaitingJoinLobby extends Awaiting {
@Override
public Optional<ClientState> onEnter(Client client) {
log.info("Waiting for session list...");
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_lobby"));
if (!client.isError()) showScreen(client);
return super.onEnter(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showScreen(client);
return Optional.empty();
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
if (message instanceof SessionListMessage list) {
log.info("There are {} open sessions.", list.getSessions().size());
return Optional.of(new Lobby(list.getSessions()));
} else {
return super.onMessage(client, message);
return unexpectedMessage(client, message);
}
}
private void showScreen(Client client) {
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_lobby"));
}
}

@ -29,16 +29,22 @@ public final class AwaitingJoinSession extends Awaiting {
@Override
public Optional<ClientState> onEnter(Client client) {
log.info("Waiting for acknowledgment...");
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_session"));
if (!client.isError()) showScreen(client);
return super.onEnter(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showScreen(client);
return Optional.empty();
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
if (message instanceof SessionJoinedMessage joined) {
var session = joined.getSession();
if (this.session != null && !this.session.equals(session)) {
return super.onMessage(client, message);
return unexpectedMessage(client, message);
} else {
var players = joined.getPlayers();
var player = joined.getPlayer();
@ -79,14 +85,19 @@ public final class AwaitingJoinSession extends Awaiting {
case NackMessage.SESSION_NAME_NOT_ALLOWED -> log.error("Session name not allowed.");
default -> log.error("Nack {}: {}", nack.getCode(), nack.getMessage());
}
showErrorScreen(client, nack.getMessage());
return Optional.of(new AwaitingJoinLobby());
} else if (message instanceof SessionModifiedMessage || message instanceof SessionRemovedMessage) {
// drop
log.debug("Dropped message {}.", message);
return Optional.empty();
} else {
return super.onMessage(client, message);
return unexpectedMessage(client, message);
}
}
private void showScreen(Client client) {
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_session"));
}
public enum Source {

@ -3,7 +3,6 @@ package eu.jonahbauer.wizard.client.libgdx.state;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.screens.ErrorScreen;
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import lombok.extern.log4j.Log4j2;
@ -32,17 +31,24 @@ public abstract class BaseState implements ClientState {
return Optional.of(new Menu());
}
protected static Optional<ClientState> unexpectedMessage(ServerMessage message) {
protected Optional<ClientState> unexpectedMessage(Client client, ServerMessage message) {
// return to menu on unexpected message
log.fatal("Unexpected message {}. Returning to menu.", message);
showErrorScreen(client, "Unexpected message %s. Returning to menu.".formatted(message));
return Optional.of(new Menu());
}
public Optional<ClientState> showErrorScreen(Client client, NackMessage nack) {
@Deprecated
public void showErrorScreen(Client client, String message) {
WizardGame game = client.getGame();
client.setError(true);
game.setScreen(new ErrorScreen(game, client, nack));
game.setScreen(new ErrorScreen(game, message));
}
return Optional.of(new Menu(client));
public Optional<ClientState> dismissErrorScreen(Client client) {
client.setError(false);
return onErrorDismissed(client);
}
protected abstract Optional<ClientState> onErrorDismissed(Client client);
}

@ -76,7 +76,7 @@ public final class Game extends BaseState {
@Override
public Optional<ClientState> onEnter(Client client) {
var out = handlePendingMessages();
var out = handlePendingMessages(client);
if (out.isPresent()) return out;
gameScreen = new GameScreen(client.getGame(), self, players);
@ -85,44 +85,47 @@ public final class Game extends BaseState {
return super.onEnter(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
return Optional.of(new Menu());
}
//<editor-fold desc="onMessage">
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
try {
if (message instanceof GameMessage game) {
var observerMessage = game.getObserverMessage();
return onMessage(observerMessage);
return onMessage(client, observerMessage);
} else if (message instanceof NackMessage nack) {
return onNackMessage(nack);
return onNackMessage(client, nack);
} else if (message instanceof AckMessage) {
onAckMessage();
return Optional.empty();
} else {
return unexpectedMessage(message);
return unexpectedMessage(client, message);
}
} finally {
executeDelayedFinishInteraction();
}
}
private Optional<ClientState> onMessage(ObserverMessage message) {
if (finishing != 0) return onMessageWhileFinishing(message);
private Optional<ClientState> onMessage(Client client, ObserverMessage message) {
if (finishing != 0) return onMessageWhileFinishing(client, message);
if (message instanceof StateMessage state) {
switch (state.getState()) {
case "starting_round" -> {
return onStartRound();
return onStartRound(client);
}
case "starting_trick" -> onStartTrick();
case "juggling" -> onJuggle();
case "finishing_round" -> onFinishingRound();
case "finished" -> {
onFinished();
return returnToSession();
return onFinished();
}
case "error" -> {
onError();
return returnToSession();
return onError(client);
}
}
} else if (message instanceof HandMessage hand) {
@ -142,12 +145,12 @@ public final class Game extends BaseState {
} else if (message instanceof TimeoutMessage) {
onTimeoutMessage();
} else {
return unexpectedMessage(new GameMessage(message));
return unexpectedMessage(client, new GameMessage(message));
}
return Optional.empty();
}
private Optional<ClientState> onMessageWhileFinishing(ObserverMessage message) {
private Optional<ClientState> onMessageWhileFinishing(Client client, ObserverMessage message) {
if (finishing == 1) { // last "finishing_round" has been received
if (message instanceof ScoreMessage score) {
onScoreMessage(score.getPoints(), false);
@ -166,13 +169,13 @@ public final class Game extends BaseState {
}
}
return unexpectedMessage(new GameMessage(message));
return unexpectedMessage(client, new GameMessage(message));
}
private Optional<ClientState> onStartRound() {
private Optional<ClientState> onStartRound(Client client) {
if (isLastRound()) {
log.fatal("Cannot start round {} with {} players", round + 1, players.size());
return unexpectedMessage(new GameMessage(new StateMessage("starting_round")));
return unexpectedMessage(client, new GameMessage(new StateMessage("starting_round")));
}
log.info("Round {} is starting...", round + 1);
@ -204,15 +207,18 @@ public final class Game extends BaseState {
if (isLastRound()) finishing = 1; // start finish procedure
}
private void onFinished() {
private Optional<ClientState> onFinished() {
log.info("The game has finished.");
if (gameScreen != null) {
gameScreen.showScoreOverlay(true);
}
return returnToSession();
}
private void onError() {
private Optional<ClientState> onError(Client client) {
log.error("The game has finished with an error.");
showErrorScreen(client, "The game has finished with an error.");
return returnToSession();
}
private void onHandMessage(@NotNull UUID player, @Unmodifiable @NotNull List<@NotNull Card> hand) {
@ -332,7 +338,7 @@ public final class Game extends BaseState {
if (gameScreen != null) gameScreen.timeout();
}
private Optional<ClientState> onNackMessage(@NotNull NackMessage nack) {
private Optional<ClientState> onNackMessage(Client client, @NotNull NackMessage nack) {
sending.set(false);
if (isActive() && currentInteraction.action() == JUGGLE_CARD && juggleCard != null) {
@ -348,7 +354,7 @@ public final class Game extends BaseState {
}
return Optional.empty();
} else {
return unexpectedMessage(nack);
return unexpectedMessage(client, nack);
}
}
@ -514,10 +520,10 @@ public final class Game extends BaseState {
}
//</editor-fold>
private Optional<ClientState> handlePendingMessages() {
private Optional<ClientState> handlePendingMessages(Client client) {
if (pendingMessages != null) {
for (var message : pendingMessages) {
var result = onMessage(message);
var result = onMessage(client, message);
if (result.isPresent()) {
return result;
}

@ -1,7 +1,6 @@
package eu.jonahbauer.wizard.client.libgdx.state;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.screens.*;
import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage;
import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage;
@ -18,8 +17,6 @@ import static eu.jonahbauer.wizard.client.libgdx.state.AwaitingJoinSession.Sourc
public final class Lobby extends BaseState {
private final Map<UUID, SessionData> sessions = new HashMap<>();
private Client client;
private LobbyScreen lobbyScreen;
public Lobby(Collection<SessionData> list) {
@ -28,11 +25,16 @@ public final class Lobby extends BaseState {
@Override
public Optional<ClientState> onEnter(Client client) {
showListScreen(client);
this.client = client;
if (!client.isError()) showListScreen(client);
return super.onEnter(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showListScreen(client);
return Optional.empty();
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
if (message instanceof SessionCreatedMessage created) {
@ -63,7 +65,7 @@ public final class Lobby extends BaseState {
}
return Optional.empty();
} else {
return unexpectedMessage(message);
return unexpectedMessage(client, message);
}
}

@ -22,17 +22,16 @@ public final class Menu extends BaseState {
if (client.getSocket() != null && client.getSocket().isOpen()) {
client.getSocket().close(CloseFrame.GOING_AWAY);
}
if (!client.isError()) showMenuScreen(client);
return super.onEnter(client);
}
public Menu(Client client) {
if(client.isError) {
//showErrorScreen(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showMenuScreen(client);
return Optional.empty();
}
public Menu() {}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
// it is possible that there are messages still queued after

@ -50,6 +50,12 @@ public final class Session extends BaseState {
return super.onEnter(client);
}
@Override
public Optional<ClientState> onErrorDismissed(Client client) {
showInfoScreen(client);
return Optional.empty();
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
if (message instanceof PlayerJoinedMessage join) {
@ -90,7 +96,7 @@ public final class Session extends BaseState {
sessionScreen.setSending(false);
return Optional.empty();
} else {
return unexpectedMessage(message);
return unexpectedMessage(client, message);
}
}

Loading…
Cancel
Save