// GuiGtpClient.java
package net.sf.gogui.gui;
import java.awt.Component;
import java.text.MessageFormat;
import javax.swing.SwingUtilities;
import net.sf.gogui.game.TimeSettings;
import net.sf.gogui.go.ConstBoard;
import net.sf.gogui.go.Komi;
import net.sf.gogui.go.Move;
import net.sf.gogui.gtp.GtpClient;
import net.sf.gogui.gtp.GtpClientBase;
import net.sf.gogui.gtp.GtpError;
import net.sf.gogui.gtp.GtpSynchronizer;
import static net.sf.gogui.gui.I18n.i18n;
/** Wrapper around gtp.GtpClient to be used in a GUI environment.
Allows to send fast commands with the GtpClientBase.send() function
immediately in the event dispatch thread and potentially slow commands in
a separate thread with a callback in the event thread after the command
finished.
Fast commands are ones that the Go engine is supposed to answer quickly
(like boardsize, play and undo), however they have a timeout to
prevent the GUI to hang, if the program does not respond.
After the timeout a dialog is opened that allows to kill the program or
continue to wait.
This class also contains a GtpSynchronizer. */
public class GuiGtpClient
extends GtpClientBase
{
public GuiGtpClient(GtpClient gtp, Component owner,
GtpSynchronizer.Listener listener,
MessageDialogs messageDialogs)
{
m_gtp = gtp;
m_owner = owner;
m_messageDialogs = messageDialogs;
m_gtpSynchronizer = new GtpSynchronizer(this, listener, false);
Thread thread = new Thread() {
public void run() {
synchronized (m_mutex)
{
boolean firstWait = true;
while (true)
{
try
{
if (m_command == null || ! firstWait)
m_mutex.wait();
}
catch (InterruptedException e)
{
System.err.println("Interrupted");
}
firstWait = false;
m_response = null;
m_exception = null;
try
{
m_response = m_gtp.send(m_command);
}
catch (GtpError e)
{
m_exception = e;
}
SwingUtilities.invokeLater(m_callback);
}
}
}
};
thread.start();
}
public void close()
{
if (! isProgramDead())
{
m_gtp.close();
TimeoutCallback timeoutCallback = new TimeoutCallback(null);
m_gtp.waitForExit(TIMEOUT, timeoutCallback);
}
}
public void destroyGtp()
{
m_gtp.destroyProcess();
}
public boolean getAnyCommandsResponded()
{
return m_gtp.getAnyCommandsResponded();
}
/** Get exception of asynchronous command.
You must call this before you are allowed to send new a command. */
public GtpError getException()
{
synchronized (m_mutex)
{
assert SwingUtilities.isEventDispatchThread();
assert m_commandInProgress;
m_commandInProgress = false;
return m_exception;
}
}
public String getProgramCommand()
{
return m_gtp.getProgramCommand();
}
/** Get response to asynchronous command.
You must call getException() first. */
public String getResponse()
{
synchronized (m_mutex)
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
return m_response;
}
}
public void initSynchronize(ConstBoard board, Komi komi,
TimeSettings timeSettings) throws GtpError
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
m_gtpSynchronizer.init(board, komi, timeSettings);
}
public boolean isCommandInProgress()
{
return m_commandInProgress;
}
public boolean isOutOfSync()
{
return m_gtpSynchronizer.isOutOfSync();
}
public boolean isProgramDead()
{
assert SwingUtilities.isEventDispatchThread();
return m_gtp.isProgramDead();
}
/** Send asynchronous command. */
public void send(String command, Runnable callback)
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
synchronized (m_mutex)
{
m_command = command;
m_callback = callback;
m_commandInProgress = true;
m_mutex.notifyAll();
}
}
public void sendComment(String comment)
{
m_gtp.sendComment(comment);
}
/** Send command in event dispatch thread. */
public String send(String command) throws GtpError
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
TimeoutCallback timeoutCallback = new TimeoutCallback(command);
return m_gtp.send(command, TIMEOUT, timeoutCallback);
}
public void setAutoNumber(boolean enable)
{
m_gtp.setAutoNumber(enable);
}
public void synchronize(ConstBoard board, Komi komi,
TimeSettings timeSettings) throws GtpError
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
m_gtpSynchronizer.synchronize(board, komi, timeSettings);
}
public void updateAfterGenmove(ConstBoard board)
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
m_gtpSynchronizer.updateAfterGenmove(board);
}
public void updateHumanMove(ConstBoard board, Move move) throws GtpError
{
assert SwingUtilities.isEventDispatchThread();
assert ! m_commandInProgress;
m_gtpSynchronizer.updateHumanMove(board, move);
}
public void waitForExit()
{
m_gtp.waitForExit();
}
public boolean wasKilled()
{
return m_gtp.wasKilled();
}
private class TimeoutCallback
implements GtpClient.TimeoutCallback
{
TimeoutCallback(String command)
{
m_command = command;
}
public boolean askContinue()
{
String mainMessage = i18n("MSG_PROGRAM_NOT_RESPONDING");
String optionalMessage;
String destructiveOption;
if (m_command == null)
{
optionalMessage = i18n("MSG_PROGRAM_NOT_RESPONDING_2");
destructiveOption = i18n("LB_FORCE_QUIT_PROGRAM");
}
else
{
optionalMessage =
MessageFormat.format(i18n("MSG_PROGRAM_NOT_RESPONDING_3"),
m_command);
destructiveOption = i18n("LB_TERMINATE_PROGRAM");
}
return ! m_messageDialogs.showWarningQuestion(null, m_owner,
mainMessage,
optionalMessage,
destructiveOption,
i18n("LB_WAIT"),
true);
}
private final String m_command;
};
/** The timeout for commands that are expected to be fast.
GoGui 0.9.4 used 8 sec, but this was not enough on some machines
when starting up engines like Aya in the Wine emulator. */
private static final int TIMEOUT = 15000;
private boolean m_commandInProgress;
private final GtpClient m_gtp;
private GtpError m_exception;
private final GtpSynchronizer m_gtpSynchronizer;
private final Component m_owner;
private final MessageDialogs m_messageDialogs;
private final Object m_mutex = new Object();
private Runnable m_callback;
private String m_command;
private String m_response;
}