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

Can't reuse WaitForSecondsRealtime

Discussion in 'Scripting' started by fabiopicchi, Jul 8, 2018.

  1. fabiopicchi

    fabiopicchi

    Joined:
    Jan 22, 2016
    Posts:
    22
    I have the habit of caching WaitForSeconds instances to avoid unnecessary Garbage Collection calls as this is an easy optimization to do. The pattern is as follows:
    Code (CSharp):
    1. WaitForSeconds Wait;
    2.  
    3. // ...
    4.  
    5. IEnumerator DummyCoroutine() {
    6.     yield return Wait;
    7. }
    More info on why this is done can be seen in this thread.

    However, if I try to follow the same pattern using WaitForSecondsRealtime, it works only the first time it is returned. It is as if WaitForSecondsRealtime is retaining state and can't be reused.

    I don't know if caching these waits is a good practice or not, nevertheless, that is clearly an inconsistent behaviour and I think it might be a bug. Has anyone come across this issue? I am using Unity 2017.4.6f1
     
    Last edited: Jul 8, 2018
    Chambers88 likes this.
  2. Exuro

    Exuro

    Joined:
    Dec 11, 2015
    Posts:
    8
    I don't know why you are having this issue, but I can confirm that this practice is actually in Unity's official guide for optimising your code to reduce garbage collection.

    You can find it here in the section on Coroutines:

    Capture.PNG
     
  3. fabiopicchi

    fabiopicchi

    Joined:
    Jan 22, 2016
    Posts:
    22
    Nice, I didn't know this pattern was encouraged by Unity developers. So, my issue happens exclusively with WaitForSecondsRealtime. I opened the thread to know whether this is a bug or a mistake of mine. If someone else can confirm this behaviour for me I will gladly open a bug report
     
  4. Exuro

    Exuro

    Joined:
    Dec 11, 2015
    Posts:
    8
    I was able to reproduce it in 2018.2, so it does seem to be an ongoing bug. I used the following code:

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class Waitforseconds : MonoBehaviour {
    5.  
    6.     WaitForSecondsRealtime _wait;
    7.  
    8.     private void Start()
    9.     {
    10.         _wait = new WaitForSecondsRealtime(3f);
    11.         StartCoroutine(CoroutineWait());
    12.     }
    13.  
    14.     private IEnumerator CoroutineWait()
    15.     {
    16.         while (true)
    17.         {
    18.             Debug.Log("TICK");
    19.             yield return _wait;
    20.         }
    21.     }
    22. }
    23.  
    The behaviour I saw was that it would tick once, wait three seconds, then just start logging every tick after that. Is this consistent with the behaviour you were seeing?
     
    fabiopicchi likes this.
  5. fabiopicchi

    fabiopicchi

    Joined:
    Jan 22, 2016
    Posts:
    22
    Exactly! Thank you :D

    Just sent a bug report
     
    Last edited: Jul 8, 2018
  6. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    7,845
    Hi. This is because WaitForSeconds and WaitForSecondsRealtime work differently.
    WaitForSeconds works internally in our C++ code where the wait is handled by the engine and the class is purely for data, however, WaitForSecondsRealtime is just a C# class that handles the waiting itself.

    This is all it is:
    Code (CSharp):
    1. public class WaitForSecondsRealtime : CustomYieldInstruction
    2. {
    3.     float waitTime;
    4.  
    5.     public override bool keepWaiting
    6.     {
    7.         get { return Time.realtimeSinceStartup < waitTime; }
    8.     }
    9.  
    10.     public WaitForSecondsRealtime(float time)
    11.     {
    12.         waitTime = Time.realtimeSinceStartup + time;
    13.     }
    14. }
    So as you can see the waitTime value makes it only usable once.
    I have the bug report that was filed and will make sure that we either expose the waitTime value or provide a Reset function so it can be reused.

    edit: waitTime can now be modified https://docs.unity3d.com/ScriptReference/WaitForSecondsRealtime-waitTime.html
     
    Last edited: Apr 23, 2019
  7. TobyKaos

    TobyKaos

    Joined:
    Mar 4, 2015
    Posts:
    212
    Not fixed in Unity 2017.x . We have not access to waitTime property.

    Edit: Implement my Own custom yield instruction thanks to your sample.
     
    Kurt-Dekker likes this.
  8. fishbai

    fishbai

    Joined:
    May 15, 2018
    Posts:
    2
    In Unity 2018.3.12f1 I am seeing odd behavior when trying to reuse a WaitForSecondsRealtime(), when used in conjunction with StopCoroutine.

    I have a pause menu, with a countdown that resumes the game after X seconds. I have an option to override the countdown to unpause the game immediately. When I use the override option, then pause the game again, and resume again, the countdown for the first number is definitely not 1 realtime second.

    I tried using the waitTime property mentioned above, to reset the coroutine after stopping the coroutine, but that did not help. The only thing that works is generating a new WaitForSecondsRealtime() instead of reusing the existing.

    Is this a bug, or intentional on Unity's part?
     
  9. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    7,845
    Its a bug. WaitForSecondsRealtime caches the wait time and only resets it after it has finished. We need to implement the Reset behaviour.
    Ill fix it. - https://issuetracker.unity3d.com/product/unity/issues/guid/1189949/

    Code (csharp):
    1. public class WaitForSecondsRealtime : CustomYieldInstruction
    2.     {
    3.         public float waitTime { get; set; }
    4.         float m_WaitUntilTime = -1; // THIS IS THE PROBLEM
    5.  
    6.         public override bool keepWaiting
    7.         {
    8.             get
    9.             {
    10.                 if (m_WaitUntilTime < 0)
    11.                 {
    12.                     m_WaitUntilTime = Time.realtimeSinceStartup + waitTime;
    13.                 }
    14.  
    15.                 bool wait =  Time.realtimeSinceStartup < m_WaitUntilTime;
    16.                 if (!wait)
    17.                 {
    18.                     // Reset so it can be reused.
    19.                     m_WaitUntilTime = -1;
    20.                 }
    21.                 return wait;
    22.             }
    23.         }
    24.  
    25.         public WaitForSecondsRealtime(float time)
    26.         {
    27.             waitTime = time;
    28.         }
    29.     }
     
    Last edited: Oct 8, 2019
    fishbai likes this.
  10. Tomi-Tukiainen

    Tomi-Tukiainen

    Joined:
    Jun 8, 2013
    Posts:
    26
    Say you have a centralized pool of different WaitForSecondsRealtime instances and you tend to use 1 second wait here and there in your project. If one coroutine starts a wait of 1 second at t=0 and second coroutine starts a wait of 1 second at t=0.5sec, with simple pooling they both would be using the same WaitForSecondsRealtime instance.

    The problem then would be that the wait of the second coroutine would terminate only after 0.5sec, which is the time when the first coroutine is expected to terminate. So if this kind of WaitForSecondsRealtime instances are to be reused, the user needs to keep in track which ones are currently in use and only allow reusing them after they have returned back to state where m_WaitUntilTime==-1.