- Delegate network game communication to a new GameProtocol class

- Rename a method to make it compatible with the new protocol
This commit is contained in:
elcnesh
2015-04-22 17:55:58 +00:00
parent 038ddbba67
commit 91d9987c1f
15 changed files with 237 additions and 242 deletions

View File

@@ -11,6 +11,6 @@ public interface IButton {
void setSelected(boolean b0);
boolean requestFocusInWindow();
void setCommand(UiCommand command0);
void setTextColor(FSkinProp color);
void setImage(FSkinProp color);
void setTextColor(int r, int g, int b);
}

View File

@@ -3,6 +3,7 @@ package forge.match;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -85,10 +86,8 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
}
@Override
public void updateCards(final Iterable<CardView> cards) {
for (final CardView card : cards) {
updateSingleCard(card);
}
public final void updateSingleCard(final CardView card) {
updateCards(Collections.singleton(card));
}
@Override

View File

@@ -0,0 +1,136 @@
package forge.net;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function;
import forge.UiCommand;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
import forge.game.GameEntityView;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IButton;
import forge.interfaces.IGuiGame;
import forge.match.MatchButtonType;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
public final class GameProtocol {
/**
* Private constructor to prevent instantiation.
*/
private GameProtocol() {
}
public static ProtocolMethod getProtocolMethod(final String name) {
return ProtocolMethod.valueOf(name);
}
/**
* The methods that can be sent through this protocol.
*/
public enum ProtocolMethod {
setGameView(Void.TYPE, GameView.class),
openView(Void.TYPE, TrackableCollection.class),
afterGameEnd(),
showCombat(),
showPromptMessage(Void.TYPE, PlayerView.class, String.class),
stopAtPhase(Boolean.TYPE, PlayerView.class, PhaseType.class),
focusButton(Void.TYPE, MatchButtonType.class),
flashIncorrectAction(),
updatePhase(),
updateTurn(Void.TYPE, PlayerView.class),
updatePlayerControl(),
enableOverlay(),
disableOverlay(),
finishGame(),
showManaPool(Void.TYPE, PlayerView.class),
hideManaPool(Void.TYPE, PlayerView.class),
updateStack(),
updateZones(Void.TYPE, Iterable.class),
updateCards(Void.TYPE, Iterable.class),
updateManaPool(Void.TYPE, Iterable.class),
updateLives(Void.TYPE, Iterable.class),
setPanelSelection(Void.TYPE, CardView.class),
getAbilityToPlay(SpellAbilityView.class, List.class, ITriggerEvent.class),
assignDamage(Map.class, CardView.class, List.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE),
message(Void.TYPE, String.class, String.class),
showErrorDialog(Void.TYPE, String.class, String.class),
showConfirmDialog(Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE),
showOptionDialog(Integer.TYPE, String.class, String.class, FSkinProp.class, Array.class, Integer.TYPE),
showCardOptionDialog(Integer.TYPE, CardView.class, String.class, String.class, FSkinProp.class, String.class, Array.class),
showInputDialog(String.class, String.class, String.class, FSkinProp.class, String.class, Array.class),
confirm(Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, Array.class),
getChoices(List.class, String.class, Integer.TYPE, Integer.TYPE, Collection.class, Object.class, Function.class),
order(List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE),
sideboard(List.class, CardPool.class, CardPool.class),
chooseSingleEntityForEffect(GameEntityView.class, String.class, TrackableCollection.class, DelayedReveal.class, Boolean.TYPE),
setCard(Void.TYPE, CardView.class),
// TODO case "setPlayerAvatar":
openZones(Boolean.TYPE, Collection.class, Map.class),
restoreOldZones(Void.TYPE, Map.class),
isUiSetToSkipPhase(Boolean.TYPE, PlayerView.class, PhaseType.class),
// BUTTONS
btn_setEnabled(Void.TYPE, Boolean.TYPE),
btn_setVisible(Void.TYPE, Boolean.TYPE),
btn_setText(Void.TYPE, String.class),
btn_isSelected(Boolean.TYPE),
btn_setSelected(Void.TYPE, Boolean.TYPE),
btn_requestFocusInWindows(Boolean.TYPE),
btn_setCommand(Void.TYPE, UiCommand.class),
btn_setImage(Void.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE),
btn_setTextImage(Void.TYPE, FSkinProp.class);
private final Class<?> returnType;
private final Class<?>[] args;
private ProtocolMethod() {
this(Void.TYPE);
}
private ProtocolMethod(final Class<?> returnType) {
this(returnType, (Class<?>[]) null);
}
@SafeVarargs
private ProtocolMethod(final Class<?> returnType, final Class<?> ... args) {
this.returnType = returnType;
this.args = args == null ? new Class<?>[] {} : args;
}
public Method getMethod() {
try {
final String name;
final Class<?> toCall;
if (name().startsWith("btn_")) {
name = name().substring("btn_".length());
toCall = IButton.class;
} else {
name = name();
toCall = IGuiGame.class;
}
final Method candidate = toCall.getMethod(name, args);
if (!candidate.getReturnType().equals(returnType)) {
throw new NoSuchMethodException(String.format("Wrong return type for method %s", name()));
}
return candidate;
} catch (final NoSuchMethodException | SecurityException e) {
System.err.println(String.format("Warning: class contains no method named %s", name()));
return null;
}
}
public boolean invokeOnButton() {
return name().startsWith("btn_");
}
}
}

View File

@@ -1,37 +1,24 @@
package forge.net.client;
import forge.FThreads;
import forge.UiCommand;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
import forge.game.GameEntityView;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.interfaces.IButton;
import forge.interfaces.IGuiGame;
import forge.match.MatchButtonType;
import forge.model.FModel;
import forge.net.GameProtocol;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.event.GuiGameEvent;
import forge.net.event.LoginEvent;
import forge.net.event.ReplyEvent;
import forge.player.PlayerZoneUpdates;
import forge.properties.ForgePreferences.FPref;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
class GameClientHandler extends ChannelInboundHandlerAdapter {
private final FGameClient client;
@@ -60,201 +47,66 @@ class GameClientHandler extends ChannelInboundHandlerAdapter {
client.getReplyPool().complete(event.getIndex(), event.getReply());
} else if (msg instanceof GuiGameEvent) {
final GuiGameEvent event = (GuiGameEvent) msg;
final String method = event.getMethod();
final Object[] args = event.getObjects();
Serializable reply = null;
boolean doReply = false;
final String methodName = event.getMethod();
final IButton btn;
if (method.startsWith("btn_") && args.length >= 2 && args[0] instanceof PlayerView && args[1] instanceof MatchButtonType) {
btn = args[1] == MatchButtonType.OK ? gui.getBtnOK((PlayerView) args[0]) : gui.getBtnCancel((PlayerView) args[0]);
} else {
btn = null;
final Object[] originalArgs = event.getObjects();
final ProtocolMethod protocolMethod = GameProtocol.getProtocolMethod(methodName);
if (protocolMethod == null) {
throw new IllegalStateException(String.format("Protocol method %s unknown", methodName));
}
final Method method = protocolMethod.getMethod();
if (method == null) {
throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
}
switch (method) {
case "setGameView":
gui.setGameView((GameView) args[0]);
break;
case "openView":
final Object toInvoke;
final Object[] args;
if (protocolMethod.invokeOnButton()) {
toInvoke = originalArgs[1] == MatchButtonType.OK ? gui.getBtnOK((PlayerView) originalArgs[0]) : gui.getBtnCancel((PlayerView) originalArgs[0]);
// Remove the player and button type from the args passed to the method
args = Arrays.copyOfRange(originalArgs, 2, originalArgs.length);
} else {
toInvoke = gui;
args = originalArgs;
}
// Pre-call actions
switch (protocolMethod) {
case openView:
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
client.setGameControllers(myPlayers);
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
gui.openView(myPlayers);
}
});
break;
case "afterGameEnd":
gui.afterGameEnd();
break;
case "showCombat":
gui.showCombat();
break;
case "showPromptMessage":
gui.showPromptMessage((PlayerView) args[0], (String) args[1]);
break;
case "stopAtPhase":
reply = gui.stopAtPhase((PlayerView) args[0], (PhaseType) args[1]);
doReply = true;
break;
case "focusButton":
gui.focusButton((MatchButtonType) args[0]);
break;
case "flashIncorrectAction":
gui.flashIncorrectAction();
break;
case "updatePhase":
gui.updatePhase();
break;
case "updateTurn":
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
gui.updateTurn((PlayerView) args[0]);
}
});
break;
case "udpdatePlayerControl":
gui.updatePlayerControl();
break;
case "enableOverlay":
gui.enableOverlay();
break;
case "disbleOverlay":
gui.disableOverlay();
break;
case "finishGame":
gui.finishGame();
break;
case "showManaPool":
gui.showManaPool((PlayerView) args[0]);
break;
case "hideManaPool":
gui.hideManaPool((PlayerView) args[0], args[1]);
break;
case "updateStack":
gui.updateStack();
break;
case "updateZones":
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
gui.updateZones((PlayerZoneUpdates) args[0]);
}
});
break;
case "updateSingleCard":
gui.updateSingleCard((CardView) args[0]);
break;
case "updateManaPool":
gui.updateManaPool((Iterable<PlayerView>) args[0]);
break;
case "updateLives":
gui.updateLives((Iterable<PlayerView>) args[0]);
break;
case "setPanelSelection":
gui.setPanelSelection((CardView) args[0]);
break;
case "getAbilityToPlay":
reply = gui.getAbilityToPlay((List<SpellAbilityView>) args[0], (ITriggerEvent) args[1]);
doReply = true;
break;
case "assignDamage":
reply = (Serializable) gui.assignDamage((CardView) args[0], (List<CardView>) args[1], (int) args[2], (GameEntityView) args[3], (boolean) args[4]);
doReply = true;
break;
case "message":
gui.message((String) args[0], (String) args[1]);
break;
case "showErrorDialog":
gui.showErrorDialog((String) args[0], (String) args[1]);
break;
case "showConfirmDialog":
reply = gui.showConfirmDialog((String) args[0], (String) args[1], (String) args[2], (String) args[3], (boolean) args[4]);
doReply = true;
break;
case "showOptionDialog":
reply = gui.showOptionDialog((String) args[0], (String) args[1], (FSkinProp) args[2], (String[]) args[3], (int) args[4]);
doReply = true;
break;
case "showCardOptionDialog":
reply = gui.showCardOptionDialog((CardView) args[0], (String) args[1], (String) args[2], (FSkinProp) args[3], (String[]) args[4], (int) args[5]);
doReply = true;
break;
case "showInputDialog":
reply = gui.showInputDialog((String) args[0], (String) args[1], (FSkinProp) args[2], (String) args[3], (String[]) args[4]);
doReply = true;
break;
case "confirm":
reply = gui.confirm((CardView) args[0], (String) args[1], (boolean) args[2], (String[]) args[3]);
doReply = true;
break;
case "getChoices":
reply = (Serializable) gui.getChoices((String) args[0], (int) args[1], (int) args[2], (Collection<Object>) args[3], args[4], (Function<Object, String>) args[5]);
doReply = true;
break;
case "order":
reply = (Serializable) gui.order((String) args[0], (String) args[1], (int) args[2], (int) args[3], (List<Object>) args[4], (List<Object>) args[5], (CardView) args[6], (boolean) args[7]);
doReply = true;
break;
case "sideboard":
reply = (Serializable) gui.sideboard((CardPool) args[0], (CardPool) args[1]);
doReply = true;
break;
case "chooseSingleEntityForEffect":
reply = gui.chooseSingleEntityForEffect((String) args[0], (TrackableCollection<GameEntityView>) args[1], (DelayedReveal) args[2], (boolean) args[3]);
doReply = true;
break;
case "setCard":
gui.setCard((CardView) args[0]);
break;
// TODO case "setPlayerAvatar":
case "openZones":
reply = gui.openZones((Collection<ZoneType>) args[0], (Map<PlayerView, Object>) args[1]);
doReply = true;
break;
case "restoreOldZones":
gui.restoreOldZones((Map<PlayerView, Object>) args[0]);
break;
case "isUiSetToSkipPhase":
reply = gui.isUiSetToSkipPhase((PlayerView) args[0], (PhaseType) args[1]);
doReply = true;
break;
// BUTTONS
case "btn_setEnabled":
btn.setEnabled((boolean) args[2]);
break;
case "btn_setVisible":
btn.setVisible((boolean) args[2]);
break;
case "btn_setText":
btn.setText((String) args[2]);
break;
case "btn_isSelected":
reply = btn.isSelected();
doReply = true;
break;
case "btn_setSelected":
btn.setSelected((boolean) args[2]);
break;
case "btn_requestFocusInWindows":
reply = btn.requestFocusInWindow();
doReply = true;
break;
case "btn_setCommand":
btn.setCommand((UiCommand) args[2]);
break;
case "btn_setTextColor":
if (args.length == 3) {
btn.setTextColor((FSkinProp) args[2]);
} else {
btn.setTextColor((int) args[2], (int) args[3], (int) args[4]);
}
break;
default:
System.err.println("Unsupported game event " + event.getMethod());
break;
}
if (doReply) {
final Class<?> returnType = method.getReturnType();
if (returnType.equals(Void.TYPE)) {
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
try {
method.invoke(toInvoke, args);
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
}
});
} else {
Serializable reply = null;
try {
final Object theReply = method.invoke(toInvoke, args);
if (theReply instanceof Serializable) {
reply = (Serializable) method.invoke(toInvoke, args);
} else {
System.err.println(String.format("Non-serializable return type %s for method %s", returnType.getName(), methodName));
}
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
client.send(new ReplyEvent(event.getId(), reply));
}
}

View File

@@ -126,7 +126,7 @@ public final class FServerManager {
}
}).start();
mapNatPort(port);
//mapNatPort(port);
Runtime.getRuntime().addShutdownHook(shutdownHook);
isHosting = true;
} catch (final InterruptedException e) {

View File

@@ -188,9 +188,9 @@ public class NetGuiGame extends AbstractGuiGame {
}
@Override
public void updateSingleCard(final CardView card) {
public void updateCards(final Iterable<CardView> cards) {
updateGameView();
send(methodName(), card);
send(methodName(), cards);
}
@Override
@@ -366,7 +366,7 @@ public class NetGuiGame extends AbstractGuiGame {
}
@Override
public void setTextColor(final FSkinProp color) {
public void setImage(final FSkinProp color) {
send("btn_" + methodName(), playerView, type, color);
}

View File

@@ -339,7 +339,7 @@ public class QuestUtil {
}
else {
view.getLblZep().setEnabled(true);
view.getLblZep().setTextColor(FSkinProp.CLR_TEXT);
view.getLblZep().setImage(FSkinProp.CLR_TEXT);
}
}
else {
@@ -413,7 +413,7 @@ public class QuestUtil {
lblCurrentDeck.setText("Build, then select a deck in the \"Quest Decks\" submenu.");
}
else {
lblCurrentDeck.setTextColor(FSkinProp.CLR_TEXT);
lblCurrentDeck.setImage(FSkinProp.CLR_TEXT);
lblCurrentDeck.setText("Your current deck is \""
+ getCurrentDeck().getName() + "\".");
}