Search Unity

Time.deltaTime vs own delta time calculation

Discussion in 'Scripting' started by willemsenzo, Sep 8, 2020.

  1. willemsenzo

    willemsenzo

    Joined:
    Nov 15, 2012
    Posts:
    585
    Sorry if I'm posting this in the wrong section but feel free to move it somewhere else. I walked into something that I'm confused about. I have a day/night cycle and it uses (or rather used) a timer based on Time.deltaTime. I noticed that on higher framerates the clock goes faster than on lower. When I calculate deltatime myself it solves that issue completely. Given this issue I can't understand why all tutorials that deal with time insist on using Time.deltaTime. I thought Time.deltaTime was suitable for creating framerate independent code or am I wrong? Here an example you can try yourself. I've tried this in the editor and also in a build, and the build runs on a higher frame rate than the editor and made it obvious to me that Time.deltaTime is problematic.

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class TimeTest : MonoBehaviour
    5. {
    6.     private DateTime tp1;
    7.     private DateTime tp2;
    8.     private float deltaTime = 0;
    9.     private float timer1 = 0;
    10.     private float timer2 = 0;
    11.  
    12.     void Start()
    13.     {
    14.         tp1 = DateTime.Now;
    15.         tp2 = DateTime.Now;
    16.     }
    17.  
    18.     void Update()
    19.     {
    20.         CalculateDeltaTime();
    21.  
    22.         timer1 += Time.deltaTime; //Timer goes slower on low frame rate, quicker on high frame rate
    23.         timer2 += deltaTime; //Timer runs like you'd expect from a clock no matter the frame rate
    24.     }
    25.  
    26.     void OnGUI()
    27.     {
    28.         GUILayout.Label(timer1.ToString());
    29.         GUILayout.Label(timer2.ToString());
    30.     }
    31.  
    32.     void CalculateDeltaTime()
    33.     {
    34.         tp2 = DateTime.Now;
    35.         deltaTime = (float) ( (tp2.Ticks - tp1.Ticks) / 10000000.0 );
    36.         tp1 = tp2;      
    37.     }
    38. }
     
    razzraziel likes this.
  2. jamespaterson

    jamespaterson

    Joined:
    Jun 19, 2018
    Posts:
    399
    Hi have you considered just using time.time or time.unscaledtime (sorry on mobile so i can't capitalise properly).
     
  3. Neonlyte

    Neonlyte

    Joined:
    Oct 17, 2013
    Posts:
    516
    I can concur that on some platforms the deltaTime is not as stable as using a System.Diagnostics.Stopwatch.

    I did a three-way test on Android with Cumulative deltaTime, C# Stopwatch class and audio playtime from a third party middleware. The audio playtime and stopwatch has an almost constant difference (> 1ms), but deltaTime cumulative wobbles in relation to stopwatch. While the wobble is not very big, about 2ms, it's not as reliable as the other two since my project has heavy reliance on short timing window.

    I did notice that there has been other discussions on the forum, and sometimes the release notes would mention improvements in that category. If you could replicate your issue consistently, it's better to report this to Unity as a bug report. I will report my findings, too, once I have time.
     
  4. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    It's supposed to be. If you've found an issue with it then it's a bug and should be reported.

    Note that your CalculateDeltaTime() is not equivalent to Time.deltaTime. Yours will calculate the time delta between the current and previous call within that instance, where Time.deltaTime gives the time delta between the start of the current tick and the start of the previous tick. Things like changes in execution order, or other code taking different amounts of time to run, would interfere with the results from your implementation. (All completely fixable, of course, and not likely to impact the result of your test here.)
     
    JoNax97 and neginfinity like this.
  5. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I'd love to see your results, though. How much faster or slower are you finding it to be?
     
  6. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Deltatime is really for interpolating visuals over short time periods. Which doesn't really require all that much precision. In theory it doesn't even need to represent the entire timespan, but some consistent fraction of it. Because you almost always use it with a multiplier of some type to get the movement speed appropriate for what you are showing.

    Even if DT was high precision your own code would still skew it. You have a chain of logic and in a real game the parts are going to take varying amounts of time in different frames, skewing the accuracy for any specific piece of code. And for what DT is designed for that's mostly fine.
     
    angrypenguin likes this.
  7. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Ok, I did a few runs, just in the editor. :)

    First run - provided code

    I immediately noticed a huge difference between the printed clocks. Pretty sure that this is because the first frame time from Unity includes initialisation time, which a timer that starts in Update() can't pick up. So I ignored the results and modified the code.

    Pretty scrappy, but it shouldn't matter. It now waits 3 seconds before starting the timers, and also shows the delta and frame count.

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using UnityEngine;
    5.  
    6. public class Timer : MonoBehaviour
    7. {
    8.     private DateTime tp1;
    9.     private DateTime tp2;
    10.     private float deltaTime = 0;
    11.     private float timer1 = 0;
    12.     private float timer2 = 0;
    13.     private int frames = 0;
    14.  
    15.     bool started = false;
    16.  
    17.     IEnumerator Start()
    18.     {
    19.         yield return new WaitForSeconds(3);
    20.         tp1 = DateTime.Now;
    21.         tp2 = DateTime.Now;
    22.  
    23.         started = true;
    24.     }
    25.  
    26.     void Update()
    27.     {
    28.         if (!started) return;
    29.  
    30.         CalculateDeltaTime();
    31.  
    32.         timer1 += Time.deltaTime; //Timer goes slower on low frame rate, quicker on high frame rate
    33.         timer2 += deltaTime; //Timer runs like you'd expect from a clock no matter the frame rate
    34.         frames++;
    35.     }
    36.  
    37.     void OnGUI()
    38.     {
    39.         GUILayout.Label("Unity:\t" + timer1.ToString());
    40.         GUILayout.Label("Custom:\t" + timer2.ToString());
    41.  
    42.         GUILayout.Label("Diff:\t" + (timer1 - timer2).ToString());
    43.  
    44.         GUILayout.Label("Frames:\t" + frames);
    45.     }
    46.  
    47.     void CalculateDeltaTime()
    48.     {
    49.         tp2 = DateTime.Now;
    50.         deltaTime = (float)((tp2.Ticks - tp1.Ticks) / 10000000.0);
    51.         tp1 = tp2;
    52.     }
    53. }

    Second runs - full speed (average 576.6 fps)
    upload_2020-9-8_19-47-32.png upload_2020-9-8_19-51-19.png
    After ~2 minutes we have about a ~0.02 second discrepancy. I repeated it and got pretty darn close results.

    Third run - "busy" speed (average 139.1 fps)
    Here I added some junk string manipulation code to my Update() method just to slow things down, slashing the frame rate.
    upload_2020-9-8_19-51-45.png
    After ~2 minutes we have a ~0.1 second discrepancy.

    Conclusion
    This is pretty unscientific testing. Only one PC, not many runs, Editor rather than a build, so take it all with a grain of salt. But...

    The worst case here is that a game running at nearly 600fps would have to run for around 100 minutes to get a full second out of whack, assuming you're using numbers accumulated all the way from the start. That's honestly not something that concerns me. As it is, if I need times to be accurate I do my best not to rely on accumulated Time.deltaTime values anyway, because repeated float operations do accumulate error.

    And that's my guess as to the cause here. The timers with the larger errors also had ~53,000 more float operations performed on them. I do find it interesting that the error is out of proportion to the number of operations, though. (The "diff" does start at a slightly negative number, but not enough to account for this.) I wonder if that's an artifact of the different sizes of the numbers being operated on?
     
  8. CityGen3D

    CityGen3D

    Joined:
    Nov 23, 2012
    Posts:
    681
    Hi,

    Not sure if completely relevant, but I thought I'd mention that I'm aware of some deltaTime fixes going into Unity, so issues maybe dependant on your Unity version.
    Worth checking out the last few pages of this thread in case any of it's useful info.
     
  9. willemsenzo

    willemsenzo

    Joined:
    Nov 15, 2012
    Posts:
    585
    I wish I could tell you I have a recorded test but I don't, I might make a new build that can show the results of both implementations at the same time. For now I can give some more details about my project and how big the difference is.

    I'm running a server that keeps track of time (with the same method using DateTime I described before) and when a client connects it sends the current time to the user. From this point the user will update time itself and there's a clock on screen that shows current time. I noticed a significant difference in the speed of how time passes while being simultaneously connected from within the editor game window, and a build that I made. The editor has much more overhead, and runs on about half the frame rate the build does. Within seconds I could see time in the build pass quicker than in the editor and it became completely out of sync. While making the window of the build smaller the frame rate of the build increases and the time difference became even bigger. I'm talking about many seconds of difference and this change happened also within matter of seconds, you can immediately see this happening. I don't think there is a need to send regular time updates to players if their clock runs at about the same rate as anyone else regardless of their frame rate. With the DateTime method I completely get what I expect and I can't see a difference anymore between the clock of the build and the one running in the editor, even after resizing the window to mess with the frame rate, the clocks stay synchronized.

    And for what it's worth I'm running Unity 2019.3 on Linux Mint 18.3.
     
    Last edited: Sep 8, 2020
    zylo01 likes this.
  10. jackmememe

    jackmememe

    Joined:
    Jan 21, 2016
    Posts:
    138
    Can't you just keep
    at the start and subtract by DateTime.Now on GUI?
     
  11. willemsenzo

    willemsenzo

    Joined:
    Nov 15, 2012
    Posts:
    585
    Technically yes but that would require to rewrite my already written code that works fine.
     
  12. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,154
    Speaking of which is there a reason we're storing the added results in a single-precision number? Here are my results from simply changing the type of the variables storing the value. Without this change the difference was approximately twice what you saw but then I'm running at twice your performance too.

    Code (csharp):
    1. private double deltaTime = 0;
    2. private double timer1 = 0;
    3. private double timer2 = 0;
    4. private int frames = 0;
    upload_2020-9-8_14-0-20.png
     
    angrypenguin likes this.
  13. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Last edited: Sep 8, 2020
  14. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Besides the results you've got, and no matter whether the tests were correct or not:

    Never use Time.time or Time.deltaTime for an attempt to keep track of "real-time", it's only for in-game time. Game engines usually have mechanisms in place that allow to recover from critical performance impacts so that the in-game experiences continues in a meaningful manner afterwards. And so does Unity.

    In practice, a single serious drop in FPS that results in surpassing the max. allowed frame time will definitely get you out of sync with a clock that's based on "real-time". And that's desired for various reasons.

    Unity also exposes realtime properties, perhaps these may help you. Also, for anything that potentially needs to run much longer than a few hours (since you mention server-client architectures) you certainly want to do what @Ryiah suggests if you need to accumulate time values.
     
    zylo01 and angrypenguin like this.
  15. willemsenzo

    willemsenzo

    Joined:
    Nov 15, 2012
    Posts:
    585
    @Suddoha I appreciate you trying to be part of the discussion, but you're not bringing anything new to the table that I haven't figured out yet. I'm aware of floating point imprecision and I'm not incrementing the clock into infinity, it resets back to 0 after 24 minutes which is the equivalent of a day in game and I haven't found any problem with that. I'm not asking for any suggestions however. I've found a reliable way to get the result I'm after and I've observed it to be accurate enough for my needs, although if you feel like making a suggestion then at least provide more info about those realtime properties you are talking about.

    Now to get back to the point, the reason I started this thread was to get an answer to why the built in timing mechanisms aren't suitable to run a more or less accurate clock/timer while none of the materials I find about the subject talk about this. At this point I tend to believe most people probably don't know about the problem and it might be the reason why it took so long for Unity to acknowledge it and work on improving the accuracy of the deltatime calculation. To be honest I never noticed it myself untill I actually did and it made me question my own sanity. But yeah, maybe deltatime indeed wasn't meant to be used for this type of stuff and people who make learningmaterials shouldn't give the wrong example by using it as such.
     
  16. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,154
    While it's definitely possible that most people aren't aware of the limitations of single precision math and Time.deltaTime, I believe it's just as likely most people never come across Time.deltaTime being used in this way. I know whenever I search for anything time related I almost always get DateTime thanks to how many non-Unity C# tutorials exist.

    I haven't taken the time to go through the most recent official tutorials but the ones I went through in the past have only used Time.deltaTime for estimating how much of an activity should take place during a single frame. They didn't discuss the limitations of Time.deltaTime but then that's likely to confuse a beginner more than anything.
     
    Last edited: Sep 9, 2020
    angrypenguin likes this.
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    My curiosity was purely about how much error the OP was talking about. If precision mattered my code wouldn't have looked like that at all.
     
    Ryiah likes this.
  18. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,154
    Same here. I didn't mention it but the difference between the two numbers in my example code was basically stable and not changing in a noticeable way between frames, seconds, or even when comparing the starting and ending values.
     
  19. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    As you can see that's radically different to the results I got. Did you get those results with the code you shared just above?

    It was going to take me well over an hour to reach a one second difference in sync. And even with that deliberately naive approach, as @Ryiah has demonstrated it's trivially easy to fix even that level of drift

    They are accurate enough for the latter for the vast majority of use cases in games. And if you need to run accurate timers over long periods of time then there's a whole bunch of stuff outside the scope of typical learner tutorials that you're going to need to also understand. So I can see why they keep it simple instead.

    If this kind of stuff is important to you I recommend having a read of this: Don't Store That In A Float by Bruce Dawson.