Refactor event handling system to be much faster and more reliable

This commit is contained in:
drdev
2014-10-02 10:31:37 +00:00
parent 75adde6794
commit c6d2a9a6ed
12 changed files with 264 additions and 323 deletions

3
.gitattributes vendored
View File

@@ -727,6 +727,7 @@ forge-gui-desktop/src/main/java/forge/ImageCache.java -text
forge-gui-desktop/src/main/java/forge/ImageLoader.java -text
forge-gui-desktop/src/main/java/forge/Singletons.java -text
forge-gui-desktop/src/main/java/forge/control/FControl.java -text
forge-gui-desktop/src/main/java/forge/control/GuiTimer.java -text
forge-gui-desktop/src/main/java/forge/control/KeyboardShortcuts.java -text
forge-gui-desktop/src/main/java/forge/control/RestartUtil.java -text
forge-gui-desktop/src/main/java/forge/control/package-info.java -text
@@ -1134,6 +1135,7 @@ forge-gui-mobile/src/forge/animation/AbilityEffect.java -text
forge-gui-mobile/src/forge/animation/ForgeAnimation.java -text
forge-gui-mobile/src/forge/animation/GifAnimation.java -text
forge-gui-mobile/src/forge/animation/GifDecoder.java -text
forge-gui-mobile/src/forge/animation/GuiTimer.java -text
forge-gui-mobile/src/forge/assets/AssetsDownloader.java -text
forge-gui-mobile/src/forge/assets/BitmapFontWriter.java -text
forge-gui-mobile/src/forge/assets/FBufferedImage.java -text
@@ -16963,6 +16965,7 @@ forge-gui/src/main/java/forge/interfaces/ICheckBox.java -text
forge-gui/src/main/java/forge/interfaces/IComboBox.java -text
forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java -text
forge-gui/src/main/java/forge/interfaces/IGuiBase.java -text
forge-gui/src/main/java/forge/interfaces/IGuiTimer.java -text
forge-gui/src/main/java/forge/interfaces/IProgressBar.java -text
forge-gui/src/main/java/forge/interfaces/ITextField.java -text
forge-gui/src/main/java/forge/interfaces/IWinLoseView.java -text

View File

@@ -21,6 +21,7 @@ import com.google.common.base.Function;
import forge.assets.FSkinProp;
import forge.assets.ISkinImage;
import forge.control.GuiTimer;
import forge.deck.CardPool;
import forge.error.BugReportDialog;
import forge.game.GameObject;
@@ -30,6 +31,7 @@ import forge.gui.CardListViewer;
import forge.gui.GuiChoose;
import forge.gui.framework.FScreen;
import forge.interfaces.IGuiBase;
import forge.interfaces.IGuiTimer;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.screens.deckeditor.CDeckEditorUI;
@@ -93,6 +95,11 @@ public class GuiDesktop implements IGuiBase {
return SwingUtilities.isEventDispatchThread();
}
@Override
public IGuiTimer createGuiTimer(Runnable proc, int interval) {
return new GuiTimer(proc, interval);
}
@Override
public ISkinImage getSkinIcon(FSkinProp skinProp) {
if (skinProp == null) { return null; }

View File

@@ -0,0 +1,25 @@
package forge.control;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
import forge.interfaces.IGuiTimer;
@SuppressWarnings("serial")
public class GuiTimer extends Timer implements IGuiTimer {
public GuiTimer(final Runnable proc0, int interval0) {
super(interval0, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
proc0.run();
}
});
}
@Override
public void setInterval(int interval0) {
setDelay(interval0);
}
}

View File

@@ -205,7 +205,7 @@ public class Forge implements ApplicationListener {
private static void setCurrentScreen(FScreen screen0) {
try {
endKeyInput(); //end key input before switching screens
ForgeAnimation.endAll(); //end all active animations before switching screens
ForgeAnimation.stopAll(true); //end all active animations before switching screens
currentScreen = screen0;
currentScreen.setSize(screenWidth, screenHeight);

View File

@@ -9,6 +9,7 @@ import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.graphics.Texture;
import com.google.common.base.Function;
import forge.animation.GuiTimer;
import forge.assets.FBufferedImage;
import forge.assets.FDelayLoadImage;
import forge.assets.FImage;
@@ -24,6 +25,7 @@ import forge.deck.FSideboardDialog;
import forge.error.BugReportDialog;
import forge.game.player.IHasIcon;
import forge.interfaces.IGuiBase;
import forge.interfaces.IGuiTimer;
import forge.item.PaperCard;
import forge.properties.ForgeConstants;
import forge.screens.match.MatchController;
@@ -87,6 +89,11 @@ public class GuiMobile implements IGuiBase {
return !ThreadUtil.isGameThread();
}
@Override
public IGuiTimer createGuiTimer(Runnable proc, int interval) {
return new GuiTimer(proc, interval);
}
@Override
public ISkinImage getSkinIcon(FSkinProp skinProp) {
if (skinProp == null) { return null; }

View File

@@ -7,6 +7,7 @@ import com.badlogic.gdx.Gdx;
public abstract class ForgeAnimation {
private static final List<ForgeAnimation> activeAnimations = new ArrayList<ForgeAnimation>();
private boolean stopped;
public void start() {
if (activeAnimations.contains(this)) { return; } //prevent starting the same animation multiple times
@@ -14,22 +15,43 @@ public abstract class ForgeAnimation {
activeAnimations.add(this);
}
public static void advanceAll() {
if (activeAnimations.isEmpty()) { return; }
protected boolean stopWhenScreenChanges() {
return true;
}
public void stop() {
stopped = true; //will be removed on the next iteration
}
public static void advanceAll() {
synchronized (activeAnimations) {
if (!activeAnimations.isEmpty()) {
float dt = Gdx.graphics.getDeltaTime();
for (int i = 0; i < activeAnimations.size(); i++) {
if (!activeAnimations.get(i).advance(dt)) {
ForgeAnimation animation = activeAnimations.get(i);
if (animation.stopped || !animation.advance(dt)) {
animation.stopped = true;
activeAnimations.remove(i);
i--;
}
}
}
}
}
public static void endAll() {
if (activeAnimations.isEmpty()) { return; }
activeAnimations.clear();
public static void stopAll(boolean forScreenChange) {
synchronized (activeAnimations) {
if (!activeAnimations.isEmpty()) {
for (int i = 0; i < activeAnimations.size(); i++) {
ForgeAnimation animation = activeAnimations.get(i);
if (!forScreenChange || animation.stopWhenScreenChanges()) {
animation.stopped = true;
activeAnimations.remove(i);
i--;
}
}
}
}
}
//return true if animation should continue, false to stop the animation

View File

@@ -0,0 +1,35 @@
package forge.animation;
import forge.interfaces.IGuiTimer;
//implement a GuiTimer as an animation since it can utilize the same logic for advancing
public class GuiTimer extends ForgeAnimation implements IGuiTimer {
private final Runnable proc;
private float elapsed;
private int interval;
public GuiTimer(Runnable proc0, int interval0) {
proc = proc0;
interval = interval0;
}
@Override
public void setInterval(int interval0) {
interval = interval0;
}
@Override
protected boolean advance(float dt) {
elapsed += dt * 1000; //convert seconds to milliseconds
if (elapsed >= interval) {
elapsed = 0;
proc.run();
}
return true;
}
@Override
protected boolean stopWhenScreenChanges() {
return false; //don't stop timers just because screen changed
}
}

View File

@@ -1,22 +1,16 @@
package forge.control;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import forge.FThreads;
import forge.GuiBase;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.event.GameEvent;
@@ -52,6 +46,8 @@ import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.interfaces.IGuiTimer;
import forge.match.IMatchController;
import forge.match.MatchUtil;
import forge.match.input.ButtonUtil;
import forge.match.input.InputBase;
@@ -59,7 +55,6 @@ import forge.model.FModel;
import forge.player.GamePlayerUtil;
import forge.properties.ForgePreferences.FPref;
import forge.util.Lang;
import forge.util.ThreadUtil;
import forge.util.gui.SGuiChoose;
import forge.util.maps.MapOfLists;
import forge.view.CardView;
@@ -67,115 +62,141 @@ import forge.view.LocalGameView;
import forge.view.PlayerView;
public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
private static FControlGameEventHandler mainHandler;
private static final String PLAYER_ZONE_DELIM = "|";
private static FControlGameEventHandler instance;
public static void processEventsAfterInput() {
if (mainHandler.processEventsSchedule != null) {
mainHandler.processEventsSchedule.cancel(false); //cancel any currently scheduled processing
}
mainHandler.processEventsDelay = 10; //speed up event processing until an event has been raised
mainHandler.processEventsSchedule = ThreadUtil.delay(mainHandler.processEventsDelay, mainHandler.processEvents);
instance.processEventsTimer.setInterval(10); //speed up event processing until an event has been raised
}
private final LocalGameView gameView;
private final boolean isMainHandler;
private final ConcurrentLinkedQueue<GameEvent> eventQueue = new ConcurrentLinkedQueue<GameEvent>();
private final IGuiTimer processEventsTimer;
private final HashSet<Card> cardsUpdate = new HashSet<Card>();
private final HashSet<Card> cardsRefreshDetails = new HashSet<Card>();
private final HashSet<Player> livesUpdate = new HashSet<Player>();
private final HashSet<Player> manaPoolUpdate = new HashSet<Player>();
private final HashSet<String> zonesUpdate = new HashSet<String>();
public FControlGameEventHandler(final LocalGameView gameView0, final boolean isMainHandler0) {
private boolean eventReceived, needPhaseUpdate, needCombatUpdate, needStackUpdate, needPlayerControlUpdate;
private boolean gameOver, gameFinished;
private Player turnUpdate;
public FControlGameEventHandler(final LocalGameView gameView0) {
gameView = gameView0;
isMainHandler = isMainHandler0;
if (isMainHandler) {
mainHandler = this;
}
processEvents.run(); //start event processing loop
instance = this;
processEventsTimer = GuiBase.getInterface().createGuiTimer(processEvents, 100);
processEventsTimer.start(); //start event processing loop
}
private ScheduledFuture<?> processEventsSchedule;
private int processEventsDelay = 100;
private final Runnable processEvents = new Runnable() {
@Override
public void run() {
if (!eventQueue.isEmpty()) {
processEventsDelay = 100; //reset to 100 once an event has been raised
synchronized (processEventsTimer) {
if (eventReceived) {
processEventsTimer.setInterval(100); //reset to 100 once an event has been raised
gameView.updateViews();
GameEvent ev;
while ((ev = eventQueue.poll()) != null) {
ev.visit(FControlGameEventHandler.this);
IMatchController controller = MatchUtil.getController();
if (!cardsUpdate.isEmpty()) {
MatchUtil.updateCards(gameView.getCardViews(cardsUpdate));
cardsUpdate.clear();
}
if (!cardsRefreshDetails.isEmpty()) {
controller.refreshCardDetails(gameView.getCardViews(cardsRefreshDetails));
cardsRefreshDetails.clear();
}
if (!livesUpdate.isEmpty()) {
controller.updateLives(gameView.getPlayerViews(livesUpdate));
livesUpdate.clear();
}
if (!manaPoolUpdate.isEmpty()) {
controller.updateManaPool(gameView.getPlayerViews(manaPoolUpdate));
manaPoolUpdate.clear();
}
if (!zonesUpdate.isEmpty()) {
List<PlayerView> players = gameView.getPlayers();
ArrayList<Pair<PlayerView, ZoneType>> zones = new ArrayList<Pair<PlayerView, ZoneType>>();
for (String z : zonesUpdate) {
int idx = z.indexOf(PLAYER_ZONE_DELIM);
zones.add(Pair.of(players.get(Integer.parseInt(z.substring(0, idx))), ZoneType.valueOf(z.substring(idx + 1))));
}
controller.updateZones(zones);
zonesUpdate.clear();
}
if (turnUpdate != null) {
controller.updateTurn(gameView.getPlayerView(turnUpdate));
turnUpdate = null;
}
if (needPhaseUpdate) {
needPhaseUpdate = false;
controller.updatePhase();
}
if (needCombatUpdate) {
needCombatUpdate = false;
gameView.refreshCombat();
controller.showCombat(gameView.getCombat());
}
if (needStackUpdate) {
needStackUpdate = false;
controller.updateStack();
}
if (needPlayerControlUpdate) {
needPlayerControlUpdate = false;
controller.updatePlayerControl();
}
if (gameOver) {
gameOver = false;
gameView.getInputQueue().onGameOver(true); // this will unlock any game threads waiting for inputs to complete
}
if (gameFinished) {
gameFinished = false;
PlayerView localPlayer = gameView.getLocalPlayerView();
InputBase.cancelAwaitNextInput(); //ensure "Waiting for opponent..." doesn't appear behind WinLo
controller.showPromptMessage(localPlayer, ""); //clear prompt behind WinLose overlay
ButtonUtil.update(localPlayer, "", "", false, false, false);
controller.finishGame();
gameView.updateAchievements();
processEventsTimer.stop();
instance = null; //this can be reset after game is finished
}
}
}
processEventsSchedule = ThreadUtil.delay(processEventsDelay, this); //only process events 10 times per second by default to improve performance
}
};
@Subscribe
public void receiveGameEvent(final GameEvent ev) {
eventQueue.add(ev);
synchronized (processEventsTimer) { //ensure multiple events aren't processed at the same time
eventReceived = true;
ev.visit(this);
}
}
private final AtomicBoolean phaseUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(final GameEventTurnPhase ev) {
if (!isMainHandler || phaseUpdPlanned.getAndSet(true)) { return null; }
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
@Override
public void run() {
phaseUpdPlanned.set(false);
MatchUtil.getController().updatePhase();
}
});
needPhaseUpdate = true;
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventPlayerPriority)
*/
private final AtomicBoolean combatUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(GameEventPlayerPriority event) {
updateCombat();
needCombatUpdate = true;
return null;
}
private void updateCombat() {
if (!isMainHandler || combatUpdPlanned.getAndSet(true)) { return; }
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
@Override
public void run() {
combatUpdPlanned.set(false);
MatchUtil.getController().showCombat(gameView.getCombat());
}
});
}
private final AtomicBoolean turnUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(final GameEventTurnBegan event) {
if (!isMainHandler) { return null; }
turnUpdate = event.turnOwner;
if (FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_CREATURES) && event.turnOwner != null) {
// anything except stack will get here
updateZone(Pair.of(gameView.getPlayerView(event.turnOwner), ZoneType.Battlefield));
updateZone(event.turnOwner, ZoneType.Battlefield);
}
if (turnUpdPlanned.getAndSet(true)) { return null; }
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
@Override
public void run() {
turnUpdPlanned.set(false);
MatchUtil.getController().updateTurn(gameView.getPlayerView(event.turnOwner));
}
});
return null;
}
@Override
public Void visit(GameEventAnteCardsSelected ev) {
if (!isMainHandler) { return null; }
final List<CardView> options = Lists.newArrayList();
for (final Entry<Player, Card> kv : ev.cards.entries()) {
final CardView fakeCard = new CardView(-1); //use fake card so real cards appear with proper formatting
@@ -192,111 +213,51 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
if (ev.player.getGame().isGameOver()) {
return null;
}
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
@Override
public void run() {
MatchUtil.getController().updatePlayerControl();
}
});
needPlayerControlUpdate = true;
return null;
}
private final Runnable unlockGameThreadOnGameOver = new Runnable() {
@Override
public void run() {
gameView.getInputQueue().onGameOver(true); // this will unlock any game threads waiting for inputs to complete
}
};
@Override
public Void visit(GameEventGameOutcome ev) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), unlockGameThreadOnGameOver);
gameOver = true;
return null;
}
@Override
public Void visit(GameEventGameFinished ev) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
@Override
public void run() {
PlayerView localPlayer = gameView.getLocalPlayerView();
InputBase.cancelAwaitNextInput(); //ensure "Waiting for opponent..." doesn't appear behind WinLo
MatchUtil.getController().showPromptMessage(localPlayer, ""); //clear prompt behind WinLose overlay
ButtonUtil.update(localPlayer, "", "", false, false, false);
if (isMainHandler) {
MatchUtil.getController().finishGame();
gameView.updateAchievements();
mainHandler = null; //this can be reset after game is finished
}
}
});
gameFinished = true;
return null;
}
private final AtomicBoolean stackUpdPlanned = new AtomicBoolean(false);
private final Runnable updStack = new Runnable() {
@Override
public void run() {
stackUpdPlanned.set(false);
MatchUtil.getController().updateStack();
}
};
@Override
public Void visit(GameEventSpellAbilityCast event) {
if (!isMainHandler) { return null; }
if (!stackUpdPlanned.getAndSet(true)) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updStack);
}
needStackUpdate = true;
return null;
}
@Override
public Void visit(GameEventSpellResolved event) {
if (!isMainHandler) { return null; }
if (!stackUpdPlanned.getAndSet(true)) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updStack);
}
needStackUpdate = true;
return null;
}
@Override
public Void visit(GameEventSpellRemovedFromStack event) {
if (!isMainHandler) { return null; }
if (!stackUpdPlanned.getAndSet(true)) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updStack);
}
needStackUpdate = true;
return null;
}
private final List<Pair<PlayerView, ZoneType>> zonesToUpdate = new Vector<Pair<PlayerView,ZoneType>>();
private final Runnable updZones = new Runnable() {
@Override
public void run() {
synchronized (zonesToUpdate) {
MatchUtil.getController().updateZones(zonesToUpdate);
zonesToUpdate.clear();
}
}
};
@Override
public Void visit(GameEventZone event) {
if (!isMainHandler) { return null; }
if (event.player != null) {
// anything except stack will get here
updateZone(Pair.of(gameView.getPlayerView(event.player), event.zoneType));
updateZone(event.player, event.zoneType);
}
return null;
}
@Override
public Void visit(GameEventCardAttachment event) {
if (!isMainHandler) { return null; }
final Game game = event.equipment.getGame();
final PlayerZone zEq = (PlayerZone)game.getZoneOf(event.equipment);
if (event.oldEntiy instanceof Card) {
@@ -305,82 +266,55 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
if (event.newTarget instanceof Card) {
updateZone(game.getZoneOf((Card)event.newTarget));
}
return updateZone(zEq);
}
private Void updateZone(final Zone z) {
return updateZone(Pair.of(gameView.getPlayerView(z.getPlayer()), z.getZoneType()));
}
private Void updateZone(final Pair<PlayerView, ZoneType> kv) {
boolean needUpdate = false;
synchronized (zonesToUpdate) {
needUpdate = zonesToUpdate.isEmpty();
if (!zonesToUpdate.contains(kv)) {
zonesToUpdate.add(kv);
}
}
if (needUpdate) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updZones);
}
updateZone(zEq);
return null;
}
private final Set<CardView> cardsToUpdate = Sets.newHashSet();
private final Runnable updCards = new Runnable() {
@Override
public void run() {
synchronized (cardsToUpdate) {
MatchUtil.updateCards(cardsToUpdate);
cardsToUpdate.clear();
private void updateZone(final Zone z) {
if (z == null) { return; }
updateZone(z.getPlayer(), z.getZoneType());
}
private void updateZone(Player p, ZoneType z) {
if (p == null || z == null) { return; }
zonesUpdate.add(p.getId() + PLAYER_ZONE_DELIM + z.name());
}
};
@Override
public Void visit(final GameEventCardTapped event) {
if (!isMainHandler) { return null; }
return updateSingleCard(gameView.getCardView(event.card));
cardsUpdate.add(event.card);
return null;
}
@Override
public Void visit(final GameEventCardPhased event) {
if (!isMainHandler) { return null; }
return updateSingleCard(gameView.getCardView(event.card));
cardsUpdate.add(event.card);
return null;
}
@Override
public Void visit(final GameEventCardDamaged event) {
if (!isMainHandler) { return null; }
return updateSingleCard(gameView.getCardView(event.card));
cardsUpdate.add(event.card);
return null;
}
@Override
public Void visit(final GameEventCardCounters event) {
if (!isMainHandler) { return null; }
return updateSingleCard(gameView.getCardView(event.card));
cardsUpdate.add(event.card);
return null;
}
@Override
public Void visit(final GameEventBlockersDeclared event) { // This is to draw icons on blockers declared by AI
if (!isMainHandler) { return null; }
for (MapOfLists<Card, Card> kv : event.blockers.values()) {
for (Collection<Card> blockers : kv.values()) {
updateManyCards(gameView.getCardViews(blockers));
cardsUpdate.addAll(blockers);
}
}
return super.visit(event);
return null;
}
@Override
public Void visit(GameEventAttackersDeclared event) {
if (!isMainHandler) { return null; }
// Skip redraw for GUI player?
if (event.player.getLobbyPlayer() == GamePlayerUtil.getGuiPlayer()) {
return null;
@@ -388,169 +322,69 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
// Update all attackers.
// Although they might have been updated when they were tapped, there could be someone with vigilance, not redrawn yet.
updateManyCards(gameView.getCardViews(event.attackersMap.values()));
return super.visit(event);
cardsUpdate.addAll(event.attackersMap.values());
return null;
}
@Override
public Void visit(GameEventCombatChanged event) {
gameView.refreshCombat();
updateCombat();
needCombatUpdate = true;
return null;
}
@Override
public Void visit(GameEventCombatEnded event) {
if (!isMainHandler) { return null; }
gameView.refreshCombat();
updateCombat();
needCombatUpdate = true;
// This should remove sword/shield icons from combatants by the time game moves to M2
updateManyCards(gameView.getCardViews(event.attackers));
updateManyCards(gameView.getCardViews(event.blockers));
return null;
}
private Void updateSingleCard(final CardView c) {
boolean needUpdate = false;
synchronized (cardsToUpdate) {
needUpdate = cardsToUpdate.isEmpty();
if (!cardsToUpdate.contains(c)) {
cardsToUpdate.add(c);
}
}
if (needUpdate) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updCards);
}
return null;
}
private Void updateManyCards(final Iterable<CardView> cc) {
boolean needUpdate = false;
synchronized (cardsToUpdate) {
needUpdate = cardsToUpdate.isEmpty();
Iterables.addAll(cardsToUpdate, cc);
}
if (needUpdate) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updCards);
}
cardsUpdate.addAll(event.attackers);
cardsUpdate.addAll(event.blockers);
return null;
}
@Override
public Void visit(GameEventCardChangeZone event) {
if (!isMainHandler) { return null; }
if (event.from != null) {
updateZone(event.from);
}
if (event.to != null) {
updateZone(event.to);
}
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventCardStatsChanged)
*/
@Override
public Void visit(GameEventCardStatsChanged event) {
if (!isMainHandler) { return null; }
final Iterable<CardView> cardViews = gameView.getCardViews(event.cards);
MatchUtil.getController().refreshCardDetails(cardViews);
return updateManyCards(cardViews);
cardsRefreshDetails.addAll(event.cards);
cardsUpdate.addAll(event.cards);
return null;
}
@Override
public Void visit(GameEventPlayerStatsChanged event) {
if (!isMainHandler) { return null; }
for (final Player p : event.players) {
MatchUtil.getController().refreshCardDetails(gameView.getCardViews(p.getAllCards()));
cardsRefreshDetails.addAll(p.getAllCards());
}
return null;
}
@Override
public Void visit(GameEventShuffle event) {
if (!isMainHandler) { return null; }
updateZone(event.player.getZone(ZoneType.Library));
return null;
}
// Update manapool
private final List<Player> manaPoolUpdate = new Vector<Player>();
private final Runnable updManaPool = new Runnable() {
@Override public void run() {
synchronized (manaPoolUpdate) {
MatchUtil.getController().updateManaPool(gameView.getPlayerViews(manaPoolUpdate));
manaPoolUpdate.clear();
}
}
};
@Override
public Void visit(GameEventManaPool event) {
if (!isMainHandler) { return null; }
boolean invokeUpdate = false;
synchronized (manaPoolUpdate) {
if (!manaPoolUpdate.contains(event.player)) {
invokeUpdate = manaPoolUpdate.isEmpty();
manaPoolUpdate.add(event.player);
}
}
if (invokeUpdate) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updManaPool);
}
return null;
}
// Update lives counters
private final List<Player> livesUpdate = new Vector<Player>();
private final Runnable updLives = new Runnable() {
@Override public void run() {
synchronized (livesUpdate) {
MatchUtil.getController().updateLives(gameView.getPlayerViews(livesUpdate));
livesUpdate.clear();
}
}
};
@Override
public Void visit(GameEventPlayerLivesChanged event) {
if (!isMainHandler) { return null; }
boolean invokeUpdate = false;
synchronized (livesUpdate) {
if (!livesUpdate.contains(event.player)) {
invokeUpdate = livesUpdate.isEmpty();
livesUpdate.add(event.player);
}
}
if (invokeUpdate) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updLives);
}
return null;
}
@Override
public Void visit(GameEventPlayerPoisoned event) {
if (!isMainHandler) { return null; }
boolean invokeUpdate = false;
synchronized (livesUpdate) {
if (!livesUpdate.contains(event.receiver)) {
invokeUpdate = livesUpdate.isEmpty();
livesUpdate.add(event.receiver);
}
}
if (invokeUpdate) {
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updLives);
}
return null;
}
}

View File

@@ -23,6 +23,7 @@ public interface IGuiBase {
void invokeInEdtLater(Runnable runnable);
void invokeInEdtAndWait(final Runnable proc);
boolean isGuiThread();
IGuiTimer createGuiTimer(Runnable proc, int interval);
ISkinImage getSkinIcon(FSkinProp skinProp);
ISkinImage getUnskinnedIcon(String path);
ISkinImage createLayeredImage(FSkinProp background, String overlayFilename, float opacity);

View File

@@ -0,0 +1,7 @@
package forge.interfaces;
public interface IGuiTimer {
void setInterval(int interval0);
void start();
void stop();
}

View File

@@ -182,8 +182,8 @@ public class MatchUtil {
LocalGameView gameView = controller.getGameView();
if (humanCount == 0) {
currentPlayer = p;
game.subscribeToEvents(new FControlGameEventHandler(gameView));
}
game.subscribeToEvents(new FControlGameEventHandler(gameView, humanCount == 0));
gameViews.add(gameView);
humanCount++;
}
@@ -193,7 +193,7 @@ public class MatchUtil {
LocalGameView gameView = new WatchLocalGame(GuiBase.getInterface(), game);
currentPlayer = sortedPlayers.get(0);
gameView.setLocalPlayer(currentPlayer);
game.subscribeToEvents(new FControlGameEventHandler(gameView, true));
game.subscribeToEvents(new FControlGameEventHandler(gameView));
gameViews.add(gameView);
}
else if (humanCount == sortedPlayers.size() && controller.hotSeatMode()) {