1

I am quite new to Unity and i am currently using it as a tool for designing a simple simulated environment for my pilot research study. Here is a simplified explanation:

The main character (agent) scans the environment and sends the state (as a JSON object) via TCP socket to a python server.

On the python server side a big calculation is performed based on the the state. The result is an action that is being sent back via TCP to Unity.

The character needs to perform this action in Unity and then again scan the environment. The new state is sent again to the python server....

This process is being repeated 'till infinity (unless the client or server is stopped) eventually leading to the agent self-developing a behavior based on its learning.

I finished creating the environment in unity as well as programmed the learning algorithm in python. Moreover, i managed to establish TCP connection between the two. However, i stumbled across the problem of the main Update() loop in Unity.

Namely, if i simplify the process to Unity sending ping (being the state) and python sending pong (being the action) i need the following process to be repeated.

  • freeze frame
  • ping
  • calculation
  • pong
  • next frame

So in the Unity Start() method i setup the socket and send the initial ping. What i would like in the main Update() loop is to make Unity wait for python's pong answer before updating the frame. I created a script containing a toy example and tried to simulate the calculation-time in python by adding 2 seconds of delay before sending the pong to Unity.

Here is the code for the Unity script (just attach it to the Main Camera):

 using UnityEngine;
 using System;
 using System.IO;
 using System.Net.Sockets;

 public class networkSocketPingPong : MonoBehaviour
 {
     public String host = "localhost";
     public Int32 port = 50000;

     internal Boolean socket_ready = false;
     internal String input_buffer = "";

     TcpClient tcp_socket;
     NetworkStream net_stream;

     StreamWriter socket_writer;
     StreamReader socket_reader;

     private void Start()
     {
         setupSocket();
         writeSocket("ping");
     }


      void Update()
      {

         string received_data = readSocket();

         switch (received_data)
              {
                  case "pong":
                      Debug.Log("Python controller sent: " + (string)received_data);
                      writeSocket("ping");
                      break;
                  default:
                      Debug.Log("Nothing received from Python");
                      break;
          }
     }

     void OnApplicationQuit()
     {
         closeSocket();
     }

     // Helper methods for:
     //...setting up the communication
     public void setupSocket()
     {
         try
         {
             tcp_socket = new TcpClient(host, port);
             net_stream = tcp_socket.GetStream();
             socket_writer = new StreamWriter(net_stream);
             socket_reader = new StreamReader(net_stream);
             socket_ready = true;
         }
         catch (Exception e)
         {
             // Something went wrong
             Debug.Log("Socket error: " + e);
         }
     }

     //... writing to a socket...
     public void writeSocket(string line)
     {
         if (!socket_ready)
             return;

         socket_writer.Write(line);
         socket_writer.Flush();
     }

     //... reading from a socket...
     public String readSocket()
     {
        if (!socket_ready)
         {
             Debug.Log("Socket is not ready");
             return "";
         }

         if (net_stream.DataAvailable)
             return socket_reader.ReadLine();

         return "";
     }

     //... closing a socket...
     public void closeSocket()
     {
         if (!socket_ready)
             return;

         socket_writer.Close();
         socket_reader.Close();
         tcp_socket.Close();
         socket_ready = false;
     }
 }

...and here is the python server code:

 import socket
 import time

 host = 'localhost' 
 port = 50000
 backlog = 5 
 size = 1024 
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 s.bind((host,port)) 
 s.listen(backlog) 

 while 1:
     client, address = s.accept() 
     print "Client connected."
     while 1:
         data = client.recv(size)
         if data == "ping":
             time.sleep(2)
             print ("Unity Sent: " + str(data))
             client.send("pong\n")
         else:
             client.send("Bye!")
             print ("Unity Sent Something Else: " + str(data))
             client.close()
             break

I first run the python server and then the simulation (via Unity editor). This results with the following console screenshot:

enter image description here

This proves that the main Update() loop is running while i want it to pause and update only when "pong" is received from the server. Any ideas how to achieve this?

I searched the forum for answers but always stumbled upon questions that ask for the opposite - how to make Unity not to freeze - and answers that suggest using Coroutines or Threads.

Any help will be much appreciated.

Thanks in advance!!!

thecritter
  • 73
  • 1
  • 6
  • You can change void `Update()` to `IEnumerator Update()` and use `yield return WWW` where WWW is your web call. It will then wait until the download is complete before continuing. – Fredrik Schön Mar 03 '17 at 09:03
  • Completely blocking Unity whilst it waits for a server will include your UI's too - it will completely feel like it has crashed (on some platforms it will prompt with a "this program is not responding" message too). Make certain parts of your project 'wait' - i.e. NPC's that do nothing until they receive that message from the server. Never block the main Unity thread. – Luke Briggs Mar 06 '17 at 02:02

4 Answers4

2

I searched the forum for answers but always stumbled upon questions that ask for the opposite - how to make Unity not to freeze - and answers that suggest using Coroutines or Threads.

That's right. This was my initial thought too after your bulletin below:

  • freeze frame
  • ping
  • calculation
  • pong
  • next frame

That translated into a code would be:

void Start()
{
    StartCoroutine(doForever());
}


IEnumerator doForever()
{

    while (true)
    {
        //freeze frame
        Time.timeScale = 0f;

        //ping
        writeSocket("ping");

        //calculation

        //pong
        string pongValue = socket_reader.ReadLine();

        //unfeeze frame
        Time.timeScale = 1f;

        //next frame
        yield return null;
    }
}

This is so wrong in many ways. This will freeze Unity when waiting or receiving data from python. This may be something you want but it's not good in any way.

You must do this in a Thread. The reason for this is because you are using the blocking version of the receive function.

Thread socketThread;

void Start()
{
    socketThread = new Thread(doForever);
    socketThread.IsBackground = true;
    socketThread.Start();
}

void doForever()
{

    while (true)
    {
        //freeze frame
        gameStatus = GameStatus.PAUSED;

        //ping
        writeSocket("ping");

        gameStatus = GameStatus.RUNNING;
        //calculation

        //pong
        string pongValue = socket_reader.ReadLine();
    }
}

If you want to use Unity's API in from another Thread or the code above you should look at this post.

Do not use read from socket from the main Thread like you are currently doing. One exception is when using the async functions but you are not in this case.

There are many reasons for this and the worst one is your UI not being responsive until you receive something from your python code.


As for pausing your game, you can simply use enum and if statement to accomplish that.

public enum GameStatus
{
    RUNNING, PAUSED
}

Then in every of your function that is noving something, you can do something like this:

GameStatus gameStatus;

void moveCharacterEveryFrame()
{
    if (gameStatus == GameStatus.PAUSED)
    {
        return;
    }

    //Move Character code below
}

When you want to pause your game from that Thread above, you just do this:

gameStatus = GameStatus.PAUSED;

To unpuse:

gameStatus = GameStatus.RUNNING;

Now, you don't freeze your Game or UI.

Community
  • 1
  • 1
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • This makes a lot of sense. Will try this as soon as possible. However, things get complicated since some of my GameObjects (enemies) are dynamically generated using a Coroutine. I am afraid that this approach would freeze my main character but not the enemies. Will get back soon. Many sincere thanks!!! – thecritter Mar 03 '17 at 11:57
  • I played with your second suggested solution. It seems that it is too complicated (and not feasible) to implement it for my purposes. I then came back to your "coroutine" suggestion. It seems that this is very close to what i need. Do not forget that i am a 3-weeks Unity user and need this as a proof of concept in AI. Moreover, the server and client are both on the same PC so i could live with lag. But, how can i pause the physics and rendering (the Update() loop) from within the coroutine? Is there a way to do so? – thecritter Mar 06 '17 at 13:32
  • I showed you the proper way to pause, you can stop animation or physics when you read the `GameStatus` state in my answer. If you still want **improper** way to pause then use `Time.timeScale = 0f` for pausing and `Time.timeScale = 1.0f;` to unpause. Added that to my first coroutine answer. Remember that I **don't** recommend that. You can always come back to the second part of my answer when you learn about Networking and Multi-Threading in C#. Happy coding! – Programmer Mar 06 '17 at 13:47
  • I really cannot understand how is it possible that it does not have an "out of the box" approach to utilize automatic (intelligent) control obtained from a server. I mean, what i aim to achieve is very simple - pause the whole process of physics/rendering while awaiting for certain server response without crashing the editor. Why do Threads or Coroutines have to be involved? If i think about a real, complex game i don't find that the `GameStatus` approach is feasible (manually adding code to all those scripts attached to all sorts of game objects) Sorry again for thinking out loud. – thecritter Mar 06 '17 at 14:22
  • Your problem has nothing to do with Unity. When designing game, there is usually a dedicated team that makes the pause/resume mechanism. Although it can be done by one person. Look up **state machine**. Enum is the best way to do this in Unity. I think you should learn more about C# before continuing with Unity. Some people jump to Unity without background knowledge of C# and they end up having hard times. Learn C#. Start from basic to advance(Multi-Thread and Networking) then come back to Unity. Also learn about state machine in Unity. This will simply your problem. – Programmer Mar 06 '17 at 14:26
  • By the way... do not get me wrong. I really appreciate yours (and everyone elses) effort in helping me. I simply identify an aspect (real world problem) that Unity does not foresee. – thecritter Mar 06 '17 at 14:39
  • That's fine. I only view you as being new to Unity and C#. Most things are very easy in Unity. Once it involves socket, it becomes a complicated/advance topic. Did `Time.timeScale` work for you? – Programmer Mar 06 '17 at 14:45
  • 1
    'Time.timeScale' works in the ping-pong example. I now have to try it in the real simulation. As per documentation, this should work and should stop the global time. Right now i am not worried about performance. I may however dive deeper into Unity and follow your advice on "reading basic C#". Thanks a lot! Will get back soon. – thecritter Mar 06 '17 at 18:00
1

Delete your Update() function and add put your code to callback function of your WriteSocket() function.

maximelian1986
  • 2,308
  • 1
  • 18
  • 32
  • Also you may try to change TimeScale https://docs.unity3d.com/ScriptReference/Time-timeScale.html – maximelian1986 Mar 03 '17 at 08:47
  • `TimeScale` has nothing to do with `Update` method or rendering itself. – mrogal.ski Mar 03 '17 at 11:35
  • You are right. But stopping the global time seems to be enough for my purpose since it temporary stops and can be resumed after the computation from the servers "arrives". – thecritter Mar 06 '17 at 18:02
1

You can use Coroutine to create Thread :

public Action<string> OnReceiveMessage;  // event trigger when receive message.

private Thread m_ReceiveThread;
private List<string> m_ReceiveStrings;
private int m_ReceiveStrCount;

private void Start() {
    m_ReceiveStrings = new List<string> ();
    m_ReceiveStrCount = m_ReceiveStrings.Count;
    StartCoroutine (CreateReceiveListener());
}

private IEnumerator CreateReceiveListener() {
    m_ReceiveThread = new Thread (ReceiveListener);
    m_ReceiveThread.IsBackground = true;
    m_ReceiveThread.Start ();
    yield return null;
}

Add to List a message:

private void ReceiveListener() {
    while (m_Connected) {
        string receiveData = ReadSocket ();
        if (string.IsNullOrEmpty (receiveData) == false) {
            m_ReceiveStrings.Add (receiveData);
            Debug.Log (receiveData);
        }
    }
}

Then check in lateupdate if have any change:

private void LateUpdate() {
    if (m_ReceiveStrCount != m_ReceiveStrings.Count) {
        if (OnReceiveMessage != null) {
            OnReceiveMessage (m_ReceiveStrings[m_ReceiveStrCount]);
            m_ReceiveStrCount = m_ReceiveStrings.Count;
        }
    }
}
Cổ Chí Tâm
  • 330
  • 1
  • 7
0

If I understood correctly you want to hang the Unity's internal drawing mechanism till you receive new data from the server?

If that's the case then just wait for it in the Update or LateUpdate method :

void Update() // or void LateUpdate()
{
    while(!received && Application.isRunning){
        // do nothing
    }

    // receive
    string msg = readSocket();
    // log
    Debug.Log(msg);
}

But this is not how you should use Unity engine.

If that's not the thing you've wanted then put the comment and I'll remove this answer :)

mrogal.ski
  • 5,828
  • 1
  • 21
  • 30
  • Thanks a lot. You do not have to remove the comment since it is very helpful. I just need to find a way to avoid the editor freezing once i stop the python server. Very promising!!! – thecritter Mar 03 '17 at 09:51
  • For this, you would have to send some message from python server to your Unity project and then quit the apllication ( stop playmode ) or just remove the component which is blocking the UI thread. – mrogal.ski Mar 03 '17 at 09:57
  • It turns out that your proposed solution only postpones the drawing mechanism but not the physics. This causes the environment to change while the calculation is taking place. What i really need is the physics to be paused. Thanks anyway! – thecritter Mar 06 '17 at 13:17
  • Yes, It was intended to hold the drawing till new message arrives from the server. To hold and wait for the Physics update ( `FixedUpdate` method ) you would have to change physics settings for your project. I will find out if it is possible to do this via code during runtime and I'll update you later with the answer edit or comment. – mrogal.ski Mar 06 '17 at 13:38