Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

PlayerLoop run in FixedUpdate

Discussion in 'Experimental Scripting Previews' started by Baste, Aug 29, 2018.

  1. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Not quite sure where I should ask this, figured that since the feature is in UnityEngine.Experimental, it kinda fits here?

    I'm trying to make a custom player loop system run in fixed time - ie as often as FixedUpdate, with Time.deltaTime being the same as fixedDeltaTIme and so on. I assumed that the way to do that would be to set the .loopConditionFunction to be the same as FixedUpdate's .loopConditionFunction.

    That doesn't seem to work, though! Even though I copy the .loopConditionFunction to a new system, that new system still runs as often as Update, and returns false for Time.inFixedTimeStep.

    The test setup is this:

    Code (csharp):
    1.  
    2. public class TestScript : MonoBehaviour {
    3.  
    4.     private class TestFixed { }
    5.     private class TestNotFixed { }
    6.  
    7.     private void Start() {
    8.         PlayerLoopInterface.InsertSystemAfter(typeof(TestFixed),    CustomFixedUpdate, typeof(FixedUpdate), true);
    9.         PlayerLoopInterface.InsertSystemAfter(typeof(TestNotFixed), CustomUpdate,      typeof(FixedUpdate), false);
    10.     }
    11.  
    12.     private void Update() {
    13.         Debug.Log("Update: " + Time.deltaTime + "/" + Time.fixedDeltaTime + "/" + Time.inFixedTimeStep);
    14.     }
    15.  
    16.     private void CustomUpdate() {
    17.         Debug.Log("Custom Update: " + Time.deltaTime + "/" + Time.fixedDeltaTime + "/" + Time.inFixedTimeStep);
    18.     }
    19.  
    20.     private void FixedUpdate() {
    21.         Debug.Log("FixedUpdate: " + Time.deltaTime + "/" + Time.fixedDeltaTime + "/" + Time.inFixedTimeStep);
    22.     }
    23.  
    24.     private void CustomFixedUpdate() {
    25.         Debug.Log("Custom fixed update: " + Time.deltaTime + "/" + Time.fixedDeltaTime + "/" + Time.inFixedTimeStep);
    26.     }
    27. }

    Only the FixedUpdate will run in fixed time, CustomFixedUpdate runs with Update and CustomUpdate.

    Where PlayerLoopInterface.InsertSystemAfter is:

    Code (csharp):
    1.  
    2.     public static void InsertSystemAfter(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertAfter, bool runInFixedUpdate = false) {
    3.         var playerLoopSystem = new PlayerLoopSystem {type = newSystemMarker, updateDelegate = newSystemUpdate};
    4.         if (runInFixedUpdate)
    5.             playerLoopSystem.loopConditionFunction = fixedUpdateLoopCondition; //this is copied off Experimental.PlayerLoop.FixedUpdate
    6.  
    7.         InsertSystemAfter(playerLoopSystem, insertAfter); // this one just recursively walks the current player loop system, inserts the new system after insertAfter, and sets the player loop system.
    8.     }

    I've checked, and the IntPtr of FixedUpdate.loopConditionFunction is unique - every other function's loopConditionFunction has a value of 0. The docs for loopConditionFunction also says that:

    Which is what I'm doing.

    Is this supposed to work? I assume so, since the .loopConditionFunction is exposed. Am I missing something, or is the system just not running the loop condition yet?

    Here's all of PlayerLoopSystem, if it's helpful:

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Experimental.LowLevel;
    6. using UnityEngine.Experimental.PlayerLoop;
    7.  
    8. public static class PlayerLoopInterface {
    9.  
    10.     private static bool hasFetchedSystem;
    11.     private static IntPtr fixedUpdateLoopCondition;
    12.     internal static PlayerLoopSystem system;
    13.  
    14.     [RuntimeInitializeOnLoadMethod]
    15.     private static void Initialize() {
    16.         EnsureSystemFetched();
    17.     }
    18.  
    19.     private static void EnsureSystemFetched() {
    20.         if (!hasFetchedSystem) {
    21.             system = PlayerLoop.GetDefaultPlayerLoop();
    22.             hasFetchedSystem = true;
    23.  
    24.             if (!TryFetchFixedUpdatePointer(system))
    25.                 Debug.LogError("Couldn't find FixedUpdate in the built-in player loop systems! This means that setting runInFixedUpdate to true in " +
    26.                                "InsertSystemBefore or After won't work!");
    27.         }
    28.     }
    29.  
    30.     private enum InsertType {
    31.         Before,
    32.         After
    33.     }
    34.  
    35.     /// <summary>
    36.     /// Inserts a new player loop system in the player loop, just after another system.
    37.     /// </summary>
    38.     /// <param name="newSystemMarker">Type marker for the new system.</param>
    39.     /// <param name="newSystemUpdate">Callback that will be called each frame after insertAfter.</param>
    40.     /// <param name="insertAfter">The subsystem to insert the system after.</param>
    41.     /// <param name="runInFixedUpdate">Set the loop condition function to the same as the built-in FixedUpdate.</param>
    42.     public static void InsertSystemAfter(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertAfter, bool runInFixedUpdate = false) {
    43.         var playerLoopSystem = new PlayerLoopSystem {type = newSystemMarker, updateDelegate = newSystemUpdate};
    44.         if (runInFixedUpdate)
    45.             playerLoopSystem.loopConditionFunction = fixedUpdateLoopCondition;
    46.  
    47.         InsertSystemAfter(playerLoopSystem, insertAfter);
    48.     }
    49.  
    50.     /// <summary>
    51.     /// Inserts a new player loop system in the player loop, just before another system.
    52.     /// </summary>
    53.     /// <param name="newSystemMarker">Type marker for the new system.</param>
    54.     /// <param name="newSystemUpdate">Callback that will be called each frame before insertBefore.</param>
    55.     /// <param name="insertBefore">The subsystem to insert the system before.</param>
    56.     /// <param name="runInFixedUpdate">Set the loop condition function to the same as the built-in FixedUpdate.</param>
    57.     public static void InsertSystemBefore(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertBefore, bool runInFixedUpdate = false) {
    58.         var playerLoopSystem = new PlayerLoopSystem {type = newSystemMarker, updateDelegate = newSystemUpdate};
    59.         if (runInFixedUpdate)
    60.             playerLoopSystem.loopConditionFunction = fixedUpdateLoopCondition;
    61.         InsertSystemBefore(playerLoopSystem, insertBefore);
    62.     }
    63.  
    64.     /// <summary>
    65.     /// Inserts a new player loop system in the player loop, just after another system.
    66.     /// </summary>
    67.     /// <param name="toInsert">System to insert. Needs to have updateDelegate and Type set.</param>
    68.     /// <param name="insertAfter">The subsystem to insert the system after</param>
    69.     public static void InsertSystemAfter(PlayerLoopSystem toInsert, Type insertAfter) {
    70.         if (toInsert.type == null)
    71.             throw new ArgumentException("The inserted player loop system must have a marker type!", nameof(toInsert.type));
    72.         if (toInsert.updateDelegate == null)
    73.             throw new ArgumentException("The inserted player loop system must have an update delegate!", nameof(toInsert.updateDelegate));
    74.         if (insertAfter == null)
    75.             throw new ArgumentNullException(nameof(insertAfter));
    76.  
    77.         EnsureSystemFetched();
    78.  
    79.         InsertSystem(ref system, toInsert, insertAfter, InsertType.After, out var couldInsert);
    80.         if (!couldInsert) {
    81.             throw new ArgumentException($"The type to insert after, {insertAfter.Name}, could not be found in the current player loop!", nameof(insertAfter));
    82.         }
    83.  
    84.         PlayerLoop.SetPlayerLoop(system);
    85.     }
    86.  
    87.     /// <summary>
    88.     /// Inserts a new player loop system in the player loop, just before another system.
    89.     /// </summary>
    90.     /// <param name="toInsert">System to insert. Needs to have updateDelegate and Type set.</param>
    91.     /// <param name="insertBefore">The subsystem to insert the system before</param>
    92.     public static void InsertSystemBefore(PlayerLoopSystem toInsert, Type insertBefore) {
    93.         if (toInsert.type == null)
    94.             throw new ArgumentException("The inserted player loop system must have a marker type!", nameof(toInsert.type));
    95.         if (toInsert.updateDelegate == null)
    96.             throw new ArgumentException("The inserted player loop system must have an update delegate!", nameof(toInsert.updateDelegate));
    97.         if (insertBefore == null)
    98.             throw new ArgumentNullException(nameof(insertBefore));
    99.  
    100.         EnsureSystemFetched();
    101.  
    102.         InsertSystem(ref system, toInsert, insertBefore, InsertType.Before, out var couldInsert);
    103.         if (!couldInsert) {
    104.             throw new ArgumentException($"The type to insert before, {insertBefore.Name}, could not be found in the current player loop!", nameof(insertBefore));
    105.         }
    106.  
    107.         PlayerLoop.SetPlayerLoop(system);
    108.     }
    109.  
    110.     private static void InsertSystem(ref PlayerLoopSystem currentLoopRecursive, PlayerLoopSystem toInsert, Type insertTarget, InsertType insertType,
    111.                                      out bool couldInsert) {
    112.         var currentSubSystems = currentLoopRecursive.subSystemList;
    113.         if (currentSubSystems == null) {
    114.             couldInsert = false;
    115.             return;
    116.         }
    117.  
    118.         int indexOfTarget = -1;
    119.         for (int i = 0; i < currentSubSystems.Length; i++) {
    120.             if (currentSubSystems[i].type == insertTarget) {
    121.                 indexOfTarget = i;
    122.                 break;
    123.             }
    124.         }
    125.  
    126.         if (indexOfTarget != -1) {
    127.             var newSubSystems = new PlayerLoopSystem[currentSubSystems.Length + 1];
    128.  
    129.             var insertIndex = insertType == InsertType.Before ? indexOfTarget : indexOfTarget + 1;
    130.  
    131.             for (int i = 0; i < newSubSystems.Length; i++) {
    132.                 if (i < insertIndex)
    133.                     newSubSystems[i] = currentSubSystems[i];
    134.                 else if (i == insertIndex) {
    135.                     newSubSystems[i] = toInsert;
    136.                 }
    137.                 else {
    138.                     newSubSystems[i] = currentSubSystems[i - 1];
    139.                 }
    140.             }
    141.  
    142.             couldInsert = true;
    143.             currentLoopRecursive.subSystemList = newSubSystems;
    144.         }
    145.         else {
    146.             for (var i = 0; i < currentSubSystems.Length; i++) {
    147.                 var subSystem = currentSubSystems[i];
    148.                 InsertSystem(ref subSystem, toInsert, insertTarget, insertType, out var couldInsertInInner);
    149.                 if (couldInsertInInner) {
    150.                     currentSubSystems[i] = subSystem;
    151.                     couldInsert = true;
    152.                     return;
    153.                 }
    154.             }
    155.  
    156.             couldInsert = false;
    157.         }
    158.     }
    159.  
    160.     private static bool TryFetchFixedUpdatePointer(PlayerLoopSystem loopSystem) {
    161.         if (loopSystem.type == typeof(FixedUpdate)) {
    162.             fixedUpdateLoopCondition = loopSystem.loopConditionFunction;
    163.             return true;
    164.         }
    165.  
    166.         var subSystems = loopSystem.subSystemList;
    167.         if (subSystems != null) {
    168.             foreach (var subSystem in subSystems) {
    169.                 if (TryFetchFixedUpdatePointer(subSystem))
    170.                     return true;
    171.             }
    172.         }
    173.  
    174.         return false;
    175.     }
    176.  
    177.     // Methods for listing sub system data. Kept around as this stuff is experimental, so being able to quickly check the info is convenient.
    178.  
    179.     // The loop functions are somewhat weird - a bunch of functions have the same one (like FixedUpdate and Update and LateUpdate), so I assume that dispatch
    180.     // to the correct update loop happens inside of that one.
    181.     private static void FindAllNativeLoopFunctions() {
    182.         Dictionary<IntPtr, List<Type>> loopFunctionToSystems = new Dictionary<IntPtr, List<Type>>();
    183.         IntPtr GrabNativeLoops(PlayerLoopSystem pls) => pls.updateFunction;
    184.  
    185.         FindNativeStuff(system, loopFunctionToSystems, GrabNativeLoops);
    186.  
    187.         string text = "Loop functions\n";
    188.         foreach (var (loopFunc, systemList) in loopFunctionToSystems) {
    189.             text += ("  function " + loopFunc + " used by:\n    " + systemList.PrettyPrint(true, t => "    " + t.Name) + "\n");
    190.         }
    191.         Debug.Log(text);
    192.     }
    193.  
    194.     // As of 2018.1.6f1, every single default system has the value 0 for their loop condition, except for FixedUpdate.
    195.     private static void FindAllNativeLoopConditions() {
    196.         Dictionary<IntPtr, List<Type>> conditionFunctionToSystems = new Dictionary<IntPtr, List<Type>>();
    197.         IntPtr GrabNativeConditions(PlayerLoopSystem pls) => pls.loopConditionFunction;
    198.  
    199.         FindNativeStuff(system, conditionFunctionToSystems, GrabNativeConditions);
    200.  
    201.         string text = "Condition functions\n";
    202.         foreach (var (loopFunc, systemList) in conditionFunctionToSystems) {
    203.             text += ("  function " + loopFunc + " used by:\n    " + systemList.PrettyPrint(true, t => "    " + t.Name) + "\n");
    204.         }
    205.         Debug.Log(text);
    206.     }
    207.  
    208.     private static void FindNativeStuff(PlayerLoopSystem playerLoopSystem, Dictionary<IntPtr, List<Type>> ptrToSystems, Func<PlayerLoopSystem, IntPtr> grabPtr) {
    209.         var ptr = grabPtr(playerLoopSystem);
    210.  
    211.         ptrToSystems.GetOrAdd(ptr).Add(playerLoopSystem.type);
    212.  
    213.         if(playerLoopSystem.subSystemList != null) {
    214.             foreach (var subSystem in playerLoopSystem.subSystemList) {
    215.                 FindNativeStuff(subSystem, ptrToSystems, grabPtr);
    216.             }
    217.         }
    218.     }
    219. }
     
    unity_IpxdANggCs1roQ and NotaNaN like this.
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Hey so somebody liked this post, and I actually know the information now!

    The loopConditionFunction is as far as I can tell a red herring, and it doesn't do anything. What you want to do is to just add the system as a child system of the built-in fixed update system,
    UnityEngine.PlayerLoop.FixedUpdate


    I've got a repository that makes things easier, here. Using that, you can simply do this:

    Code (csharp):
    1. public static class MyFixedTimestepSystem {
    2.     [RuntimeInitializeOnLoadMethod]
    3.     public static void BootSystem() {
    4.         PlayerLoopInterface.InsertSystemBefore(typeof(MyFixedTimestepSystem), MyFixedTimestepMethod, typeof(UnityEngine.PlayerLoop.FixedUpdate.ScriptRunBehaviourFixedUpdate));
    5.     }
    6.  
    7.     private static void MyFixedTimestepMethod() {
    8.         Debug.Log($"Is in Fixed Timestep: {Time.inFixedTimeStep}, Fixed time: {Time.fixedTime}");
    9.     }
    10. }
     
  3. Wriggler

    Wriggler

    Joined:
    Jun 7, 2013
    Posts:
    133
    Just wanted to chime in here and say that this is really cool and helpful. Thanks for posting this!

    Ben
     
    Baste likes this.