DroidFish: Made it possible to edit UCI options while the engine is

thinking. The changes take effect the next time the engine stops
thinking.
This commit is contained in:
Peter Osterlund 2015-12-28 14:39:51 +01:00
parent 2ca80fc19d
commit 7026555cbe
8 changed files with 109 additions and 52 deletions

View File

@ -86,7 +86,7 @@ public class ButtonActions {
for (UIAction a : menuActions) {
if (a != null) {
haveActions = true;
if (a.enabled())
if (a.enabled())
haveEnabledActions = true;
}
}

View File

@ -2897,8 +2897,6 @@ public class DroidFish extends Activity implements GUIInterface {
/** Return true if engine UCI options can be set now. */
private final boolean canSetEngineOptions() {
if (!ctrl.computerIdle())
return false;
UCIOptions uciOpts = ctrl.getUCIOptions();
if (uciOpts == null)
return false;

View File

@ -21,6 +21,7 @@ package org.petero.droidfish.engine;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import org.petero.droidfish.EngineOptions;
import org.petero.droidfish.book.BookOptions;
@ -46,7 +47,9 @@ public class DroidComputerPlayer {
private final Context context;
private final SearchListener listener;
private final DroidBook book;
private EngineOptions engineOptions;
private EngineOptions engineOptions = new EngineOptions();
/** Pending UCI options to send when engine becomes idle. */
private Map<String,String> pendingOptions = new TreeMap<String,String>();
/** Set when "ucinewgame" needs to be sent. */
private boolean newGame = false;
@ -226,8 +229,8 @@ public class DroidComputerPlayer {
}
}
private EngineState engineState;
private SearchRequest searchRequest;
private EngineState engineState = new EngineState();
private SearchRequest searchRequest = null;
private Thread engineMonitor;
/** Constructor. Starts engine process if not already started. */
@ -235,9 +238,6 @@ public class DroidComputerPlayer {
this.context = context;
this.listener = listener;
book = DroidBook.getInstance();
engineOptions = new EngineOptions();
engineState = new EngineState();
searchRequest = null;
}
/** Return true if computer player is consuming CPU time. */
@ -253,16 +253,30 @@ public class DroidComputerPlayer {
}
}
/** Return true if computer player is in IDLE state. */
public final synchronized boolean computerIdle() {
return engineState.state == MainState.IDLE;
/** Return true if computer player has been loaded. */
public final synchronized boolean computerLoaded() {
return (engineState.state != MainState.READ_OPTIONS) &&
(engineState.state != MainState.DEAD);
}
public final synchronized UCIOptions getUCIOptions() {
UCIEngine uci = uciEngine;
if (uci == null)
return null;
return uci.getUCIOptions();
UCIOptions opts = uci.getUCIOptions();
if (opts == null)
return null;
try {
opts = opts.clone();
} catch (CloneNotSupportedException e) {
return null;
}
for (Map.Entry<String,String> e : pendingOptions.entrySet()) {
UCIOptions.OptionBase o = opts.getOption(e.getKey());
if (o != null)
o.setFromString(e.getValue());
}
return opts;
}
/** Return maximum number of PVs supported by engine. */
@ -279,11 +293,27 @@ public class DroidComputerPlayer {
engineOptions = options;
}
/** Send pending UCI option changes to the engine. */
private synchronized boolean applyPendingOptions() {
if (pendingOptions.isEmpty())
return false;
boolean modified = false;
UCIEngine uci = uciEngine;
if (uci != null)
modified = uci.setUCIOptions(pendingOptions);
pendingOptions.clear();
return modified;
}
public synchronized void setEngineUCIOptions(Map<String,String> uciOptions) {
if (engineState.state == MainState.IDLE) {
pendingOptions.putAll(uciOptions);
boolean modified = true;
if (engineState.state == MainState.IDLE)
modified = applyPendingOptions();
if (modified) {
UCIEngine uci = uciEngine;
if (uci != null)
uci.setUCIOptions(uciOptions);
uci.saveIniFile(getUCIOptions());
}
}
@ -297,8 +327,8 @@ public class DroidComputerPlayer {
return engineName;
}
/** Clear transposition table. Takes effect when next search started. */
public final synchronized void clearTT() {
/** Sends "ucinewgame". Takes effect when next search started. */
public final synchronized void uciNewGame() {
newGame = true;
}
@ -508,7 +538,7 @@ public class DroidComputerPlayer {
return;
}
// Send "ucinewgame" (clear hash table) if needed
// Send "ucinewgame" if needed
if (newGame) {
uciEngine.writeLineToEngine("ucinewgame");
uciEngine.writeLineToEngine("isready");
@ -517,6 +547,13 @@ public class DroidComputerPlayer {
return;
}
// Apply pending UCI option changes
if (applyPendingOptions()) {
uciEngine.writeLineToEngine("isready");
engineState.setState(MainState.WAIT_READY);
return;
}
// Check if only engine start was requested
boolean isSearch = sr.isSearch;
boolean isAnalyze = sr.isAnalyze;
@ -681,6 +718,7 @@ public class DroidComputerPlayer {
switch (engineState.state) {
case READ_OPTIONS: {
if (readUCIOption(uci, s)) {
pendingOptions.clear();
uci.initOptions(engineOptions);
uci.applyIniFile();
uci.writeLineToEngine("ucinewgame");

View File

@ -52,9 +52,7 @@ public class InternalStockFish extends ExternalEngine {
if (!super.configurableOption(name))
return false;
if (name.equals("skill level") || name.equals("write debug log") ||
name.equals("write search log") || name.equals("search log filename") ||
name.equals("book file") || name.equals("best book move") ||
name.equals("ownbook"))
name.equals("write search log"))
return false;
return true;
}

View File

@ -40,7 +40,10 @@ public interface UCIEngine {
public void applyIniFile();
/** Set engine UCI options. */
public void setUCIOptions(Map<String,String> uciOptions);
public boolean setUCIOptions(Map<String,String> uciOptions);
/** Save non-default UCI option values to file. */
public void saveIniFile(UCIOptions options);
/** Get engine UCI options. */
public UCIOptions getUCIOptions();

View File

@ -35,7 +35,7 @@ import android.content.Context;
public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive;
UCIOptions options;
private UCIOptions options;
protected boolean isUCI;
public static UCIEngine getEngine(Context context, String engine,
@ -99,7 +99,7 @@ public abstract class UCIEngineBase implements UCIEngine {
}
@Override
public final void setUCIOptions(Map<String,String> uciOptions) {
public final boolean setUCIOptions(Map<String,String> uciOptions) {
boolean modified = false;
for (Map.Entry<String,String> ent : uciOptions.entrySet()) {
String key = ((String)ent.getKey()).toLowerCase(Locale.US);
@ -107,23 +107,26 @@ public abstract class UCIEngineBase implements UCIEngine {
if (configurableOption(key))
modified |= setOption(key, value);
}
if (modified) { // Save .ini file
Properties iniOptions = new Properties();
for (String name : options.getOptionNames()) {
UCIOptions.OptionBase o = options.getOption(name);
if (configurableOption(name) && o.modified())
iniOptions.put(o.name, o.getStringValue());
}
File optionsFile = getOptionsFile();
FileOutputStream os = null;
try {
os = new FileOutputStream(optionsFile);
iniOptions.store(os, null);
} catch (IOException ex) {
} finally {
if (os != null)
try { os.close(); } catch (IOException ex) {}
}
return modified;
}
@Override
public final void saveIniFile(UCIOptions options) {
Properties iniOptions = new Properties();
for (String name : options.getOptionNames()) {
UCIOptions.OptionBase o = options.getOption(name);
if (configurableOption(name) && o.modified())
iniOptions.put(o.name, o.getStringValue());
}
File optionsFile = getOptionsFile();
FileOutputStream os = null;
try {
os = new FileOutputStream(optionsFile);
iniOptions.store(os, null);
} catch (IOException ex) {
} finally {
if (os != null)
try { os.close(); } catch (IOException ex) {}
}
}

View File

@ -6,7 +6,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
public class UCIOptions implements Serializable {
public class UCIOptions implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private ArrayList<String> names;
private Map<String, OptionBase> options;
@ -19,12 +19,17 @@ public class UCIOptions implements Serializable {
STRING
}
public abstract static class OptionBase implements Serializable {
public abstract static class OptionBase implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
public String name;
public Type type;
public boolean visible = true;
@Override
public OptionBase clone() throws CloneNotSupportedException {
return (OptionBase)super.clone();
}
/** Return true if current value != default value. */
abstract public boolean modified();
@ -204,7 +209,22 @@ public class UCIOptions implements Serializable {
options = new TreeMap<String, OptionBase>();
}
@Override
public UCIOptions clone() throws CloneNotSupportedException {
UCIOptions copy = new UCIOptions();
copy.names = new ArrayList<String>();
copy.names.addAll(names);
copy.options = new TreeMap<String, OptionBase>();
for (Map.Entry<String, OptionBase> e : options.entrySet())
copy.options.put(e.getKey(), e.getValue().clone());
return copy;
}
public void clear() {
names.clear();
options.clear();
}

View File

@ -94,7 +94,7 @@ public class DroidChessController {
computerPlayer.queueStartEngine(searchId, engine);
searchId++;
game = new Game(gameTextListener, tcData);
computerPlayer.clearTT();
computerPlayer.uciNewGame();
setPlayerNames(game);
updateGameMode();
}
@ -279,7 +279,7 @@ public class DroidChessController {
gameTextListener.clear();
updateGameMode();
abortSearch();
computerPlayer.clearTT();
computerPlayer.uciNewGame();
updateComputeThreads();
gui.setSelection(-1);
updateGUI();
@ -297,13 +297,10 @@ public class DroidChessController {
return (computerPlayer != null) && computerPlayer.computerBusy();
}
/** Return true if computer player is in IDLE state. */
public final synchronized boolean computerIdle() {
return (computerPlayer != null) && computerPlayer.computerIdle();
}
/** Return engine UCI options if an engine has been loaded and has
* reported its UCI options. */
public final synchronized UCIOptions getUCIOptions() {
if (!computerIdle() || computerPlayer == null)
if (computerPlayer == null || !computerPlayer.computerLoaded())
return null;
return computerPlayer.getUCIOptions();
}