Unity C#: Communicate with console program asynchronously

Viewed 78

I'm currently trying to write a chess GUI in Unity. I have a chess engine as an exe-file which communicates via UCI (console). The program is in a very early stage and I just want to communicate with the chess engine (console application) as a proof of concept.

At the moment I'm using the Process class with a redirected output and input. Everything that the chess engine outputs is currently being printed in the unity console. Unfortunately nothing I try works like expected.

How the Chess Engine (Console Program) works:

  • User can write a command in the console e.g. "uci" or "go dept 1"
  • The engine will respond with "uciok" or "bestmove e2e4"
    These responses can also have multiple lines.

What I want:

  1. Press play in Unity
  2. My script instantly sends the message "uci" to the chess engine
  3. The output (muliple lines) gets printed to the unity console
  4. Everytime I press 'c' step 2 and 3 get repeated

What currently happens:

  1. Press play in Unity
  2. Console (chess engine) opens
  3. I manually have to close the console window
  4. The message "uci" gets sent to the chess engine
  5. Multiple lines get outputted to the unity console
  6. When I press 'c' I'm getting an IOException

What I've also tried:

  • startInfo.CreateNoWindow = true; --> I have to close unity with the task manager as it waits until the process finishes
  • engineProcess.WaitForExit(); --> Does not work, as the process is not supposed to exit
  • engineProcess.BeginOutputReadLine(); --> I'm getting spammed by unity with exceptions because I'm mixing synchronus and asynchronous operations
  • Many other solutions already suggested on StackOverflow. Nothing worked so far :(

I'm pretty new to Unity and C#, so I hope that this question does not make me look stupid. I posted my code below.

public string pathToExe = "PathToExe";
private Process engineProcess;

//gets automatically called once when I press start in Unity 
private void Start() {
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.CreateNoWindow = false;
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardOutput = true;
    startInfo.RedirectStandardInput = true;
    startInfo.FileName = pathToExe;

    //Start process and write "uci" to chess engine
    try {
        engineProcess = Process.Start(startInfo);
        engineProcess.StandardInput.WriteLine("uci");
    }
    catch {
        UnityEngine.Debug.Log("Error when starting Engine");
    }
}

//gets called every tick when Unity is running
private void Update() {
    //write uci to chess engine when c is pressed
    if (Input.GetKeyDown(KeyCode.C)) {
        engineProcess.StandardInput.WriteLine("uci");
    }
    //read engine output if it exists
    while (!engineProcess.StandardOutput.EndOfStream) {
        string line = engineProcess.StandardOutput.ReadLine();
        UnityEngine.Debug.Log(line);
    }
}
1 Answers

Thanks to @BugFinder to pointing out that there are events that you can attach a method to. Using your tipps and the documentation of Process.BeginOutputReadLine I was able to solve the problem.
I commented where I added or removed line in comparison with the original code.

public string pathToExe;
private Process engineProcess;

private void Start() {
    ProcessStartInfo startInfo = new ProcessStartInfo();

    startInfo.CreateNoWindow = false;
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardOutput = true;
    startInfo.RedirectStandardInput = true;
    startInfo.FileName = pathToExe;

    try {
        engineProcess = Process.Start(startInfo);
        engineProcess.StandardInput.WriteLine("uci");
    }
    catch {
        UnityEngine.Debug.Log("Error when starting Engine");
    }

    //Added these two lines so the method "engineOutputHandler" 
    //gets automatically called every time an output is received
    engineProcess.OutputDataReceived += engineOutputHandler;
    engineProcess.BeginOutputReadLine();
}

private void Update() {
    if (Input.GetKeyDown(KeyCode.C)) {
        engineProcess.StandardInput.WriteLine("uci");
    }

    //removed the entire while loop

}

//Added this method, which prints out the data received
private void engineOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
    if (!string.IsNullOrEmpty(outLine.Data)) {
        UnityEngine.Debug.Log(outLine.Data);
    }
}

Related