Controlling Unity Application Through NamedPipe
Tags: unity3d, csharp
Published: 2021-02-17 08:32:27

This article shows how to control scenes on a Unity standalone player by a Windows application with NamedPipe like the following demonstration.  The full source is on [the GitHub repository](https://github.com/fujieda/UnityNamedPipe).

{% youtube 9cx0xN9AJ88 %}

## Windows Side

You can use [this IPC class](https://github.com/fujieda/UnityNamedPipe/blob/main/Client/Client/IPC.cs) as a NamePipe client to send requests to a Unity standalone player. In the WPF application, a property setter bound to a view sends a request with `IPC.Send` as follows. `IPC.Send` waits and returns the response from the player.

```csharp
public string SelectedScene
{
    set
    {
        if (value == null)
            return;
        Task.Run(async () =>
        {
            IsEnabled = false;
            try
            {
                Status = await _ipc.Send("Scene " + value);
            }
            catch (Exception e)
            {
                Status = e.Message;
            }
            IsEnabled = true;
        });
    }
}
```

## Unity Side

In the first place,  you must change Api Compatibility Level to .NET 4.x in Player Settings to make NamedPipe available. NamePipe works on the Unity editor's play mode even in .NET Standard 2.0,  but it results in an error on a standalone player.

![unity player settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cl7s4kkkdge7uivdg5rc.png)

 You can use [this IPC class](https://github.com/fujieda/UnityNamedPipe/blob/main/Server/Assets/Scripts/IPC.cs) as a NamedPipe server to receive requests from a Windows application. The following is the implementation of `IPC.Receive` to wait for connections from a Windows application.

```csharp
private static async UniTask<string> Receive()
{
    try
    {
        _pipe = new NamedPipeServerStream(PipeName);
        return await UniTask.Run(() =>
        {
            var buffer = new byte[1024];
            _pipe.WaitForConnection();
            var len = _pipe.Read(buffer, 0, buffer.Length);
            return Encoding.UTF8.GetString(buffer, 0, len);
        });
    }
    catch (Exception e)
    {
        _pipe?.Dispose();
        Debug.Log(e.Message);
        return "";
    }
}
```
This is an asynchronous method implemented with [UniTask](https://github.com/Cysharp/UniTask). UniTask is much easier than coroutines to implement asynchronous operations. This method uses synchronous methods of NamedPipe because Unity doesn't implement the asynchronous versions. They don't cause any errors in compilation but result in errors at runtime. 

The following Controller class processes requests from the Windows application. You have to put an empty GameObject on every scene and attach a Controller object to the GameObject.

```csharp
public class Controller : MonoBehaviour
{
    private GameObject _target;

    private void Awake()
    {
        if (FindObjectsOfType<Controller>().Length > 1)
            Destroy(gameObject);
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        _target = GameObject.Find("/Target");
        IPC.Run(Handler);
    }

    private async UniTask Handler(string message)
    {
        var tokens = message.Split(' ');
        if (tokens.Length != 2)
        {
            await IPC.Send("Invalid message: " + message);
            return;
        }
        switch (tokens[0])
        {
            case "Scene":
                SceneManager.LoadScene(tokens[1]);
                await IPC.Send("Succeeded");
                _target = GameObject.Find("/Target");
                return;
            case "Color":
                if (_target == null)
                {
                    await IPC.Send("Target not exists");
                    return;
                }
                if (ColorUtility.TryParseHtmlString(tokens[1], out var color))
                    _target.GetComponent<Renderer>().material.color = color;
                await IPC.Send("Succeeded");
                return;
        }
        await IPC.Send("Invalid command: " + tokens[0]);
    }

    private void OnApplicationQuit()
    {
        IPC.Close();
    }
}
```

`Awake` invokes `DontDestroyOnLoad` not to destroy the Controller on a scene change. Otherwise, the Controller can't send the response of a scene change request after `LoadScene`. `Start` invokes `IPC.Run` with a request handler to start receiving requests.

`OnDisable` invokes `IPC.Close` of which implementation is shown below. The method immediately returns on the scene change, in which case the pipe is connected.

```csharp
public static void Close()
{
    if (_pipe != null && _pipe.IsConnected)
        return;
    _cancelled = true;
    try
    {
        var pipe = new NamedPipeClientStream(PipeName);
        pipe.Connect(500);
        pipe.Flush();
        pipe.Close();
    }
    catch (FileNotFoundException)
    {
    }
    catch (Exception e)
    {
        Debug.Log(e.Message);
    }
    finally
    {
        _pipe?.Close();
    }
}
```

Otherwise, it connects its own NamedPipe to stop `IPC.Receive` waiting for connections. When the code runs on the editor's play mode, if not doing so, a waiting thread leaves on the editor and freezes it on terminating and reloading code.

I originally implemented the above IPC classes. Some articles are showing how to use NamedPipe on Unity. But they are just experimentations and not appropriate to use in real applications. So I Implemented them.