0
\$\begingroup\$

My game freezes randomly, mostly when changing from one level to the next. I cannot tell why this is the case. There are no errors or warnings. This happens when i build the game and when I am testing the game in the Unity editor. It will say unfreeze itself after a few seconds. I have attached one of the level codes and the timer code. I have also attached a picture of how it looks when it freezes.enter image description here This is one of the level codes :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class Arduino5 : MonoBehaviour
{
    SerialPort sp = new SerialPort("\\\\.\\COM5", 9600);// Com port and the baud rate of the arduino
    Material m_Material;
    GameObject Sphere;

    void Awake()
    {
        Sphere = GameObject.FindWithTag("Player");
        m_Material = GameObject.FindWithTag("Player").GetComponent<Renderer>().material;
    }
    void Start()
    {
        if (!sp.IsOpen)
        { // If the erial port is not open 
            sp.Open(); // Open 
        }
        sp.ReadTimeout = 250; // Timeout for reading 
    }

    // Update is called once per frame
    void Update()
    {
        if (sp.IsOpen)
        { // Check to see if the serial port is open 
            try
            {
                string portreading = sp.ReadLine(); // get the string output of the serial port 
                float amount = int.Parse(portreading);

                if ((amount > 50f) )
                {
                    m_Material.color = Color.grey;
                    Renderer _rend = Sphere.GetComponent<Renderer>();
                    Sphere.GetComponent<Renderer>().material = _rend.material;
                    _rend.material.mainTexture = Resources.Load("face4") as Texture;

                }
                else
                {
                    m_Material.color = Color.white;
                }
            }
            catch (System.Exception)
            {

            }
        }
    }
}

and this is the Timer code:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class Timer : MonoBehaviour
{
    public int timeLeft = 5;
    public Text countdownText;

    // Use this for initialization
    void Start()
    {
        StartCoroutine("LoseTime");
    }

    // Update is called once per frame
    void Update()
    {
        countdownText.text = ("Time Left = " + timeLeft);

        if (timeLeft <= 0)
        {
            StopCoroutine("LoseTime");
            countdownText.text = "Times Up!";
            Invoke("ChangeLevel", 1.0f);
        }
    }

    IEnumerator LoseTime()
    {
        while (true)
        {
            yield return new WaitForSeconds(1);
            timeLeft--;
        }
    }
    void ChangeLevel()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }
}

In terms of trying to close the serial port, would something like this work:

void Start()

{ if (!sp.IsOpen) { // If the erial port is not open sp.Open(); // Open sp.ReadTimeout = 250; }

// Timeout for reading 
else if (sp != null)
{

    {
        sp.Close();
    }
}

}

\$\endgroup\$
6
  • 1
    \$\begingroup\$ I'm not too familiar with Unity, but when it freezes, can you "pause" the game in the debugger and see where the call stack is? If you do that a couple of times, you'll most likely spot where the game is "stuck". \$\endgroup\$
    – Vaillancourt
    Commented Aug 15, 2018 at 0:50
  • \$\begingroup\$ When it freezes, I cannot press the "pause" button unfortunately \$\endgroup\$
    – user19964
    Commented Aug 15, 2018 at 0:52
  • \$\begingroup\$ I haven't worked much with the SerialPort, but it looks like you're using a blocking method that can stall the main thread for up to 0.25 seconds each frame when a new line of data is not yet available to read. I also don't see where you close & dispose of the SerialPort when unloading this object (eg. when the scene changes or the game is stopped). Have you considered offloading this serial port IO to a separate thread with its own lifetime? \$\endgroup\$
    – DMGregory
    Commented Aug 15, 2018 at 0:57
  • \$\begingroup\$ I did not know that one had to close and dispose of the SerialPort when the scene changes. How do I do this? \$\endgroup\$
    – user19964
    Commented Aug 15, 2018 at 1:00
  • \$\begingroup\$ Why would you try to close the serial port inside the Start method? Isn't Start generally the beginning of when you want to use it, not the end? \$\endgroup\$
    – DMGregory
    Commented Aug 15, 2018 at 11:09

1 Answer 1

1
\$\begingroup\$

I'd be tempted to pull the serial port processing out of Unity's game loop, to ensure:

a) the I/O can't stall and cause our game loop to stutter or lag.

b) stuff we do in the scene can't inadvertently break the serial connection or misuse the shared COM resources in some way.

I can't guarantee either of those points above relates to your issue, but I have experienced stalls in the past when working with external connections if I got sloppy and tried to open two of the same connection / left a connection open after I was done with it / closed & re-opened connections excessively.

Here's one way we can make this a bit easier to tame:

public class SerialReader {

    // Keep just one open reader at a time for each port, so we don't
    // accidentally pile up several on the same shared resource.
    static Dictionary<string, SerialReader> _readers; 

    public static SerialReader GetOrOpen(string portName, int baudRate) {
        SerialReader reader;

        if(_readers == null)
            _readers = new Dictionary<string, SerialReader>();
        else if(_readers.TryGetValue(portName, out reader))
            return reader;

        reader = new SerialReader(portName, baudRate);
        _readers.Add(portName, reader);

        return reader;
    }   

    // Expose a master kill switch we can pull on application quit, to simplify.
    public static void StopAll() {
        if(_readers == null)
            return;

        foreach(var pair in _readers)
            pair.Value.Stop();
    }

    // Port configuration parameters.
    public readonly string portName;
    public readonly int baudRate;

    // Let other scripts read the current value of this reader.
    public volatile float value { get; private set; }

    // Local kill switch to stop just this one reader when we need to.
    volatile bool _continueReading = true;

    // Private constructor, so we can only call this via the request method above.
    // Starts a thread so the serial port I/O can happen in parallel.
    SerialReader(string portName, int baudRate) {
        this.portName = portName;
        this.baudRate = baudRate;
        var thread = new Thread(ReadLoop);
        thread.Start();
    }

    void ReadLoop() {
        // "Using" automatically disposes of the port once we exit this block.
        using(var port = new SerialPort(portName, baudRate) {
            port.Open();

            // Keep listening for new serial data until we're told to stop.
            while(_continueReading) {
                try {
                    string portreading = sp.ReadLine();
                    value = int.Parse(portreading);
                } catch (System.TimeoutException e) {
                    // Timeouts are expected and harmless, so we ignore these.
                }
                // Optionally, sleep before checking again.
            }

            // If we exit the loop, it's time to shut down.
            // Remove this reader from the index, and close the port.
            _readers.Remove(portName);
            port.Close();
        }
    }

    // Tell this reader we're done with it, so the read loop will exit.
    public bool Stop() {
        _continueReading = false;
    }
}

Now your script that relies on the reader might look something like this:

public class MaterialChanger : MonoBehaviour {

    public string portName = @"\\.\COM5";
    public int baudRate = 9600;

    SerialReader _reader;


    // On startup: request a reader - this will recycle one that's already
    // open if possible, instead of spinning up a new one.
    void Start() {
        _reader = SerialReader.GetOrOpen(portName, baudRate);
    }

    // Each frame, check & act on the latest value from the reader.
    // Note that this is now non-blocking: we don't stall waiting for changes.
    void Update() {        
        if (_reader.value > 50f) {
            // Do some material changes...
        } else {
            // Or do some other material changes...
        }
    }

    // When we're all done, close the connection.
    // This is safe even if multiple scripts try to close the same reader.
    void OnApplicationQuit() {
        if(_reader != null)
            _reader.Stop();
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ i edited the question to add another piece of code \$\endgroup\$
    – user19964
    Commented Aug 15, 2018 at 5:33
  • \$\begingroup\$ Note that this will potentially lose some data. If multiple inputs come in between two updates, only the last one in will be processed. That doesn’t appear to be a problem for your use case, but if it does ever end up being a problem, you’d need to implement a queuing mechanism to ensure no data is ignored. \$\endgroup\$
    – Ed Marty
    Commented Aug 16, 2018 at 12:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .