From c6d2a9a6edacf65afd3752f8ef4e2ad6366ded37 Mon Sep 17 00:00:00 2001 From: drdev Date: Thu, 2 Oct 2014 10:31:37 +0000 Subject: [PATCH] Refactor event handling system to be much faster and more reliable --- .gitattributes | 3 + .../src/main/java/forge/GuiDesktop.java | 7 + .../src/main/java/forge/control/GuiTimer.java | 25 + forge-gui-mobile/src/forge/Forge.java | 4 +- forge-gui-mobile/src/forge/GuiMobile.java | 7 + .../src/forge/animation/ForgeAnimation.java | 44 +- .../src/forge/animation/GuiTimer.java | 35 ++ .../src/forge/util/WaitCallback.java | 4 +- .../control/FControlGameEventHandler.java | 446 ++++++------------ .../main/java/forge/interfaces/IGuiBase.java | 1 + .../main/java/forge/interfaces/IGuiTimer.java | 7 + .../src/main/java/forge/match/MatchUtil.java | 4 +- 12 files changed, 264 insertions(+), 323 deletions(-) create mode 100644 forge-gui-desktop/src/main/java/forge/control/GuiTimer.java create mode 100644 forge-gui-mobile/src/forge/animation/GuiTimer.java create mode 100644 forge-gui/src/main/java/forge/interfaces/IGuiTimer.java diff --git a/.gitattributes b/.gitattributes index 7779b268b4c..3a87076a93f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index 1fb102d4174..24d8996fcc8 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -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; } diff --git a/forge-gui-desktop/src/main/java/forge/control/GuiTimer.java b/forge-gui-desktop/src/main/java/forge/control/GuiTimer.java new file mode 100644 index 00000000000..97cbbb70bec --- /dev/null +++ b/forge-gui-desktop/src/main/java/forge/control/GuiTimer.java @@ -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); + } +} diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 605cea96e2a..b113ae35a7a 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -205,8 +205,8 @@ 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); currentScreen.onActivate(); diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 2107dcfae39..9806618705c 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -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; } diff --git a/forge-gui-mobile/src/forge/animation/ForgeAnimation.java b/forge-gui-mobile/src/forge/animation/ForgeAnimation.java index 74edffc2714..a7aeabf0f33 100644 --- a/forge-gui-mobile/src/forge/animation/ForgeAnimation.java +++ b/forge-gui-mobile/src/forge/animation/ForgeAnimation.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.Gdx; public abstract class ForgeAnimation { private static final List activeAnimations = new ArrayList(); + 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; + } - float dt = Gdx.graphics.getDeltaTime(); - for (int i = 0; i < activeAnimations.size(); i++) { - if (!activeAnimations.get(i).advance(dt)) { - activeAnimations.remove(i); - i--; + 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++) { + 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 diff --git a/forge-gui-mobile/src/forge/animation/GuiTimer.java b/forge-gui-mobile/src/forge/animation/GuiTimer.java new file mode 100644 index 00000000000..2a00ff11d87 --- /dev/null +++ b/forge-gui-mobile/src/forge/animation/GuiTimer.java @@ -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 + } +} diff --git a/forge-gui-mobile/src/forge/util/WaitCallback.java b/forge-gui-mobile/src/forge/util/WaitCallback.java index bede6d736e3..99987d3d639 100644 --- a/forge-gui-mobile/src/forge/util/WaitCallback.java +++ b/forge-gui-mobile/src/forge/util/WaitCallback.java @@ -14,7 +14,7 @@ public abstract class WaitCallback extends Callback implements Runnable { @Override public final void run(T result0) { result = result0; - synchronized(lock) { + synchronized (lock) { lock.notify(); } } @@ -23,7 +23,7 @@ public abstract class WaitCallback extends Callback implements Runnable { FThreads.assertExecutedByEdt(GuiBase.getInterface(), false); //not supported if on UI thread FThreads.invokeInEdtLater(GuiBase.getInterface(), this); try { - synchronized(lock) { + synchronized (lock) { lock.wait(); } } diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index d378e66aea0..7a6fee9a6d5 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -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 { - 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 eventQueue = new ConcurrentLinkedQueue(); + private final IGuiTimer processEventsTimer; + private final HashSet cardsUpdate = new HashSet(); + private final HashSet cardsRefreshDetails = new HashSet(); + private final HashSet livesUpdate = new HashSet(); + private final HashSet manaPoolUpdate = new HashSet(); + private final HashSet zonesUpdate = new HashSet(); - 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 - gameView.updateViews(); + 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 players = gameView.getPlayers(); + ArrayList> zones = new ArrayList>(); + 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 options = Lists.newArrayList(); for (final Entry 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 { 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> zonesToUpdate = new Vector>(); - 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 { 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 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 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 kv : event.blockers.values()) { for (Collection 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 { // 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 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); - } + updateZone(event.from); + 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 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 manaPoolUpdate = new Vector(); - 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); - } + manaPoolUpdate.add(event.player); return null; } - // Update lives counters - private final List livesUpdate = new Vector(); - 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); - } + livesUpdate.add(event.player); 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); - } + livesUpdate.add(event.receiver); return null; } } \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java index a05a9e8e53a..cea5dec746b 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java @@ -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); diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiTimer.java b/forge-gui/src/main/java/forge/interfaces/IGuiTimer.java new file mode 100644 index 00000000000..fb9668e2e8e --- /dev/null +++ b/forge-gui/src/main/java/forge/interfaces/IGuiTimer.java @@ -0,0 +1,7 @@ +package forge.interfaces; + +public interface IGuiTimer { + void setInterval(int interval0); + void start(); + void stop(); +} diff --git a/forge-gui/src/main/java/forge/match/MatchUtil.java b/forge-gui/src/main/java/forge/match/MatchUtil.java index 868ef6db4ef..cb825b098dd 100644 --- a/forge-gui/src/main/java/forge/match/MatchUtil.java +++ b/forge-gui/src/main/java/forge/match/MatchUtil.java @@ -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()) {