I have a game that requires interfacing with a SPICE Simulation Software (electrical circuit simulator). This software is command-line based, meaning you normally run it from CMD or Powershell. You input some data or the path to a simulation file through the command line, and the software returns the simulation information, also through the command line.
I was wondering how I can get my Unity game to launch this CLI software (preferably not in a CMD window that the user can see, I would rather all happened under the hood), then send the required data to the program CLI (via I would assume a string variable), then get the returned data from the simulation (also probably through a string).
I’ve tried to implement the system using the C# processes. Just using process.Start() on its own freezes my program until the opened .exe’s window is closed, (I’m running the external .exe in the start function of a script). To solve this, I have tried running process.Start() inside a new thread, which runs my main program as usual, but it doesn’t seem to receive outputs from the .exe running on the other thread.
What exactly did you do? In the past I actually did successfully redirect the input / output of a CLI application.
process.Start does not block, process.WaitForExit does. If you already have some code that doesn’t work, you should include that in your post.
The answers provides different approaches for different situations. Though generally you have to set “UseShellExecute” to false in order to start the process directly and being able to redirect the input / output. Keep in mind that this external application runs asynchronously from your own and reading from / writing to the standard I/O may not always read / write all of the data you may expect. So depending on what data your application expects / outputs you have to be careful to properly receive it on the other end. Though we know literally nothing about the application you try to run and how your current setup looks like.
I had process.Start() in the void Start() function, and for some reason, this blocked my program from launching. When I instead put process.Start() in the void Update(), it was not blocking, even when I didn’t launch the process from a separate thread.
I’m sorry I didn’t post some more context as to what I’m actually trying to run and my methods. I will do that here.
The software I’m trying to run is Ngspice, a CLI electrical simulation software. There are two versions of the application, the normal version, which launches in its own GUI (which is basically just a CLI), and another version which is specifically a console version, which lanches in CMD when you open it in file explorer. I assume the console version of the app is what I need for my purposes, so my latest attempts to implement this has been using this version.
When I launch the console version of the application, I get this output at the very start:
So this is what I would expect to be outputted to my Unity application when this process is created.
At the moment, the code that I have for launching and interfacing with this program is what is below.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using System;
using System.Threading;
using System.IO;
using System.Text;
public class SPICE_interface : MonoBehaviour
{
bool once = true;
string output = "";
StringBuilder outputStringBuilder = new StringBuilder();
Process spiceProcess = new Process();
StreamWriter spiceCLIin;
//StreamReader spiceCLIout;
void Start()
{
spiceProcess.StartInfo.RedirectStandardInput = true; //setting necessary launch parameters
spiceProcess.StartInfo.RedirectStandardOutput = true;
spiceProcess.StartInfo.CreateNoWindow = true;
spiceProcess.StartInfo.UseShellExecute = false;
string filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "Spice64\\bin\\ngspice_con.exe"); //setting the filepath
spiceProcess.StartInfo.FileName = filePath;
spiceProcess.EnableRaisingEvents = false;
spiceProcess.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data); //preparing for async data recieving
StartCoroutine(WriteTestCoroutine()); //this function writes text to the lanched process after a delay
}
void Update()
{
if(outputStringBuilder.ToString() != ""){ //if the output string builder is not empty, output to debug log
UnityEngine.Debug.Log(outputStringBuilder.ToString());
}
runOnce(); //run once lanunched the process
}
void runOnce(){
if(once){ //if the once variable is true, launch the process
if(spiceProcess.Start()){
UnityEngine.Debug.Log("Ngspice process started successfully");
spiceProcess.BeginOutputReadLine(); //begin output readline
spiceCLIin = spiceProcess.StandardInput; //setting up a stream writer to write data to CLI
}
else UnityEngine.Debug.Log("Ngspice process failed to start");
once = false;
}
}
IEnumerator WriteTestCoroutine(){
yield return new WaitForSeconds(10); //waits 10 secs to ensure process had fully lanched
UnityEngine.Debug.Log("Writing Text"); //writes to Log to ensure this part has been executed
spiceCLIin.WriteLine("testing"); //writes text to streamwriter, the usual reply by the CLI is "testing: no such command available in ngspice"
}
}
The console output says the process was successfully launched, and after 10 seconds, it says the test data was written to the process using the stream writer, but there is never any output from the process that I can see printed to the Debug log of Unity.
I just had a quick look at ngspice_con. I think you should be careful how you use it. It also has an interactive mode and I’m not sure if it actually uses standard I/O. However running the application in batch mode with all necessary parameters should work fine I guess. Though I’m not really familiar with the interface or syntax of that application (though I’m a trained electrical engineer ^^). The application seems to have many different operation modes (it also has a server mode). Unfortunately I don’t have the time to dig into that.
Note that the Unity editor does have an internal utility class called “Program” which does exactly that, wrapping the Process class in order to call CLI applications and receive the output. Though most CLI applications are run in a write - process - read manner and not really interactively. I guess the editor uses this class to call things like the mono compiler, androids adb interface or other external CLI applications. It’s hard to tell if your problems are rooted on your side or on the side of ngspice.
Assuming as Bunny pointed out above that it uses stdio, and further that it writes to stdout.
Often times command line apps write their “usage details” and “intro header” to stderr instead.
Hack up a little c / cpp program that writes some output to stdout, then spawn that from Unity, and get that working first before you try to fire up this massive battleship infrastructure of this circuit simulator software.
Always start with the minimum known working function, and we know Unity can exec stuff.
Right, after I posted my last answer I wanted to point out to monitor stderr as well, especially since he expects the application to print out an error when using a non existent command (“testing” in his case). Since most CLI applications direct both stdout and stderr to the console you don’t really see the difference.
Just want to point out, if you need to run another application and block until it completes, you can just do that from another thread. That way you won’t block the main thread.
As @Kurt-Dekker suggested, I created a quick console app to test whether Unity was seeing the standard outputs of the process, and this was successful, meaning Unity wasn’t seeing specifically Ngspice’s outputs. I had a look at the Ngspice documentation, and saw that the launch flag “-p” allows a program to act as a GUI frontend for ngspice through a pipe. This sounded promising, so I added spiceProcess.StartInfo.Arguments = "-p"; to my Unity script.
This solved my issue. Now when I send “help all” to “ngspice_con”, I get the entire output that I would get from the program normally, the stdout stream seems to be working!
One problem still persists, but it isn’t entirely game-breaking, so I can continue my project without this feature if necessary. The errors don’t seem to get picked up by Unity. For example, when I do commands such as “help all” or “newhelp ac”, the output of the CLI is received by Unity, because the resultant outputs of these commands are not errors. When I do something like “testing”, or a command that gives an error, Unity does not read an output. (The intro header of ngspice_con.exe is also not picked up by Unity)
I have done spiceProcess.StartInfo.RedirectStandardError = true; and spiceProcess.ErrorDataReceived += (sender, eventArgs) => UnityEngine.Debug.Log(eventArgs.Data);, but this stderr stream doesn’t seem to pass any data.
Interesting! I wonder if it’s just because they are bypassing BOTH stdout and stderr for their error messages? You could hack your little console app to spew to stderr and see if you can grab that. It’s possible the SPICE thing’s -p option doesn’t support redirecting the error stream.
I just made a dumb mistake when I went to implement stderr redirection. I followed @Kurt-Dekker 's suggestion again and implemented an error output in my small test app and saw that the errors from this also weren’t coming through.
I found that I had accidentally omitted the line spiceProcess.BeginErrorReadLine(); after launching the process. After adding this time in, everything, including the errors, are being picked up by the Unity app. (The intro header is still not being passed, but that is completely fine)