Switch game thread executor to be a single-thread

executor instead, of a thread pool. This is needed
because when things need to execute on the game
thread, it's not thread safe so only one such thing
should be executing at a time.

This should fix ConcurrentModificationException
from CardAreaPanel.selectCard() on mobile.

This is a reland of r34680 with a fix for being able
to run tasks on the game thread while waiting for input
from the user. Specifically, tested paying costs and
using the dev panel, but should work with anything that
schedules tasks via GameAction.invoke().
This commit is contained in:
Myrd
2017-07-13 05:03:34 +00:00
parent 7bc6761502
commit 1d3f91fb9c
3 changed files with 55 additions and 11 deletions

View File

@@ -1,25 +1,34 @@
package forge.match.input;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import com.google.common.base.Function;
import forge.FThreads;
import forge.error.BugReporter;
import forge.player.PlayerControllerHuman;
import forge.util.ThreadUtil;
public abstract class InputSyncronizedBase extends InputBase implements InputSynchronized {
private static final long serialVersionUID = 8756177361251703052L;
private final CountDownLatch cdlDone;
private static final Runnable terminationMarker = new Runnable() { public void run() { } };
// The gameTaskQueue indicates tasks to run while blocked on the game. To stop, add terminationMarker (as null is
// not allowed).
private LinkedBlockingDeque<Runnable> gameTaskQueue = new LinkedBlockingDeque<Runnable>();
public InputSyncronizedBase(final PlayerControllerHuman controller) {
super(controller);
cdlDone = new CountDownLatch(1);
}
@Override
public void awaitLatchRelease() {
FThreads.assertExecutedByEdt(false);
try{
cdlDone.await();
try {
Runnable r = gameTaskQueue.take();
while (r != terminationMarker) {
r.run();
r = gameTaskQueue.take();
}
} catch (final InterruptedException e) {
BugReporter.reportException(e);
}
@@ -27,12 +36,34 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn
@Override
public final void relaseLatchWhenGameIsOver() {
cdlDone.countDown();
gameTaskQueue.add(terminationMarker);
}
public void showAndWait() {
final boolean isGameThread = ThreadUtil.isGameThread();
if (isGameThread) {
// If we're on the game thread, redirect the "run on game thread" function to instead go through us.
getController().getGame().getAction().setInvokeFunction(new Function<Runnable, Void>() {
public Void apply(Runnable r) {
gameTaskQueue.add(r);
return null;
}
});
}
getController().getInputQueue().setInput(this);
awaitLatchRelease();
if (isGameThread) {
// Reset the invoke function to null.
getController().getGame().getAction().setInvokeFunction(null);
// There's a race that a new task may be queued up before we've reset the invoke function.
// To handle this, schedule any remaining tasks normally.
for (Runnable r : gameTaskQueue) {
getController().getGame().getAction().invoke(r);
}
}
}
protected final void stop() {
@@ -50,7 +81,7 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn
if (getController().getInputQueue().getInput() != null) {
getController().getInputQueue().removeInput(InputSyncronizedBase.this);
}
cdlDone.countDown();
gameTaskQueue.add(terminationMarker);
}
protected void onStop() { }