probitaille
probitaille

Reputation: 2007

How to precisely sample data with frequency of 60Hz?

Actually, I use the InvokeRepeating method to invoke another method every 1/x seconds. The problem is that the precision of the delay between the invoke and the data I got is not good.

How I can precisely sample transform.position with a frequency of 60Hz.

Here's my code:

public class Recorder : MonoBehaviour {

public float samplingRate = 60f; // sample rate in Hz
public string outputFilePath;

private StreamWriter _sw;

private List<Data> dataList = new List<Data>();

public void OnEnable()
{

    InvokeRepeating("SampleNow", 0, 1 / samplingRate);
}

public void OnDisable()
{
    _sw = System.IO.File.AppendText(outputFilePath);

    for (int k=0; k< dataList.Count; k++)
    {
        _sw.WriteLine("t {0} x {1} y {2} z {3}",
           dataList[k].time, dataList[k].x, dataList[k].y, dataList[k].z);
    }

    _sw.Close();
    CancelInvoke();
}

public void SampleNow()
{
    dataList.Add(new Data(Time.time, transform.position.x, transform.position.y, transform.position.z));
}

public class Data
{
    public float time;
    public float x;
    public float y;
    public float z;

    public Data(float time, float x, float y, float z)
    {
        this.time = time;
        this.x = x;
        this.y = y;
        this.z = z;
    }
  }
}

So, with this, I can get this kind of result:

t 0 x 0 y 0 z 0
t 0.02 x 0.283776 y -0.76 z 0
t 0.04 x 0.599808 y -0.52 z 0
t 0.06 x 0.599808 y -0.52 z 0
t 0.08 x 0.599808 y -0.52 z 0
t 0.09999999 x 0.599808 y -0.52 z 0
t 0.12 x 0.599808 y -0.52 z 0
t 0.14 x 0.599808 y -0.52 z 0
t 0.16 x 0.599808 y -0.52 z 0
t 0.18 x 0.599808 y -0.52 z 0
t 0.2 x 0.599808 y -0.52 z 0
t 0.22 x 0.599808 y -0.52 z 0
t 0.24 x 0.599808 y -0.52 z 0
t 0.26 x 0.599808 y -0.52 z 0
t 0.28 x 0.599808 y -0.52 z 0
t 0.3 x 0.599808 y -0.52 z 0
t 0.32 x 0.599808 y -0.52 z 0
t 0.3338465 x 0.599808 y -0.52 z 0
t 0.3338465 x 0.2918357 y -0.7538424 z 0
t 0.34 x 0.2918357 y -0.7538424 z 0
t 0.3539519 x 0.2918357 y -0.7538424 z 0
t 0.3539519 x 0.6092016 y -0.5125771 z 0
t 0.3705226 x 0.6092016 y -0.5125771 z 0
t 0.3870888 x 0.8340279 y -0.3137283 z 0
t 0.4036556 x 0.9750986 y -0.114934 z 0
t 0.42 x 0.9865224 y 0.09031145 z 0
...

As you can see in this result, I can collect duplicate position for different time (t=0.04 to t=0.32) and worst, I can get duplicate time (t=0.3539519) with different positions (giving an infinite acceleration).

In fact, the gameobject analysed moved with a linear fonction in time, doing a circle in the axis X-Y over 20sec.

So, this code doesn't give me a good precision for a scientific analyze.

What can I do to get more precision with Unity3D?

Upvotes: 1

Views: 1103

Answers (1)

Programmer
Programmer

Reputation: 125435

This is complicated. Although, I wouldn't say that it is impossbile to do in Unity. It can be done precisely but not with InvokeRepeating.

You still have two other ways to do this.

1.Coroutine

If InvokeRepeating is too slow, maybe that's because it uses reflection to call the function or maybe the implementation is not perfect.

You may be able to accomplish this by doing a direct function call with a coroutine and waiting with WaitForSecondsRealtime instead of WaitForSeconds. WaitForSecondsRealtime is new in Unity and does not depend on the frame-rate to wait. WaitForSeconds does.

public float samplingRate = 60f; // sample rate in Hz

void OnEnable()
{
    StartCoroutine(startSampling());
}

IEnumerator startSampling()
{
    WaitForSecondsRealtime waitTime = new WaitForSecondsRealtime(1f / samplingRate);
    while (true)
    {
        SampleNow();
        yield return waitTime;
    }
}

public void SampleNow()
{
    Debug.Log("Called");
    dataList.Add(new Data(Time.time, transform.position.x, transform.position.y, transform.position.z));
}

2.Thread (Recommended)

This, I recommended you should use as you will totally avoid any framerate slow down by Unity's main Thread. Do the timer operation in another Thread.

The problem with this is that you won't be able to use transform.position.x or the Transform class in another Thread. Unity prevents you from doing so. You will have to execute dataList.Add(new Data(Time.time, transform.position.x... in the Main Thread. Another option is to store transform.position in a global Vector3 variable then acceess it from another Thread.

You cannot also use Time.time in another Thread and will have to get it in the Main Thread, then store it in a local variable that will be used in a Thread function

You will also have to use the lock keyword to make it Thread-safe. Note that using the lock keyword will slow down your Game a little bit. It will remove at-least 2 or 3 frames from your game, but it is required and worth the benefit and protection it provides.

The code below will do everything I just said. The sampling rate is set to 2 times per-sec for testing purposes. You can change chage that to your 60Hz in the samplingRate variable, when you think it is working propery.

private List<Data> dataList = new List<Data>();

Thread samplingThread;
const float samplingRate = 2f; // sample rate in Hz

Vector3 posInThread;
float TimetimeInThread;

private System.Object threadLocker = new System.Object();


void OnEnable()
{
    startSampling();
}

void startSampling()
{
    samplingThread = new Thread(OnSamplingData);
    samplingThread.Start();
}

void Update()
{
    lock (threadLocker)
    {
        //Update this values to be used in another Thread
        posInThread = transform.position;
        TimetimeInThread = Time.time;
    }
}

void OnSamplingData()
{
    long oldTime = DateTime.Now.Ticks;
    long currentTime = oldTime;
    const float waitTime = 1f / samplingRate;
    float counter;

    while (true)
    {
        currentTime = DateTime.Now.Ticks;
        counter = (float)TimeSpan.FromTicks(currentTime - oldTime).TotalSeconds;

        if (counter >= waitTime)
        {
            //Debug.Log("counter: " + counter + "  waitTime: " + waitTime);
            oldTime = currentTime; //Store current Time
            SampleNow();
        }
        Thread.Sleep(0); //Make sure Unity does not freeze
    }
}

public void SampleNow()
{
    Debug.Log("Called");
    lock (threadLocker)
    {
        dataList.Add(new Data(TimetimeInThread, posInThread.x, posInThread.y, posInThread.z));
    }
}

void OnDisable()
{
    if (samplingThread != null && samplingThread.IsAlive)
    {
        samplingThread.Abort();
    }
    samplingThread.Join(); //Wait for Thread to finish then exit
}

Upvotes: 2

Related Questions