DroidFish: Rewrote the cuckoochess communication so that internet permission is no longer needed.

This commit is contained in:
Peter Osterlund
2012-01-15 11:39:23 +00:00
parent bd9a0c4928
commit e4b468d88d
8 changed files with 116 additions and 233 deletions

View File

@@ -6,6 +6,8 @@
android:installLocation="auto"> android:installLocation="auto">
<supports-screens android:largeScreens="true" <supports-screens android:largeScreens="true"
android:anyDensity="true" /> android:anyDensity="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:icon="@drawable/icon" <application android:icon="@drawable/icon"
android:label="@string/app_name" android:label="@string/app_name"
android:debuggable="false"> android:debuggable="false">
@@ -45,9 +47,6 @@
android:label="@string/cpu_warning_title"> android:label="@string/cpu_warning_title">
</activity> </activity>
</application> </application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="3" <uses-sdk android:minSdkVersion="3"
android:targetSdkVersion="10"/> android:targetSdkVersion="10"/>
</manifest> </manifest>

View File

@@ -803,7 +803,7 @@ public class DroidComputerPlayer {
private final synchronized int getReadTimeout() { private final synchronized int getReadTimeout() {
boolean needGuiUpdate = depthModified || currMoveModified || pvModified || statsModified; boolean needGuiUpdate = depthModified || currMoveModified || pvModified || statsModified;
int timeout = 1000; int timeout = 2000000000;
if (needGuiUpdate) { if (needGuiUpdate) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
timeout = (int)(lastGUIUpdate + guiUpdateInterval - now + 1); timeout = (int)(lastGUIUpdate + guiUpdateInterval - now + 1);

View File

@@ -26,8 +26,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.LinkedList;
import java.util.List;
import org.petero.droidfish.R; import org.petero.droidfish.R;
import android.content.Context; import android.content.Context;
@@ -45,7 +43,7 @@ public class ExternalEngine extends UCIEngineBase {
private Thread exitThread; private Thread exitThread;
private Thread stdInThread; private Thread stdInThread;
private Thread stdErrThread; private Thread stdErrThread;
private List<String> inLines; private LocalPipe inLines;
private boolean startedOk; private boolean startedOk;
private boolean isRunning; private boolean isRunning;
@@ -58,7 +56,7 @@ public class ExternalEngine extends UCIEngineBase {
exitThread = null; exitThread = null;
stdInThread = null; stdInThread = null;
stdErrThread = null; stdErrThread = null;
inLines = new LinkedList<String>(); inLines = new LocalPipe();
startedOk = false; startedOk = false;
isRunning = false; isRunning = false;
} }
@@ -120,8 +118,7 @@ public class ExternalEngine extends UCIEngineBase {
if ((ep == null) || Thread.currentThread().isInterrupted()) if ((ep == null) || Thread.currentThread().isInterrupted())
return; return;
synchronized (inLines) { synchronized (inLines) {
inLines.add(line); inLines.addLine(line);
inLines.notify();
if (first) { if (first) {
startedOk = true; startedOk = true;
isRunning = true; isRunning = true;
@@ -130,8 +127,8 @@ public class ExternalEngine extends UCIEngineBase {
} }
} }
} catch (IOException e) { } catch (IOException e) {
return;
} }
inLines.close();
} }
}); });
stdInThread.start(); stdInThread.start();
@@ -176,26 +173,13 @@ public class ExternalEngine extends UCIEngineBase {
/** @inheritDoc */ /** @inheritDoc */
@Override @Override
public String readLineFromEngine(int timeoutMillis) { public String readLineFromEngine(int timeoutMillis) {
try { String ret = inLines.readLine(timeoutMillis);
synchronized (inLines) { if (ret == null)
if (inLines.size() == 0) { return null;
Thread inThread = stdInThread; if (ret.length() > 0) {
if ((inThread == null) || !inThread.isAlive()) // System.out.printf("Engine -> GUI: %s\n", ret);
return null;
inLines.wait(timeoutMillis);
}
}
synchronized (inLines) {
if (inLines.size() > 0) {
String ret = inLines.get(0);
inLines.remove(0);
// System.out.printf("Engine -> GUI: %s\n", ret);
return ret;
}
}
} catch (InterruptedException e) {
} }
return ""; return ret;
} }
/** @inheritDoc */ /** @inheritDoc */

View File

@@ -0,0 +1,63 @@
package org.petero.droidfish.engine;
import java.util.LinkedList;
/** Implements line-based text communication between threads. */
public class LocalPipe {
private LinkedList<String> lines = new LinkedList<String>();
private boolean closed = false;
/** Write a line to the pipe. */
public final synchronized void printLine(String format) {
String s = String.format(format, new Object[]{});
addLine(s);
}
/** Write a line to the pipe. */
public final synchronized void printLine(String format, Object ... args) {
String s = String.format(format, args);
addLine(s);
}
public final synchronized void addLine(String line) {
lines.add(line);
notify();
}
/** Read a line from the pipe. Returns null on failure. */
public final synchronized String readLine() {
return readLine(-1);
}
/** Read a line from the pipe. Returns null on failure. Returns empty string on timeout. */
public final synchronized String readLine(int timeoutMillis) {
if (closed)
return null;
try {
if (lines.isEmpty()) {
if (timeoutMillis > 0)
wait(timeoutMillis);
else
wait();
}
if (lines.isEmpty())
return closed ? null : "";
String ret = lines.get(0);
lines.remove(0);
return ret;
} catch (InterruptedException e) {
return null;
}
}
/** Close pipe. Makes readLine() return null. */
public final synchronized void close() {
closed = true;
notify();
}
/** Return true if writer side has closed the pipe. */
public final synchronized boolean isClosed() {
return closed;
}
}

View File

@@ -23,11 +23,9 @@ import chess.ComputerPlayer;
import chess.Move; import chess.Move;
import chess.Position; import chess.Position;
import chess.TextIO; import chess.TextIO;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.util.ArrayList; import java.util.ArrayList;
import org.petero.droidfish.engine.LocalPipe;
import org.petero.droidfish.engine.UCIEngineBase; import org.petero.droidfish.engine.UCIEngineBase;
/** /**
@@ -46,22 +44,16 @@ public class CuckooChessEngine extends UCIEngineBase {
// Set to true to break out of main loop // Set to true to break out of main loop
private boolean quit; private boolean quit;
private Pipe guiToEngine; private LocalPipe guiToEngine;
private Pipe engineToGui; private LocalPipe engineToGui;
private NioInputStream inFromEngine;
private Thread engineThread; private Thread engineThread;
public CuckooChessEngine(Report report) { public CuckooChessEngine(Report report) {
pos = null; pos = null;
moves = new ArrayList<Move>(); moves = new ArrayList<Move>();
quit = false; quit = false;
try { guiToEngine = new LocalPipe();
guiToEngine = Pipe.open(); engineToGui = new LocalPipe();
engineToGui = Pipe.open();
inFromEngine = new NioInputStream(engineToGui);
} catch (IOException e) {
report.reportError(e.getMessage());
}
} }
/** @inheritDoc */ /** @inheritDoc */
@@ -69,9 +61,7 @@ public class CuckooChessEngine extends UCIEngineBase {
protected final void startProcess() { protected final void startProcess() {
engineThread = new Thread(new Runnable() { engineThread = new Thread(new Runnable() {
public void run() { public void run() {
NioInputStream in = new NioInputStream(guiToEngine); mainLoop(guiToEngine, engineToGui);
NioPrintStream out = new NioPrintStream(engineToGui);
mainLoop(in, out);
} }
}); });
int pMin = Thread.MIN_PRIORITY; int pMin = Thread.MIN_PRIORITY;
@@ -93,7 +83,7 @@ public class CuckooChessEngine extends UCIEngineBase {
setOption("strength", strength); setOption("strength", strength);
} }
private final void mainLoop(NioInputStream is, NioPrintStream os) { private final void mainLoop(LocalPipe is, LocalPipe os) {
String line; String line;
while ((line = is.readLine()) != null) { while ((line = is.readLine()) != null) {
handleCommand(line, os); handleCommand(line, os);
@@ -103,12 +93,18 @@ public class CuckooChessEngine extends UCIEngineBase {
} }
} }
@Override
public void shutDown() {
super.shutDown();
guiToEngine.close();
}
/** @inheritDoc */ /** @inheritDoc */
@Override @Override
public final String readLineFromEngine(int timeoutMillis) { public final String readLineFromEngine(int timeoutMillis) {
if ((engineThread != null) && !engineThread.isAlive()) if ((engineThread != null) && !engineThread.isAlive())
return null; return null;
String ret = inFromEngine.readLine(timeoutMillis); String ret = engineToGui.readLine(timeoutMillis);
if (ret == null) if (ret == null)
return null; return null;
if (ret.length() > 0) { if (ret.length() > 0) {
@@ -121,25 +117,21 @@ public class CuckooChessEngine extends UCIEngineBase {
@Override @Override
public final synchronized void writeLineToEngine(String data) { public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data); // System.out.printf("GUI -> Engine: %s\n", data);
try { guiToEngine.addLine(data);
String s = data + "\n";
guiToEngine.sink().write(ByteBuffer.wrap(s.getBytes()));
} catch (IOException e) {
}
} }
private final void handleCommand(String cmdLine, NioPrintStream os) { private final void handleCommand(String cmdLine, LocalPipe os) {
String[] tokens = tokenize(cmdLine); String[] tokens = tokenize(cmdLine);
try { try {
String cmd = tokens[0]; String cmd = tokens[0];
if (cmd.equals("uci")) { if (cmd.equals("uci")) {
os.printf("id name %s%n", ComputerPlayer.engineName); os.printLine("id name %s", ComputerPlayer.engineName);
os.printf("id author Peter Osterlund%n"); os.printLine("id author Peter Osterlund");
DroidEngineControl.printOptions(os); DroidEngineControl.printOptions(os);
os.printf("uciok%n"); os.printLine("uciok");
} else if (cmd.equals("isready")) { } else if (cmd.equals("isready")) {
initEngine(os); initEngine(os);
os.printf("readyok%n"); os.printLine("readyok");
} else if (cmd.equals("setoption")) { } else if (cmd.equals("setoption")) {
initEngine(os); initEngine(os);
StringBuilder optionName = new StringBuilder(); StringBuilder optionName = new StringBuilder();
@@ -260,7 +252,7 @@ public class CuckooChessEngine extends UCIEngineBase {
} }
} }
private final void initEngine(NioPrintStream os) { private final void initEngine(LocalPipe os) {
if (engine == null) { if (engine == null) {
engine = new DroidEngineControl(os); engine = new DroidEngineControl(os);
} }

View File

@@ -35,12 +35,14 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import org.petero.droidfish.engine.LocalPipe;
/** /**
* Control the search thread. * Control the search thread.
* @author petero * @author petero
*/ */
public class DroidEngineControl { public class DroidEngineControl {
NioPrintStream os; LocalPipe os;
Thread engineThread; Thread engineThread;
private final Object threadMutex; private final Object threadMutex;
@@ -76,18 +78,18 @@ public class DroidEngineControl {
* This class is responsible for sending "info" strings during search. * This class is responsible for sending "info" strings during search.
*/ */
static class SearchListener implements Search.Listener { static class SearchListener implements Search.Listener {
NioPrintStream os; LocalPipe os;
SearchListener(NioPrintStream os) { SearchListener(LocalPipe os) {
this.os = os; this.os = os;
} }
public void notifyDepth(int depth) { public void notifyDepth(int depth) {
os.printf("info depth %d%n", depth); os.printLine("info depth %d", depth);
} }
public void notifyCurrMove(Move m, int moveNr) { public void notifyCurrMove(Move m, int moveNr) {
os.printf("info currmove %s currmovenumber %d%n", moveToString(m), moveNr); os.printLine("info currmove %s currmovenumber %d", moveToString(m), moveNr);
} }
public void notifyPV(int depth, int score, int time, long nodes, int nps, boolean isMate, public void notifyPV(int depth, int score, int time, long nodes, int nps, boolean isMate,
@@ -103,16 +105,16 @@ public class DroidEngineControl {
} else if (lowerBound) { } else if (lowerBound) {
bound = " lowerbound"; bound = " lowerbound";
} }
os.printf("info depth %d score %s %d%s time %d nodes %d nps %d pv%s%n", os.printLine("info depth %d score %s %d%s time %d nodes %d nps %d pv%s",
depth, isMate ? "mate" : "cp", score, bound, time, nodes, nps, pvBuf.toString()); depth, isMate ? "mate" : "cp", score, bound, time, nodes, nps, pvBuf.toString());
} }
public void notifyStats(long nodes, int nps, int time) { public void notifyStats(long nodes, int nps, int time) {
os.printf("info nodes %d nps %d time %d%n", nodes, nps, time); os.printLine("info nodes %d nps %d time %d", nodes, nps, time);
} }
} }
public DroidEngineControl(NioPrintStream os) { public DroidEngineControl(LocalPipe os) {
this.os = os; this.os = os;
threadMutex = new Object(); threadMutex = new Object();
setupTT(); setupTT();
@@ -260,9 +262,9 @@ public class DroidEngineControl {
Move ponderMove = getPonderMove(pos, m); Move ponderMove = getPonderMove(pos, m);
synchronized (threadMutex) { synchronized (threadMutex) {
if (ponderMove != null) { if (ponderMove != null) {
os.printf("bestmove %s ponder %s%n", moveToString(m), moveToString(ponderMove)); os.printLine("bestmove %s ponder %s", moveToString(m), moveToString(ponderMove));
} else { } else {
os.printf("bestmove %s%n", moveToString(m)); os.printLine("bestmove %s", moveToString(m));
} }
engineThread = null; engineThread = null;
sc = null; sc = null;
@@ -362,14 +364,14 @@ public class DroidEngineControl {
return ret; return ret;
} }
static void printOptions(NioPrintStream os) { static void printOptions(LocalPipe os) {
os.printf("option name Hash type spin default 2 min 1 max 2048%n"); os.printLine("option name Hash type spin default 2 min 1 max 2048");
os.printf("option name OwnBook type check default false%n"); os.printLine("option name OwnBook type check default false");
os.printf("option name Ponder type check default true%n"); os.printLine("option name Ponder type check default true");
os.printf("option name UCI_AnalyseMode type check default false%n"); os.printLine("option name UCI_AnalyseMode type check default false");
os.printf("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://web.comhem.se/petero2home/javachess/index.html%n", os.printLine("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://web.comhem.se/petero2home/javachess/index.html",
ComputerPlayer.engineName); ComputerPlayer.engineName);
os.printf("option name Strength type spin default 1000 min 0 max 1000\n"); os.printLine("option name Strength type spin default 1000 min 0 max 1000");
} }
final void setOption(String optionName, String optionValue) { final void setOption(String optionName, String optionValue) {

View File

@@ -1,109 +0,0 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
/** Simple InputStream look-alike on top of nio. */
class NioInputStream {
Pipe.SourceChannel in;
ByteBuffer buffer;
Selector selector;
ArrayList<Character> inBuf;
StringBuilder lineBuf;
public NioInputStream(Pipe pipe) {
in = pipe.source();
try {
in.configureBlocking(false);
selector = Selector.open();
in.register(selector, SelectionKey.OP_READ);
buffer = ByteBuffer.allocate(1024);
inBuf = new ArrayList<Character>();
lineBuf = new StringBuilder(128);
} catch (IOException e) {
}
}
public String readLine() {
while (true) {
String s = readLine(1000);
if (s != null)
return s;
}
}
public String readLine(int timeoutMillis) {
try {
boolean haveNewLine = false;
for (int i = 0; i < inBuf.size(); i++) {
if (inBuf.get(i) == '\n') {
haveNewLine = true;
break;
}
}
if (!haveNewLine) {
// Refill inBuf
if (timeoutMillis < 1)
timeoutMillis = 1;
selector.select(timeoutMillis);
buffer.clear();
for (SelectionKey sk : selector.selectedKeys())
if (sk.isValid() && sk.isReadable())
in.read(buffer);
buffer.flip();
while (buffer.position() < buffer.limit()) {
byte b = buffer.get();
inBuf.add((char)b);
}
}
// Extract line
String ret = "";
int i;
for (i = 0; i < inBuf.size(); i++) {
char c = inBuf.get(i);
if (c == '\n') {
int newSize = inBuf.size() - i - 1;
for (int j = 0; j < newSize; j++)
inBuf.set(j, inBuf.get(j+i+1));
while (inBuf.size() > newSize)
inBuf.remove(inBuf.size() - 1);
ret = lineBuf.toString();
lineBuf = new StringBuilder(128);
break;
} else {
lineBuf.append(c);
}
}
if (i == inBuf.size())
inBuf.clear();
return ret;
} catch (IOException e) {
}
return null;
}
}

View File

@@ -1,48 +0,0 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
/** Simple PrintStream look-alike on top of nio. */
class NioPrintStream {
Pipe.SinkChannel out;
public NioPrintStream(Pipe pipe) {
out = pipe.sink();
}
public void printf(String format) {
try {
String s = String.format(format, new Object[]{});
out.write(ByteBuffer.wrap(s.getBytes()));
} catch (IOException e) {
}
}
public void printf(String format, Object ... args) {
try {
String s = String.format(format, args);
out.write(ByteBuffer.wrap(s.getBytes()));
} catch (IOException e) {
}
}
}