Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Question How to guarantee method execution order between Game Objects?

Discussion in 'Scripting' started by roberto_sc, May 16, 2024.

  1. roberto_sc

    roberto_sc

    Joined:
    Dec 13, 2010
    Posts:
    149
    According to the documentation:
    "The order in which Unity calls each GameObject's Awake is not deterministic. Because of this, you should not rely on Awake being called on one GameObject before or after another."

    I can confirm that the order changes for me when hitting play in the editor vs reloading the scene via
    SceneManager.LoadSceneAsync().

    Is there a way to guarantee the sequence of some method call in different game objects?

    For example, I have:
    private void Awake() { EntityManager.Instance.Register(this) }​
    Register will add the object to a list, I want this list to always have the same order.

    I need to replace Awake by a method that will be called on each game object by some class that will guarantee the calls order. That relies on being able to order game objects somehow.

    I can't order then by name because I may have objects with the same name. I can't order by Instance ID because that changes on every run.

    I thought I could get the root objects and go through each child so that would be the order in the hierarchy. However I don't know if Scene.GetRootGameObjects() is guaranteed to return roots in the same order. Probably not but does it matter? What are the different root objects when there's only 1 scene loaded?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,390
    Across different types of components, there is the Script Execution Order settings. Across the same component type, I don't believe so.

    To achieve this you usually need to introduce your own system that can guarantee order. How you do so really depends on the particular of your project.

    If I had a number of game objects in a scene I wanted to be registered to some top-level entity, I would probably either:
    • Add my own data point that they can be sorted by
    • Make a 'group' component that can serialise references to all the necessary components across child game objects, with some simple editor scripts to automate this. Which then registers them in the order they are referenced (or sorted by).
     
    Bunny83 and Kurt-Dekker like this.
  3. roberto_sc

    roberto_sc

    Joined:
    Dec 13, 2010
    Posts:
    149
    I like your idea of having a script that will reference game objects during edit time so it's always the same order when I run, thank you. But I wish there was a way to do it automatically during runtime.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,594
    EDIT - to explain wtf I'm going on about here
    You're going to have to define the order some way. May it be by inserting it into that list in its appropriate spot or sorting by some int value that is configured. You could do this at editor time or at runtime though it could get convoluted.

    Here I'll go into a way to accomplish this on a per GameObject process with an 'order' script. OnEnable this will insert itself into the global list at runtime.

    If you want to configure it note that I didn't write specific support for modifying 'Order' while the script is enabled and active. You'll have to disable it... modify the order... then re-enable it.
    /END EDIT

    Place something like this onto a GameObject to define that GameObjects order.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.LowLevel;
    3. using System.Collections.Generic;
    4.  
    5. public class SynchronizedUpdater : MonoBehaviour
    6. {
    7.  
    8.     #region Static Interface
    9.  
    10.     static bool _initialized = false;
    11.     static void TryStartCustomPlayerLoop()
    12.     {
    13.         if (_initialized) return;
    14.  
    15.         _initialized = true;
    16.         var loop = PlayerLoop.GetCurrentPlayerLoop();
    17.         var arr = new PlayerLoopSystem[loop.subSystemList.Length + 1];
    18.         System.Array.Copy(loop.subSystemList, arr, loop.subSystemList.Length);
    19.         arr[arr.Length - 1] = new PlayerLoopSystem()
    20.         {
    21.             type = typeof(SynchronizedUpdater),
    22.             updateDelegate = PoolUpdateCallback,
    23.         };
    24.         loop.subSystemList = arr;
    25.         PlayerLoop.SetPlayerLoop(loop);
    26.     }
    27.  
    28.     static readonly List<SynchronizedUpdater> Pool = new();
    29.     static void Register(SynchronizedUpdater obj)
    30.     {
    31.         //NOTE - this is a really naive insertion approach, should probably go with a BinaryHeap or something if you have an implementation laying around.
    32.         for (int i = 0; i < Pool.Count; i++)
    33.         {
    34.             var o = Pool[i];
    35.             if (o == obj) return; //collection already contains the obj
    36.             if (o.Order > obj.Order)
    37.             {
    38.                 Pool.Insert(i, obj);
    39.                 return;
    40.             }
    41.         }
    42.  
    43.         Pool.Add(obj);
    44.     }
    45.  
    46.     static void Unregister(SynchronizedUpdater obj)
    47.     {
    48.         Pool.Remove(obj);
    49.     }
    50.  
    51.     static void PoolUpdateCallback()
    52.     {
    53.         //NOTE - I have not written any code pertaining to ensuring the 'Pool' doesn't change during SynchronizeUpdate.
    54.         //It's a bad idea to disable a GameObject during SynchronizedUpdate else this will throw an exception unless you write logic to handle that scenario
    55.         foreach (var obj in Pool)
    56.         {
    57.             foreach (var c in obj._components)
    58.             {
    59.                 if (c.isActiveAndEnabled) c.SynchronizedUpdate();
    60.             }
    61.         }
    62.     }
    63.  
    64.     #endregion
    65.  
    66.     #region Fields
    67.  
    68.     [SerializeField]
    69.     private int _order;
    70.  
    71.     [SerializeField]
    72.     private bool _manageChildComponents;
    73.  
    74.     [System.NonSerialized]
    75.     private IUpdateable[] _components;
    76.  
    77.     #endregion
    78.  
    79.     #region CONSTRUCTOR
    80.  
    81.     private void OnEnable()
    82.     {
    83.         _components = _manageChildComponents ? this.GetComponentsInChildren<IUpdateable>(true) : this.GetComponents<IUpdateable>();
    84.         System.Array.Sort(_components, (a, b) => a.SubOrder.CompareTo(b.SubOrder));
    85.  
    86.         Register(this);
    87.         TryStartCustomPlayerLoop();
    88.     }
    89.  
    90.     private void OnDisable()
    91.     {
    92.         Unregister(this);
    93.     }
    94.  
    95.     #endregion
    96.  
    97.     #region Properties
    98.  
    99.     public int Order
    100.     {
    101.         get => _order;
    102.         set
    103.         {
    104.             //if you want to support changing order while enabled & active, you'll need to sort the 'Pool' after doing this
    105.             if (this.isActiveAndEnabled) throw new System.InvalidOperationException("Modifying order while active is not supported.");
    106.             _order = value;
    107.         }
    108.     }
    109.  
    110.     #endregion
    111.  
    112.     #region Special Types
    113.  
    114.     public interface IUpdateable
    115.     {
    116.         bool isActiveAndEnabled { get; } //as long as you inherit from MonoBehaviour this will automatically be implemented
    117.         int SubOrder { get => 0; } //the order of this script relative to the gameobject, default implementation in case you don't need it for that script
    118.         void SynchronizedUpdate();
    119.     }
    120.  
    121.     #endregion
    122.  
    123. }
    Example scripts:
    Code (csharp):
    1.     public class zTest01 : MonoBehaviour, SynchronizedUpdater.IUpdateable
    2.     {
    3.  
    4.         void SynchronizedUpdater.IUpdateable.SynchronizedUpdate()
    5.         {
    6.             //DO STUFF
    7.         }
    8.  
    9.     }
    10.  
    11.     public class zTest02 : MonoBehaviour, SynchronizedUpdater.IUpdateable
    12.     {
    13.  
    14.         [SerializeField]
    15.         private int _suborder;
    16.  
    17.         int SynchronizedUpdater.IUpdateable.SubOrder => _suborder;
    18.  
    19.         void SynchronizedUpdater.IUpdateable.SynchronizedUpdate()
    20.         {
    21.             //DO STUFF
    22.         }
    23.  
    24.     }
    Note how I only define SubOrder for 1 of them. You may not require a 'SubOrder' for every script.

    SubOrder in this scenario is the order of that script relative to other synchronized updateables on/in that gameobject.

    ...

    NOTE - this is mostly untested slop code I slapped together real fast here in the forums just now. No guarantees it works perfectly and you may want to tweak to your needs. I left various comments through out highlighting certain aspects.
     
    Last edited: May 16, 2024
    roberto_sc, Ryiah and Bunny83 like this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,357
    Do you really care about all of their order?

    Or are there just a couple you want FIRST in the list, then you do any of the rest later.

    I find in practice there's often just a few criticals you gotta have in first, then the rest don't matter, so it may be worth making a "early" and a "normal" list of delegates / registered objects just to keep it simple.

    That way you don't really need to keep thinking order for all future items, just "early or normal."

    You can also use a chain of dependencies build with singletons like this to ensure criticals are loaded in the order they must be:

    (A -> requests B -> B -> Requests C, etc. in a cascading explicit order of reference)

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance
     
    CodeSmile likes this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,594
    I should say.

    I do agree... does this order actually matter?

    I've been using Unity for a looooooooong time and have never needed to do anything like this really.
     
    Kurt-Dekker, CodeSmile, Ryiah and 2 others like this.
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,189
    Right. In most cases this is an ad-hoc demand as it may be a convenient solution for some other design flaw.
     
    Kurt-Dekker, Ryiah and spiney199 like this.
  8. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,922
    Exactly. Same here.

    Only cases were quick fixes using Script Execution Order, mainly when either the codebase was already spaghettized and it just needed to work, or when a third party script or some Unity API was involved and it was important that our code ran before or after that under all circumstances.

    Really rare, perhaps once every other year.
     
    Kurt-Dekker, Ryiah and Bunny83 like this.
  9. roberto_sc

    roberto_sc

    Joined:
    Dec 13, 2010
    Posts:
    149
    Hello everyone, thank you all for your answers and sorry for the late reply.

    I'm not trying to guarantee the order so objects are instantiated in a way that dependencies are created first :)

    I should have explained better: I don't care about the order at all, I just want the order to be the same on every run, on every device.
    That's because I'm trying to achieve determinism.

    I control the game loop call on each game object that's being simulated, independently of Unity's Update loop. For this, these game objects are registered to a manager, and most of the time the instantiation & subsequent registration are done through factories during the simulation so determinism is kept.
    However, I do have a lot of game objects already in the scene, and these are currently being registered in their Awake methods. I could manually add them in edit time to the manager but I'd like a way to do it automatically.
     
  10. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,681
    Your best bet is to just take full control of all the magic methods. Your control script can have
    Awake
    ,
    Start
    , etc while everything else has
    OnAwake
    ,
    OnStart
    , etc that is called by the control script. I'm normally not a fan of this method but
    FindObjectsByType
    with LINQ may make sense here.

    Code (csharp):
    1. var behaviorsToRegister = FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None)
    2.                              .OfType<IRegister>()
    3.                              .OrderBy(obj => obj.ID)  // or whatever you use for order of execution
    4.                              .ToArray();
     
    Last edited: May 21, 2024
    Bunny83 likes this.
  11. roberto_sc

    roberto_sc

    Joined:
    Dec 13, 2010
    Posts:
    149
    That's exactly my original question, what to use for order execution? :) I can't rely on GameObject names or Instance IDs, is there anything I can rely on? I don't want to setup ids manually.
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,390
    You are probably going to have to introduce your own data point to sort them by, honestly. But you can use editor scripts to automate their generation/assignment.
     
    Bunny83 likes this.
  13. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,922
    In that case I would go for the in-editor registration of the in-scene placed objects. It doesn't take more than listen for the appropriate gameobject change events to register/unregister these objects. Having these already registered will also speed up scene loading a bit.
     
  14. roberto_sc

    roberto_sc

    Joined:
    Dec 13, 2010
    Posts:
    149
    Great tip about the object change events, thank you!

    Thank you all for your answers, people.
     
    CodeSmile likes this.
  15. kader1081

    kader1081

    Joined:
    Oct 16, 2021
    Posts:
    400
    Just call it from other gameobject for example lets say you have 2 object and 2 scrippt attach to it object a will have reference to object b's script and on awake you call it just like function

    objectBscript myScript


    awake
    {
    myscript.CallTheObjectBFunction();
    }

    you can arrange the which function will be called first

    Or you can make an entire other script which will act like an manager
    you can call every function you want to call in awake in that script