Unity and ZeroMQ Integration

Hello,

It appears that there is already a lot of work towards Unity and ZeroMQ integration, and I’ve been able to use NuGet for Unity to get the NetMQ and System.ServiceModel.Syndication packages installed, and it initially works, assuming I have a separate program running the server prior to playing the scene. For background, I only wanted the NetMQ packages, but after installing that the Unity Editor had a compilation error saying that System.ServiceModel.Syndication was missing, so I added that too.

While initially the program seems to run fine, if I exit the scene via the play button and then attempt to replay the scene (again via the play button), the Unity editor gets frozen on “Reloading Domain,” and it requires closing the Unity Editor with task manager and restarting it, which takes way too much time and is incredibly inconvenient.

Question: Does anyone know why this is happening? Is there a way to avoid this?

Below is my code if that’s helpful for debugging/replication. I just attached this C# script to a cube object in a new scene in an otherwise blank project, and that’s enough for the Unity Editor (v2022.3.7f1) to be stuck in “Reloading Domain” perpetually.

More potentially relevant background, I did some testing with the ports (using netstat -ano in cmd) to see if there were any orphaned processes, and there might be some issues there - while the program is running, I see the following ports in use:

TCP 127.0.0.1:5556 0.0.0.0:0 LISTENING 25996 // This is the server
TCP 127.0.0.1:5556 127.0.0.1:52017 ESTABLISHED 25996 // This is the Unity program
TCP 127.0.0.1:52017 127.0.0.1:5556 ESTABLISHED 27572 // Not sure what this is, maybe some virtualization…?

After the program closes, we see the last entry above still present:

TCP 127.0.0.1:5556 0.0.0.0:0 LISTENING 25996 // The server again
TCP 127.0.0.1:52017 127.0.0.1:5556 TIME_WAIT 0 // ???

Please let me know if you need any further details!

CubeController:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NetMQ;
using NetMQ.Sockets;
using System.Threading;
using System;

public class CubeController : MonoBehaviour
{
    private double m_x;
    private double m_y;
    private double m_z;
    // Start is called before the first frame update

    Thread m_UpdatePositionThread;
    void Start()
    {
        m_UpdatePositionThread = new Thread(UpdatePosition);
        m_UpdatePositionThread.Start();
    }

    // Update is called once per frame
    void Update()
    {
        float x = (float)Math.Sin(m_x / 100 * Math.PI);
        float y = (float)Math.Sin(m_y / 100 * Math.PI);
        float z = (float)Math.Sin(m_z / 100 * Math.PI);
        transform.position = new Vector3(x, y, z);
    }

    public void UpdatePosition()
    {
        try
        {
            using (var client = new RequestSocket(">tcp://localhost:5556"))  // connect
            {
                while (true)
                {
                    // Send a message from the client socket
                    client.SendFrame("cube1");

                    // Receive the response from the client socket
                    string m2 = client.ReceiveFrameString();

                    String[] pos = m2.Split(',');
                    m_x = double.Parse(pos[0]);
                    m_y = double.Parse(pos[1]);
                    m_z = double.Parse(pos[2]);

                    Thread.Sleep(50);
                }

            }
        } catch (ThreadInterruptedException)
        {
            Debug.Log("Closing out of the cube thread.");
        }
       
    }

    private void OnApplicationQuit()
    {
        Debug.Log("Ending the application");
        if (m_UpdatePositionThread != null) m_UpdatePositionThread.Interrupt();
        m_UpdatePositionThread = null;
    }
}

Separate C# program main, ran via Visual Studio 2022:

using NetMQ;
using NetMQ.Sockets;
using zeroMQTestingServer;

PosSource source = new PosSource();

using (var server = new ResponseSocket("@tcp://localhost:5556")) // bind
{
    while (true)
    {
        string m1 = server.ReceiveFrameString();

        if (m1 == "cube1")
        {
            string response = source.GetPosition();

            Console.WriteLine("Entered cube1, resp -> " + response);
            server.SendFrame(response);
        }
    }
}

The class PosSource, used by the separate C# program:

namespace zeroMQTestingServer
{
    internal class PosSource
    {
        public double m_x;
        public double m_y;
        public double m_z;

        public PosSource()
        {
            m_z = 1;
            Thread updateVals = new Thread(TruthUpdate);
            updateVals.Start();
        }

        private void TruthUpdate()
        {
            while (true)
            {
                m_x++;
                m_y++;
                Thread.Sleep(200);
            }
        }

        public string GetPosition()
        {
            return "" + m_x + "," + m_y + "," + m_z;
        }

    }
}

Found a pretty good solution at the very bottom here: Unity get stuck · Issue #526 · zeromq/netmq · GitHub

When I check out the ports via netstat -ano, I still see processes on the ports, but some aspect of the code changes compensates(?). Not entirely sure why it works, but it does with the code below.

CubeController:

using UnityEngine;
using NetMQ;
using NetMQ.Sockets;
using System;
using UnityEngine.Assertions;

public class CubeController : MonoBehaviour
{
    private double m_x;
    private double m_y;
    private double m_z;
    // Start is called before the first frame update
    public RequestSocket _client;

    public float _updateInterval = 0.01f;

    private float _timer = 0;
    private int _cnter = 1;

    public int _port = 5556;
    void Start()
    {
        AsyncIO.ForceDotNet.Force();
        NetMQConfig.Linger = new TimeSpan(0, 0, 1);

        _client = new RequestSocket();
        _client.Options.Linger = new TimeSpan(0, 0, 1);
        _client.Connect($"tcp://localhost:{_port}");
        print($"client connects {_port}");

        Assert.IsNotNull(_client);
    }

    void OnDisable()
    {
        _client?.Dispose();
        NetMQConfig.Cleanup(false);
    }

    // Update is called once per frame
    void Update()
    {
        _timer += Time.deltaTime;
        if (_timer >= _updateInterval)
        {
            _timer = 0;
            var c_sent = "cube1";
            _client.SendFrame(c_sent);
            print($"client sents: {c_sent}");

            string c_recv = _client.ReceiveFrameString();
            print($"client receives {c_recv}");
         
            string[] pos = c_recv.Split(',');
            m_x = double.Parse(pos[0]);
            m_y = double.Parse(pos[1]);
            m_z = double.Parse(pos[2]);

            float x = (float)Math.Sin(m_x / 500 * Math.PI);
            float y = (float)Math.Sin(m_y / 500 * Math.PI);
            float z = (float)Math.Sin(m_z / 500 * Math.PI);
            transform.position = new Vector3(x, y, z);

            _cnter++;
        }
    } 
}

Server (external to Unity)

using NetMQ;
using NetMQ.Sockets;
using zeroMQTestingServer;

PosSource source = new PosSource();

using (var server = new ResponseSocket("@tcp://localhost:5556")) // bind
{
    while (true)
    {
        string m1 = server.ReceiveFrameString();

        string response = source.GetPosition();

        Console.WriteLine("Entered cube1, resp -> " + response);
        server.SendFrame(response);     
    }
}

PosSource, used by the Server:

namespace zeroMQTestingServer
{
    internal class PosSource
    {
        public double m_x;
        public double m_y;
        public double m_z;

        public PosSource()
        {
            m_z = 1;
            Thread updateVals = new Thread(TruthUpdate);
            updateVals.Start();
        }

        private void TruthUpdate()
        {
            while (true)
            {
                m_x++;
                m_y++;
                Thread.Sleep(40);
            }
        }

        public string GetPosition()
        {
            return "" + m_x + "," + m_y + "," + m_z;
        }

    }
}