Is it possible to use InvokeRepeating as an accurate 5ms timer?

I need to run an external API call at 200 Hz (every 5ms). Ideally, this fast update call is render-framerate independent, but for simplicity, it would be acceptable if the rate drops for some frames.

Initially, I was looking at creating a thread with a custom update loop, but then I found out that other developers have been using InvokeRepeating with an interval of 5ms to create this kind of fast update loop in a simple manner without any custom threads.

Is InvokeRepeating precise enough to do something every 5ms? What happens if the render-framerate drops?

How could I measure the timings of an InvokeRepeating call to verify that it can hold 5ms steadily? I’ve tried to use a Stopwatch, but I don’t understand the results.

public class MyClass : MonoBehaviour
{
    private System.Diagnostics.Stopwatch watch;

    private void Start()
    {
        // Update at 200 hz / every 5 ms
        InvokeRepeating("MyFastUpdate", 0f, 0.005f);
        watch = System.Diagnostics.Stopwatch.StartNew();
    }

    private void MyFastUpdate()
    {
        watch.Stop();
        Debug.Log(watch.ElapsedMilliseconds);
        watch.Restart();
    }
}

This logged very confusing values such as a series of: 0, 0, 15, 0, 7, 32, 0, 0, 16, etc.

I do realize that the overhead of logging or using the stopwatch may exceed a frame time of 5ms, so I tried to remove the log and buffer all timings in a list instead and only log at the end of a short session, but it did not change the results.

Next, I tried a rough approximation:

public class MyClass : MonoBehaviour
{
    private int count;

    private void Start()
    {
        // Update at 200 hz / every 5 ms
        InvokeRepeating("MyFastUpdate", 0f, 0.005f);
    }

    private void MyFastUpdate()
    {
        count++;
    }

    private void LateUpdate()
    {
        // How many FastUpdate calls have been processed during this frame?
        // At 60 fps this shows roughly 3 calls per LateUpdate.
        Debug.Log(count);
        count = 0;
    }
}

This indeed returned more sensible values such as 3 FastUpdate calls per LateUpdate at roughly 60 fps. However, this is only a very rough measurement.

So, how do I verify that InvokeRepeating does in fact keep my desired update rate without skewing the measurement with additional code? As an aside, the code I’m running within the FastUpdate function is very lightweight and takes less than a millisecond to process on target hardware.

No, as far as I understand it, it runs on the main thread, which means it’ll always be 5ms + some change, depending on the exact time in which the frame is triggered. If your goal is simply to trigger a function 200 times a second, it’s effective enough in that regard provided the framerate is high enough, but you cannot actually rely on the timing precision to be exact. If the framerate is less than 200, then you’ll have some frames in which the difference is greater than 10ms and one of the calls are “skipped”. Even if the framerate is exactly 200, or over, the precise time between one frame and the next is almost never going to be precisely 5ms, some frames might not get a call at all (because .0049 from last frame), and the next would have a delay of almost 10ms since the last call was made.

If you need real precision, you’ll need to use a second thread, and any interactions from that second thread back to the first thread will possess the same delay as using it on the main thread directly (it would either need to be sync’d to the main thread or just assign a value that wouldn’t get read until the next frame’s update phase). Most cases you’ve found likely just discovered they didn’t need that level of precision to be worth the effort, or the effects they wanted to have in-game required interaction with the main thread and so didn’t benefit from the second thread being more precise.

1 Like

Technically speaking, you can’t create reliable low resolution timer in “normal” OS’s like windows or linux, you need a realtime os for this because common OS may switch context for any process thread for any time period. In unity/c# environment you may simulate such timer running separate thread and using Stopwatch class, but it will be only a simulation. Consider this:

while(currentTime < nextTickTime) {
  currentTime=stopwatch.GetTicks();
}
tick();

This example contains few dozens instructions when translated to machine code. Basically OS may stop your thread for an undefined time period in between any two of them. Because of this in tick functions you must do

int numberOfTicks = (currentTime - timeSincePreviousCall) / tickDuration;
for(int i = 0; i < numberOfTicks; i++){
    calculateSingleTick();
}

On the other hand, realtime OS provides you a sheduler wich guarantee your code to run for specific period of time or frequency.

1 Like