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) { for (UIAction a : menuActions) {
if (a != null) { if (a != null) {
haveActions = true; haveActions = true;
if (a.enabled()) if (a.enabled())
haveEnabledActions = true; 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. */ /** Return true if engine UCI options can be set now. */
private final boolean canSetEngineOptions() { private final boolean canSetEngineOptions() {
if (!ctrl.computerIdle())
return false;
UCIOptions uciOpts = ctrl.getUCIOptions(); UCIOptions uciOpts = ctrl.getUCIOptions();
if (uciOpts == null) if (uciOpts == null)
return false; return false;

View File

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

View File

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

View File

@ -40,7 +40,10 @@ public interface UCIEngine {
public void applyIniFile(); public void applyIniFile();
/** Set engine UCI options. */ /** 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. */ /** Get engine UCI options. */
public UCIOptions getUCIOptions(); public UCIOptions getUCIOptions();

View File

@ -35,7 +35,7 @@ import android.content.Context;
public abstract class UCIEngineBase implements UCIEngine { public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive; private boolean processAlive;
UCIOptions options; private UCIOptions options;
protected boolean isUCI; protected boolean isUCI;
public static UCIEngine getEngine(Context context, String engine, public static UCIEngine getEngine(Context context, String engine,
@ -99,7 +99,7 @@ public abstract class UCIEngineBase implements UCIEngine {
} }
@Override @Override
public final void setUCIOptions(Map<String,String> uciOptions) { public final boolean setUCIOptions(Map<String,String> uciOptions) {
boolean modified = false; boolean modified = false;
for (Map.Entry<String,String> ent : uciOptions.entrySet()) { for (Map.Entry<String,String> ent : uciOptions.entrySet()) {
String key = ((String)ent.getKey()).toLowerCase(Locale.US); String key = ((String)ent.getKey()).toLowerCase(Locale.US);
@ -107,23 +107,26 @@ public abstract class UCIEngineBase implements UCIEngine {
if (configurableOption(key)) if (configurableOption(key))
modified |= setOption(key, value); modified |= setOption(key, value);
} }
if (modified) { // Save .ini file return modified;
Properties iniOptions = new Properties(); }
for (String name : options.getOptionNames()) {
UCIOptions.OptionBase o = options.getOption(name); @Override
if (configurableOption(name) && o.modified()) public final void saveIniFile(UCIOptions options) {
iniOptions.put(o.name, o.getStringValue()); Properties iniOptions = new Properties();
} for (String name : options.getOptionNames()) {
File optionsFile = getOptionsFile(); UCIOptions.OptionBase o = options.getOption(name);
FileOutputStream os = null; if (configurableOption(name) && o.modified())
try { iniOptions.put(o.name, o.getStringValue());
os = new FileOutputStream(optionsFile); }
iniOptions.store(os, null); File optionsFile = getOptionsFile();
} catch (IOException ex) { FileOutputStream os = null;
} finally { try {
if (os != null) os = new FileOutputStream(optionsFile);
try { os.close(); } catch (IOException ex) {} 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.Map;
import java.util.TreeMap; import java.util.TreeMap;
public class UCIOptions implements Serializable { public class UCIOptions implements Serializable, Cloneable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private ArrayList<String> names; private ArrayList<String> names;
private Map<String, OptionBase> options; private Map<String, OptionBase> options;
@ -19,12 +19,17 @@ public class UCIOptions implements Serializable {
STRING STRING
} }
public abstract static class OptionBase implements Serializable { public abstract static class OptionBase implements Serializable, Cloneable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public String name; public String name;
public Type type; public Type type;
public boolean visible = true; public boolean visible = true;
@Override
public OptionBase clone() throws CloneNotSupportedException {
return (OptionBase)super.clone();
}
/** Return true if current value != default value. */ /** Return true if current value != default value. */
abstract public boolean modified(); abstract public boolean modified();
@ -204,7 +209,22 @@ public class UCIOptions implements Serializable {
options = new TreeMap<String, OptionBase>(); 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() { public void clear() {
names.clear();
options.clear(); options.clear();
} }

View File

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