Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Bug Inconsistent FixedUpdate deltaTime

Discussion in 'Scripting' started by unity_5w20XO9ra_gm3g, Apr 11, 2023.

  1. unity_5w20XO9ra_gm3g

    unity_5w20XO9ra_gm3g

    Joined:
    Jul 4, 2018
    Posts:
    8
    Im using unity 2022.1.23f1.
    Trying to make authority server using dedicated server build + client
    I need consistent fixed update to keep client sync with server.
    But sometimes i get some strange deltas in fixed update that produce inconsistent sync:
    for 60 ticks per second i get something like that:
    16+-, 16, 16, 16, 16......... 0-5, 16, 16, 16
    short delta updates happen once per 100-200 updates.
    Reproducable in editor, standalone + dedicated server builds.

    Typical outpu of code below:

    Sim delta: 0.0127 | 0.01665417 | 103
    Sim delta: 0.0137 | 0.01665317 | 209
    Sim delta: 0.014 | 0.01665287 | 281
    Sim delta: 0.0115 | 0.01665527 | 211
    Sim delta: 0.0146 | 0.01665237 | 182


    Simple code that shows problem:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Diagnostics;
    4. using UnityEngine;
    5. using Debug = UnityEngine.Debug;
    6.  
    7. public class TimerTest : MonoBehaviour
    8. {
    9.     private Stopwatch _timer = new Stopwatch();
    10.     private float _treshold = 0.5f;
    11.     private float _simRate = 60;
    12.     private int _counter;
    13.     private void Awake()
    14.     {
    15.         Application.targetFrameRate = (int)_simRate;
    16.         Time.fixedDeltaTime = 1 / _simRate;
    17.     }
    18.  
    19.     void FixedUpdate()
    20.     {
    21.         _counter++;
    22.         var delta = Mathf.Abs((float)_timer.Elapsed.TotalSeconds - Time.fixedDeltaTime);
    23.  
    24.         if (delta > Time.fixedDeltaTime * _treshold)
    25.         {
    26.             Debug.Log($"Sim delta: {_timer.Elapsed.TotalMilliseconds} | {delta} | {_counter}");
    27.             _counter = 0;
    28.         }
    29.  
    30.         _timer.Restart();
    31.     }
    32. }
    33.  
     

    Attached Files:

    Last edited: Apr 11, 2023
  2. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,051
    FixedUpdate is not actually executed at a fixed interval, it just simulates the physics with a fixed time step. If you look at the event functions diagram in the documentation, you see that it's executed at a fixed point during the player loop. It can also be not executed at all or executed multiple times in a loop, depending on difference between game and fixed update time. So I guess what you're seeing is that Unity sometimes skips the FixedUpdate during a player loop or executes multiple of them right after each other to catch up.
     
    angrypenguin, Bunny83 and Ryiah like this.
  3. unity_5w20XO9ra_gm3g

    unity_5w20XO9ra_gm3g

    Joined:
    Jul 4, 2018
    Posts:
    8
    Yes, i know all of that.
    But why it should to catch up if its empty scene no physics etc. It should be stable.
    For me it looks like sim is ticking a bit slower than should, and gets error accumulated somewhere.
    So if i set 60 ticks per second, it actually ticks 59, and at some moment adds one more tick right after sim.
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,571
    Right, FixedUpdate and fixedTime simply "catch up" to "time" in a loop just before Update. That's why you may get zero, one or multiple calls to FixedUpdate in a row. So at a high framerate "time" is increasing in smaller steps so FixedUpdate doesn't run every frame. For example at a framerate of say 100 fps we only get a FixedUpdate call about every second frame. However at a low framerate, say 25fps, we actually get 2 FixedUpdate calls every frame. Those two calls are executed in a loop right after each other.

    A long time ago I made this simulation that tries to visually showcase what Unity actually does. Note the red and yellow vertical lines which indicate Time.time and Time.fixedTime respectively.

    You can think of how FixedUpdate is actually called like this. This is just pseudo code how Unity works internally

    Code (CSharp):
    1. void Frame()
    2. {
    3.     while(Time.fixedTime < Time.time)
    4.     {
    5.         Time.deltaTime = Time.fixedDeltaTime;
    6.         FixedUpdate();
    7.         InternalPhysicsUpdate();
    8.         Time.fixedTime += Time.deltaTime;
    9.     }
    10.     Time.deltaTime = ActualDeltaTime;
    11.     Update();
    12.     LateUpdate();
    13.     Rendering();
    14. }
    What you should note is:
    • FixedUpdate may be called several times within the same frame in order to "catch up" with the game time.
    • Inside FixedUpdate, Time.deltaTime actually returns the value of fixedDeltaTime and that's actually a constant value by which the virtual "fixedTime" is increased each FixedUpdate.
    • Therefore FixedUpdate is not executing at a fix real-time interval but just statisticly checks out at 50 calls per second (when fixedDeltaTime is set to the default 0.02ms => 1/0.02 == 50).
     
  5. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,051
    I think it's the other way round: The pyhsics update is fixed and stable by definition. But it tracks the game time / frame rate and that won't be stable. Even in an empty scene, the frame time can fluctuate (and maybe Unity is more prone to that than other engines) and this can force the fixed update to have to slow down / speed up.

    There's no guarantee for the accuracy you try to rely on, Unity doesn't guarantee perfectly stable game and physics updates, even under best conditions. You need to design your networking to be resilient to this kind of inaccuracies and maybe implement your own fixed update time step, to control exactly how it is scheduled (from Unity 2020.3+ you can do this with Physics.autoSimulation and Physics.Simulate).
     
  6. unity_5w20XO9ra_gm3g

    unity_5w20XO9ra_gm3g

    Joined:
    Jul 4, 2018
    Posts:
    8
    Physics.Simulate isn't bound to fixed update, it schedules fixedupdates, not generates, and problem persists, i tried that before.
    Only way i want to try next - is to use PlayerLoop api, and remove auto update\fixed update, not just Physics
     
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,620
    To be clear here for other devs reading this, FixedUpate is part of the runtime playerloop. Systems such as the scripts, physics(2d), animation (etc) are hooked into it. Physics doesn't drive FixedUpdate. When auto-simulate is on (now called simulation-mode = FixedUpdate) then that callback calls Physics.Simulate (or Physics2D.Simulate) else it does nothing.

    In the same way, Physics (and other systems) hook into "Update" so they can perform interpolation if it's required.
     
    Bunny83 likes this.
  8. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,051
    Yeah, sorry if that wasn't clear. What I meant is that you implement FixedUpdate yourself (a bit like in @Bunny83's pseudocode above) and then, if you need physics simulation as well, you can using
    Physics.Simulate
    in your custom fixed update. Then you can make different trade offs and e.g. ensure fixed update is run an exact average number over a given time interval.

    I don't think the PlayerLoop API is going to change anything compared to regular
    Update
    in your case, you'll always work with the resolution of Unity's game loop and will have to work around irregularities in how long frames take. If you need more precise ticks, the only option is to use a separate thread (but ofc you then can no longer use most of Unity's API).
     
  9. unity_5w20XO9ra_gm3g

    unity_5w20XO9ra_gm3g

    Joined:
    Jul 4, 2018
    Posts:
    8
    Solution i came up with for dedicated server: PlayerLoop + calling FixedUpdate from code.
    If Update Lags - it also makes simulation to lag, but no double fixed updates anymore. Update shouldn't lag in dedicated build, so it will work well for server.

    On client if fps restricted with 60fps(mobile), it still have same bug, but at least server have no bug.

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Diagnostics;
    5. using System.Runtime.InteropServices;
    6. using UnityEngine;
    7. using UnityEngine.LowLevel;
    8. using Debug = UnityEngine.Debug;
    9.  
    10. public class TimerTest : MonoBehaviour
    11. {
    12.     private Stopwatch _timer = new Stopwatch();
    13.     private float _treshold = 0.5f;
    14.     private float _simRate = 60;
    15.     private int _counter;
    16.     private PlayerLoopSystem _fixedUpdateSystem;
    17.     private Stopwatch _updateGenTimer = new Stopwatch();
    18.     private double _accumulator = 0;
    19.     private void Awake()
    20.     {
    21.         Physics.autoSimulation = false;
    22.         Application.targetFrameRate = (int)_simRate * 5;
    23.         Time.fixedDeltaTime = 1 / _simRate;
    24.         var currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
    25.  
    26.         var list = new List<PlayerLoopSystem>(currentPlayerLoop.subSystemList);
    27.         var index = list.FindIndex(el => el.type.Name.Contains("FixedUpdate"));
    28.         _fixedUpdateSystem = list[index];
    29.         list.RemoveAt(index);
    30.         currentPlayerLoop.subSystemList = list.ToArray();
    31.         currentPlayerLoop.subSystemList[index - 1].updateDelegate += FixedUpdateChecker;
    32.         PlayerLoop.SetPlayerLoop(currentPlayerLoop);
    33.         _updateGenTimer.Restart();
    34.     }
    35.  
    36.     delegate void TestCallbackDelegate();
    37.  
    38.     private void CallUpdate(IntPtr ptr)
    39.     {
    40.         unsafe
    41.         {
    42.             var nativePtr = new IntPtr(*(Int64*)ptr.ToPointer());
    43.             var callback = Marshal.GetDelegateForFunctionPointer<TestCallbackDelegate>( nativePtr );
    44.             callback();
    45.         }
    46.     }
    47.  
    48.     private void RunUpdates()
    49.     {
    50.         foreach (var system in _fixedUpdateSystem.subSystemList)
    51.         {
    52.             CallUpdate(system.updateFunction);
    53.         }
    54.     }
    55.  
    56.     private void FixedUpdateChecker()
    57.     {
    58.         Debug.Log("FixedUpdateChecker:" + Time.frameCount);
    59.         _accumulator += _updateGenTimer.Elapsed.TotalSeconds;
    60.         _updateGenTimer.Restart();
    61.         if (_accumulator > Time.fixedDeltaTime )
    62.         {
    63.             //update
    64.             _accumulator -= Time.fixedDeltaTime;
    65.             RunUpdates();
    66.         }
    67.     }
    68.  
    69.     void FixedUpdate()
    70.     {
    71.         _counter++;
    72.         var delta = Mathf.Abs((float)_timer.Elapsed.TotalSeconds - Time.fixedDeltaTime);
    73.  
    74.         if (delta > Time.fixedDeltaTime * _treshold)
    75.         {
    76.             Debug.Log($"Sim delta: {_timer.Elapsed.TotalMilliseconds} | {delta} | {_counter}");
    77.             _counter = 0;
    78.         }
    79.  
    80.         _timer.Restart();
    81.     }
    82. }
    83.  
     
    Last edited: Apr 13, 2023
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,571
    That's not a FixedUpdate in the sense it's defined. If the load of your server increases so it can not keep up the 60 updates per second, your "_accumulator" would start increasing and when the load peak goes down again, it would fire several FixedUpdates in a row to "catch up". The difference to how Unity handles it is that Unity will check every frame if it needs to catch up and calls as many FixedUpdate as necessary to reach the set "CPS" (calls per second).

    So I'm not sure this is what you actually have in mind because if you have some load that drops the performance below 60 fps you still get slowdowns and speed ups in your FixedUpdate. If you just wanted to "limit" the call rate to 60 you should do:

    Code (CSharp):
    1.     private void FixedUpdateChecker()
    2.     {
    3.         Debug.Log("FixedUpdateChecker:" + Time.frameCount);
    4.         _accumulator += _updateGenTimer.Elapsed.TotalSeconds;
    5.         _updateGenTimer.Restart();
    6.         if (_accumulator > Time.fixedDeltaTime )
    7.         {
    8.             while (_accumulator > Time.fixedDeltaTime )
    9.                 _accumulator -= Time.fixedDeltaTime;
    10.             RunUpdates();
    11.         }
    12.     }
    The "while" loop would get rid of any accumulated complete frames and just keeps the fractional remainder. So you still get a slow down when the performance drops, but you would not get a speed up when the performance is back to normal.