Reading Input from another process

Discussion of chess software programming and technical issues.

Moderator: Ras

Richard Allbert
Posts: 794
Joined: Wed Jul 19, 2006 9:58 am

Reading Input from another process

Post by Richard Allbert »

Hi,

I'm writing a Chess Gui in Java. So far I am able to play vs an internal engine and all works ok.

I would like to be able to use UCI engines, and have been trying to get the reading and writing to and from a process to work.

The writing works ok - it's a thread that sleeps until woken up with a command which it then sends to the process.

The problem is the read from the process. How do I get the process to "wake when there is something to be read()? Using infinite loops with Sleep(50) and polling doesn't seem right...

Here's the code of a small test program I've been playing around with.

Code: Select all


/*
The ProcessRunner creates a ProcessComm object, which starts the engine process and has the input and output streams.

Then, two threads are created, one will sit in the "read loop" of the Processomm object, the other in the "write loop".

A third thread is a very simple gui with some buttons to send commands.
*/

public class ProcessRunner {
 //"D:\\Chess\\Engines\\UCI\\Jabba10\\Jabba10.exe"
       public static void main(String args[]) {  
            ExecutorService application = Executors.newCachedThreadPool();

            ProcessComm engineCom = new ProcessComm();
            engineCom.startProcess("D:\\Chess\\Engines\\UCI\\Jabba10\\Jabba10.exe");

            application.execute(new EngineReader(engineCom));
            application.execute(new EngineWriter(engineCom));
            application.execute(new SendGui(engineCom));

            application.shutdown();            
        }
}

Code: Select all

 /*
This class runs the engine process - the threads created in the class above are directed to readFromProcess() and writeFromProcess()
*/

public class ProcessComm {

    BufferedReader input;
    BufferedWriter output;
    Runtime rt;
    Process pr;
    String command;
    boolean commandIn;
    boolean closing;

    public ProcessComm() {
        this.commandIn = false;
        this.closing = false;
    }

    public void startProcess(String namePath) {

        rt = Runtime.getRuntime();
        try
        {
            pr = rt.exec(namePath);
        } catch (IOException e) {
            System.err.println("Process start error");
        }

        this.input = new BufferedReader(new InputStreamReader(pr.getInputStream()));
        this.output = new BufferedWriter(new OutputStreamWriter(pr.getOutputStream()));
    }

    public void closeProcess() {
        this.closing = true;
        try
        {
            int exitVal = pr.waitFor();
            System.out.println("Exited with error code "+exitVal);
        } catch (InterruptedException e) {
            System.err.println("Process close error");
        }
    }

    public synchronized void readFromProcess() {
        String line = null;
        System.out.println("Reader Going To Wait");
        try
        {
            System.out.println("Reader Loop Start");
            while((line = input.readLine())!=null) {
            System.out.println("in--> " + input.readLine());
            if(this.closing == true) {
                 System.out.println("Reader Leaving");
                 break;
                }
                line = input.readLine();
            }
            System.out.println("Reader Into top while");
        } catch (Exception e) {
            System.err.println("Process read wait error");
        }
    }

    public synchronized void writeToProcess() {
        System.out.println("Writer Going To Wait");
        try
        {
            while(this.commandIn == false) {
                wait();
                System.out.println("Writer Out of Wait");
                if(this.closing == true) {
                    System.out.println("Writer Leaving");
                    break;
                }
                this.output.write(this.command);
                this.output.flush();
                this.commandIn = false;
                System.out.println("Writer Finshed Writing");
            }
        } catch (Exception e) {
            System.err.println("Process read wait error");
        }
    }

    public synchronized void setCommand(String c) {
        this.command = c;
        this.commandIn = true;
        System.out.println("setCommand: " + c);
        notifyAll();
    }

}

Code: Select all


/*
This is just the GUI thread, which is used to send a command and calls notifyAll() to wake the writing thread when a command is sent
*/
public class SendGui extends JPanel implements ActionListener, Runnable {

    JButton positionButton;
    JButton goButton;
    JButton newButton;
    JButton uci;
    JButton quit;
    ProcessComm engine;


    public SendGui(ProcessComm engine) {
        this.engine = engine;

        positionButton = new JButton("position");
        positionButton.setVerticalTextPosition(AbstractButton.CENTER);
        positionButton.setHorizontalTextPosition(AbstractButton.CENTER);
        positionButton.addActionListener(this);

        goButton = new JButton("go");
        goButton.setVerticalTextPosition(AbstractButton.CENTER);
        goButton.setHorizontalTextPosition(AbstractButton.CENTER);
        goButton.addActionListener(this);

        newButton = new JButton("new");
        newButton.setVerticalTextPosition(AbstractButton.CENTER);
        newButton.setHorizontalTextPosition(AbstractButton.CENTER);
        newButton.addActionListener(this);

        uci = new JButton("uci");
        uci.setVerticalTextPosition(AbstractButton.CENTER);
        uci.setHorizontalTextPosition(AbstractButton.CENTER);
        uci.addActionListener(this);

        quit = new JButton("quit");
        quit.setVerticalTextPosition(AbstractButton.CENTER);
        quit.setHorizontalTextPosition(AbstractButton.CENTER);
        quit.addActionListener(this);

        add(goButton);
        add(positionButton);
        add(newButton);
        add(uci);
        add(quit);
    }

    public void run() {
        JFrame frame = new JFrame("Send Commands");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setOpaque(true);
        frame.setContentPane(this);
        frame.pack();
        frame.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {

        String command  = e.getActionCommand();
        String toSend = null;
        if(command.equals("quit")) {
           toSend = "quit\n";
        } else if (command.equals("go")) {
           toSend = "go depth 8\n";
        } else if (command.equals("new")) {
           toSend = "ucinewgame\n";
        }   else if (command.equals("position")) {
           toSend = "position startpos\n";
        }   else if (command.equals("uci")) {
           toSend = "uci\n";
        }
        sendCommand(toSend);
    }

    public synchronized void sendCommand(String sendMe) {
        if(sendMe.equals("quit\n")) {
            engine.closeProcess();
        }
        engine.setCommand(sendMe);
    }

}

Code: Select all


/*
The two classes used as threads to read and write
*/

public class EngineReader implements Runnable {
        ProcessComm engine;

        public EngineReader(ProcessComm e) {
            this.engine = e;
        }

        public void run() {
            this.engine.readFromProcess();
        }
}

public class EngineWriter implements Runnable {
        ProcessComm engine;

        public EngineWriter(ProcessComm e) {
            this.engine = e;
        }

        public void run() {
            this.engine.writeToProcess();
        }
}
Sorry if that's a bit too long!

Again, the problem I'm having is getting readFromProcess() to sit and wake when input is available.

I tried with a wait(), but of course the thread only wakes when I command is sent.

Any help appreciated!

Thanks

Richard
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Reading Input from another process

Post by bob »

Richard Allbert wrote:Hi,

I'm writing a Chess Gui in Java. So far I am able to play vs an internal engine and all works ok.

I would like to be able to use UCI engines, and have been trying to get the reading and writing to and from a process to work.

The writing works ok - it's a thread that sleeps until woken up with a command which it then sends to the process.

The problem is the read from the process. How do I get the process to "wake when there is something to be read()? Using infinite loops with Sleep(50) and polling doesn't seem right...

Here's the code of a small test program I've been playing around with.

Code: Select all


/*
The ProcessRunner creates a ProcessComm object, which starts the engine process and has the input and output streams.

Then, two threads are created, one will sit in the "read loop" of the Processomm object, the other in the "write loop".

A third thread is a very simple gui with some buttons to send commands.
*/

public class ProcessRunner {
 //"D:\\Chess\\Engines\\UCI\\Jabba10\\Jabba10.exe"
       public static void main(String args[]) {  
            ExecutorService application = Executors.newCachedThreadPool();

            ProcessComm engineCom = new ProcessComm();
            engineCom.startProcess("D:\\Chess\\Engines\\UCI\\Jabba10\\Jabba10.exe");

            application.execute(new EngineReader(engineCom));
            application.execute(new EngineWriter(engineCom));
            application.execute(new SendGui(engineCom));

            application.shutdown();            
        }
}

Code: Select all

 /*
This class runs the engine process - the threads created in the class above are directed to readFromProcess() and writeFromProcess()
*/

public class ProcessComm {

    BufferedReader input;
    BufferedWriter output;
    Runtime rt;
    Process pr;
    String command;
    boolean commandIn;
    boolean closing;

    public ProcessComm() {
        this.commandIn = false;
        this.closing = false;
    }

    public void startProcess(String namePath) {

        rt = Runtime.getRuntime();
        try
        {
            pr = rt.exec(namePath);
        } catch (IOException e) {
            System.err.println("Process start error");
        }

        this.input = new BufferedReader(new InputStreamReader(pr.getInputStream()));
        this.output = new BufferedWriter(new OutputStreamWriter(pr.getOutputStream()));
    }

    public void closeProcess() {
        this.closing = true;
        try
        {
            int exitVal = pr.waitFor();
            System.out.println("Exited with error code "+exitVal);
        } catch (InterruptedException e) {
            System.err.println("Process close error");
        }
    }

    public synchronized void readFromProcess() {
        String line = null;
        System.out.println("Reader Going To Wait");
        try
        {
            System.out.println("Reader Loop Start");
            while((line = input.readLine())!=null) {
            System.out.println("in--> " + input.readLine());
            if(this.closing == true) {
                 System.out.println("Reader Leaving");
                 break;
                }
                line = input.readLine();
            }
            System.out.println("Reader Into top while");
        } catch (Exception e) {
            System.err.println("Process read wait error");
        }
    }

    public synchronized void writeToProcess() {
        System.out.println("Writer Going To Wait");
        try
        {
            while(this.commandIn == false) {
                wait();
                System.out.println("Writer Out of Wait");
                if(this.closing == true) {
                    System.out.println("Writer Leaving");
                    break;
                }
                this.output.write(this.command);
                this.output.flush();
                this.commandIn = false;
                System.out.println("Writer Finshed Writing");
            }
        } catch (Exception e) {
            System.err.println("Process read wait error");
        }
    }

    public synchronized void setCommand(String c) {
        this.command = c;
        this.commandIn = true;
        System.out.println("setCommand: " + c);
        notifyAll();
    }

}

Code: Select all


/*
This is just the GUI thread, which is used to send a command and calls notifyAll() to wake the writing thread when a command is sent
*/
public class SendGui extends JPanel implements ActionListener, Runnable {

    JButton positionButton;
    JButton goButton;
    JButton newButton;
    JButton uci;
    JButton quit;
    ProcessComm engine;


    public SendGui(ProcessComm engine) {
        this.engine = engine;

        positionButton = new JButton("position");
        positionButton.setVerticalTextPosition(AbstractButton.CENTER);
        positionButton.setHorizontalTextPosition(AbstractButton.CENTER);
        positionButton.addActionListener(this);

        goButton = new JButton("go");
        goButton.setVerticalTextPosition(AbstractButton.CENTER);
        goButton.setHorizontalTextPosition(AbstractButton.CENTER);
        goButton.addActionListener(this);

        newButton = new JButton("new");
        newButton.setVerticalTextPosition(AbstractButton.CENTER);
        newButton.setHorizontalTextPosition(AbstractButton.CENTER);
        newButton.addActionListener(this);

        uci = new JButton("uci");
        uci.setVerticalTextPosition(AbstractButton.CENTER);
        uci.setHorizontalTextPosition(AbstractButton.CENTER);
        uci.addActionListener(this);

        quit = new JButton("quit");
        quit.setVerticalTextPosition(AbstractButton.CENTER);
        quit.setHorizontalTextPosition(AbstractButton.CENTER);
        quit.addActionListener(this);

        add(goButton);
        add(positionButton);
        add(newButton);
        add(uci);
        add(quit);
    }

    public void run() {
        JFrame frame = new JFrame("Send Commands");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setOpaque(true);
        frame.setContentPane(this);
        frame.pack();
        frame.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {

        String command  = e.getActionCommand();
        String toSend = null;
        if(command.equals("quit")) {
           toSend = "quit\n";
        } else if (command.equals("go")) {
           toSend = "go depth 8\n";
        } else if (command.equals("new")) {
           toSend = "ucinewgame\n";
        }   else if (command.equals("position")) {
           toSend = "position startpos\n";
        }   else if (command.equals("uci")) {
           toSend = "uci\n";
        }
        sendCommand(toSend);
    }

    public synchronized void sendCommand(String sendMe) {
        if(sendMe.equals("quit\n")) {
            engine.closeProcess();
        }
        engine.setCommand(sendMe);
    }

}

Code: Select all


/*
The two classes used as threads to read and write
*/

public class EngineReader implements Runnable {
        ProcessComm engine;

        public EngineReader(ProcessComm e) {
            this.engine = e;
        }

        public void run() {
            this.engine.readFromProcess();
        }
}

public class EngineWriter implements Runnable {
        ProcessComm engine;

        public EngineWriter(ProcessComm e) {
            this.engine = e;
        }

        public void run() {
            this.engine.writeToProcess();
        }
}
Sorry if that's a bit too long!

Again, the problem I'm having is getting readFromProcess() to sit and wake when input is available.

I tried with a wait(), but of course the thread only wakes when I command is sent.

Any help appreciated!

Thanks

Richard
Do you have anything to do when waiting on input? If not, a plain read() will work just fine. Otherwise I suspect you will need to use an I/O thread. In Unix, using C, one can use "select" to either test to see if input is present or to block waiting on I/O from several sources at the same time. Students taking my network programming course have reported no "select()" in Java (I am hardly a Java expert, however). That means a separate thread that can read the data from one source and post a global variable when data has been read...
Richard Allbert
Posts: 794
Joined: Wed Jul 19, 2006 9:58 am

Re: Reading Input from another process

Post by Richard Allbert »

Thanks for the reply.

I knew I should have stuck with C++ ;)

I'll try your suggestion tomorrow, and a couple of other things, and post the result

Regards

Richard
User avatar
ilari
Posts: 750
Joined: Mon Mar 27, 2006 7:45 pm
Location: Finland

Re: Reading Input from another process

Post by ilari »

I strongly advice against polling. It's going to be too inefficient and too laggy. The best solution is to put a loop in a separate thread that uses synchronous/blocking read calls to read data from a pipe. When a read call completes, the thread should signal the availability of data to the main thread, maybe by posting an event in the main event loop (I don't know how this works in Java).
Richard Allbert
Posts: 794
Joined: Wed Jul 19, 2006 9:58 am

Re: Reading Input from another process

Post by Richard Allbert »

Java does have pipe reading, according to the API.

I was using a buffered reader wrapped around an inputstream.

I'll try your suggestion, thanks.

Might be later this week, as I'm a bit pressed for time at the moment!

Ciao

Richard