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. Dismiss Notice

Does WaitForSeconds coroutines guarantee execution order in this scenario?

Discussion in 'Scripting' started by ryanawe, Apr 19, 2016.

  1. ryanawe

    ryanawe

    Joined:
    Jul 1, 2015
    Posts:
    34
    Code (csharp):
    1. public class myclass : monobehaviour{
    2.     private void Start(){
    3.         StartCoroutine(myc(1.005f));
    4.         StartCoroutine(myc(1.007f));
    5.         StartCoroutine(myc(1.006f));
    6.     }
    7.  
    8.     private IEnumerator myc(float wait){
    9.         yield return new WaitForSeconds(wait);
    10.         Debug.Log(wait);
    11.     }
    12. }
    Question 1) Is there any guarantee that the console will always "1.005" before anything else?
    Question 2) Is there any guarantee that the console will always print in the order of "1.005", "1.006", "1.007"?

    May I kindly have a quote with the link to Unity's coroutine system's documentation that explicitly states whether or not this execution order is a guarantee?

    I'm making a turn-based multi-player game and want to know if there is any guarantee in the execution order in order to eliminate any potential de-synchronization between clients that may be caused by this.

    Thank you!
     
    Last edited: Apr 19, 2016
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,181
    I don't think there's any guarantees, no.

    WaitForSeconds(t) means "wait until the first frame after t time has passed", not "wait exactly t seconds". So in WaitForSeconds(t), t is a lower bound on the amount of time that will pass, not an exact amount. The difference between that lower bound and the time the coroutine steps next on is dependent on the framerate.

    If you try with a small change to your code:

    Code (csharp):
    1.  
    2.     private void Start() {
    3.         StartCoroutine(myc(1.00000001f, "0"));
    4.         StartCoroutine(myc(1.00000003f, "1"));
    5.         StartCoroutine(myc(1.00000002f, "2"));
    6.     }
    7.  
    8.     private IEnumerator myc(float wait, string callSite) {
    9.         yield return new WaitForSeconds(wait);
    10.         Debug.Log(callSite);
    11.     }
    12.  
    You'll see that "0", "1", "2" is printed to the console, in that order, meaning that the size of the waits are not respected.

    This means that if you want this feature, you'll have to order the invocations yourself.
     
    ericbegue, Kiwasi and Nigey like this.
  3. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    More generally, in a communication over the network, there is nothing that guarantee client-server synchronization unless you use (or implement) a protocol designed for this purpose.
     
  4. ryanawe

    ryanawe

    Joined:
    Jul 1, 2015
    Posts:
    34
    Hi Baste, do you think a proper coroutine system should have 6 or 2 printed first?
    Shouldn't there be some sort of time sorting internally?
    Code (csharp):
    1.  
    2. void Start(){
    3.     StartCoroutine(myc(6.0f,"6"));
    4.     wasteTime();
    5.     StartCoroutine(myc(2.0f,"2"));
    6. }
    7. void wasteTime(){
    8.     //assume this function takes approx. 10.0f seconds to complete
    9.     for(int i = 0; i < 10000; i++){
    10.           Debug.Log("Waste Time");
    11.     }
    12. }
    13.  
     
    Last edited: Apr 19, 2016
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,181
    Pretty sure 10k Debug.Logs will take far longer than 10 seconds :p

    Hard question, though. What you're expressing in a WaitForSeconds is "I want this to happen in x seconds". This means that you expect "2" to be printed 2 seconds after the second coroutine was started, and "6" to be printed 6 seconds after the first coroutine was started.

    Going by the rule "the first frame that starts after that much time has passed", you'd expect "6" to be printed on the next frame, and "2" to be printed 2 seconds later.

    But that assumes that the registered starting time of a WaitForSeconds is the exact current time. It's probably actually Time.time, which is the time at which the current frame started. That would mean that both "6" and "2" would be printed immediately, in that order. The order is from the last test, and I would not rely on the the coroutines being called in the order they're created - especially across scripts.

    It's also possible that coroutines have their timer start at the end of the current frame. That would make "2" be printed ~2 seconds after Start had run, and "6" be printed 4 seconds after that.
     
  6. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    If you want to have a strict execution order of long running tasks, you might be interested by Panda BT.
    For example:
    Code (CSharp):
    1. tree "root"
    2.     sequence
    3.         DebugLog("A")
    4.         DebugLog("B")
    5.         Wait(2.0)
    6.         DebugLog("C")
    7.  
    This script will print "A", "B", then wait for 2 second and finally print "C". The sequence node will execute the tasks in the order they appear (from top to bottom). It does not matter how much take time it takes for the task to accomplish, their execution order is definite.

    Edit:
    A link to the website might help :p:
    http://www.pandabehaviour.com/
     
    Last edited: Apr 19, 2016
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,181
    Follow-up; I did a test:

    Code (csharp):
    1.  
    2.     private void Start() {
    3.         Debug.Log("Start entry Time: " + Time.realtimeSinceStartup);
    4.         StartCoroutine(myc(6.0f, "6"));
    5.         wasteTime();
    6.         StartCoroutine(myc(2.0f, "2"));
    7.         Debug.Log("Start exit Time: " + Time.realtimeSinceStartup);
    8.     }
    9.  
    10.     private IEnumerator myc(float time, string text) {
    11.         yield return new WaitForSeconds(time);
    12.         Debug.Log(Time.realtimeSinceStartup + ": " + text);
    13.     }
    14.  
    15.     private void wasteTime() {
    16.         //assume this function takes approx. 10.0f seconds to complete
    17.         for (int i = 0; i < 100000; i++) {
    18.             Debug.LogWarning("Waste Time");
    19.         }
    20.     }
    The results are:
    Start entry Time: 1.721864
    Start exit Time: 126.3312 (100k Debug.Logs is very costly, as you can see)
    128.3186: 2
    132.3182: 6

    So, as you can see, a WaitForSeconds starts waiting at the end of the current frame.

    There's a lot of information here about WaitForSeconds that are not obvious, and should probably be documented. I'll make a post in the documentation subforums.

    Edit: documentation subforum thread.
     
    Last edited: Apr 19, 2016
    ryanawe likes this.
  8. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    If you're making a turn-based multiplayer game, your best bet would be to have a single coroutine act as a game loop that checks wether any action occurred / was received. That way you can always be certain with order of execution. Something like:

    Example using PhotonNetworking
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using Photon;
    4.  
    5. void SendJSON (string json)
    6. {
    7.      // sent by server player
    8.      photonView.RPC("ReceiveJSON", PhotonTarget.AllBuffered, json );
    9. }
    10.  
    11. [PunRPC]
    12. void ReceiveJSON(string json)
    13. {
    14.      JSONObject obj = new JSONObject (json);
    15.      queue.Enqueue (obj);
    16. }
    17.  
    18. public Queue<JSONObject> queue = new Queue<JSONObject>();
    19. IEnumerator GameLoop()
    20. {
    21.      while (true)
    22.      {
    23.           if (queue.Count == 0)
    24.           {
    25.                // set idle game state
    26.                gameState = GameState.IDLE;
    27.                yield return null;
    28.                continue;
    29.           }
    30.            
    31.           // set idle game state
    32.           gameState = GameState.RUNNING;
    33.           JSONObject obj = queue.Dequeue ();
    34.  
    35.           // process game logic based on action
    36.           yield return StartCoroutine (ProcessAction (obj));
    37.      }
    38. }