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
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Ways to make things refresh themselves without using too many resources

Discussion in 'Scripting' started by DroidifyDevs, Jul 23, 2016.

  1. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Hi!

    So everyone knows about Update, FixedUpdate and LateUpdate. However, using these can be expensive during gameplay. So what I was thinking was instead of using Update, why not run a script 2 times a second or so? For this I tried using an IEnumerator and StartCoroutine. However, I don't know how expensive a coroutine is, so I'm afraid this might be as expensive or more expensive than an Update.

    In other words, if you needed to run a script a few times a second, what would you do to try to keep it from using too much CPU?

    Thanks for any ideas you might have
     
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Use the profiler. Also, InvokeRepeating exists.

    --Eric
     
    DroidifyDevs likes this.
  3. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Oh crap I totally forgot about the profiler. I'll look up InvokeRepeating.

    Thanks
     
  4. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    Coroutines would be best.. you have a lot of control over sequences and timing. Coroutines are like functions that you can pause for a duration or until a certain condition is met.

    So for something to run 2 times a second, you have that logic inside a while loop, and you also have a half-second yield inside the same while loop. When your coroutine "yields", the rest of your program can still run, so you won't be "stuck" inside that while loop unless you forgot the yield. It will require some research.
     
    DroidifyDevs likes this.
  5. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    So what's better, a coroutine or InvokeRepeating? I already have used coroutines before
     
  6. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    It's probably a personal preference thing, but I would be more comfortable using a coroutine for such a thing. Coroutines can do quite a bit more and will give you more control, but InvokeRepeating probably does everything you need here.
     
    DroidifyDevs likes this.
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    According to Eric5h5's benchmarks, InvokeRepeating is faster than a coroutine. However, I don't know if the coroutine's loop allocated a new WaitForSeconds() each frame. It's more efficient to pre-allocate the WaitForSeconds, and this might make up the difference. I haven't benchmarked it.

    As others mentioned, the nice thing about coroutines is that you can vary the refresh rate -- for example based on distance from the player. For example:

    Code (csharp):
    1. private WaitForSeconds refreshRate = new WaitForSeconds(1);
    2. private GameObject player;
    3.  
    4. void Start() {
    5.     player = GameObject.FindObjectWithTag("Player");
    6.     InvokeRepeating("UpdateRefreshRate", 5, 5);
    7.     StartCoroutine(Refresh());
    8. }
    9.  
    10. void UpdateRefreshRate() {
    11.     // Every 5 seconds, recompute the refresh rate based on distance from player:
    12.     refreshRate = new WaitForSeconds(Vector3.Distance(this.transform.position, player.transform.position));
    13. }
    14.  
    15. IEnumerator Refresh() {
    16.     while (true) {
    17.         // (your code here)
    18.         yield return refreshRate;
    19.     }
    20. }
    Technically Start can be a coroutine, but I kept it separate in this example so you can stop and restart Refresh.
     
  8. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Very interesting topic I ran into, I guess I'll keep using coroutines for the most part, but InvokeRepeating if performance is lacking.
     
  9. Oribow

    Oribow

    Joined:
    Nov 9, 2012
    Posts:
    38
    That reminds me a bit of "soft threading". That means, that execution and managemend is handled by your own threads. You might want to considere developing some sort of scheduler, because if you have 10 soft threads, that run at the same frequencie, they all clash on one frame, causing a stutter in a fixed interval. Even if the frequencies aren't the same, they could still clash at some point. For example: freq = 2 & freq = 6 would clash every 6 frame.
    To get you started with, here is a example for an schedulere I once copied from a book. Every "task" has to manage there time budget and saving the state for another execution by them self. When adding, the schedulere looks a fixed amount of frames ahead and tries to find the one, with the least task running on. Thats a kinda naive approach, but it works for the most part.

    Code (CSharp):
    1. public class AIScheduler : MonoBehaviour
    2.     {
    3.         struct BehaviorRecord
    4.         {
    5.             public AITask task;
    6.             public int frequenz;
    7.             public int phase;
    8.             public BehaviorRecord(AITask task, int frequenz, int phase)
    9.             {
    10.                 this.task = task;
    11.                 this.frequenz = frequenz;
    12.                 this.phase = phase;
    13.             }
    14.         }
    15.  
    16.         public long timePerFrameMS;
    17.         List<BehaviorRecord> tasks;
    18.  
    19.         void Awake()
    20.         {
    21.             tasks = new List<BehaviorRecord>();
    22.         }
    23.  
    24.         public void AddTask(AITask task, int frequenz)
    25.         {
    26.  
    27.             int bestPhase = 0;
    28.             if (frequenz > 1)
    29.             {
    30.                 int minimum = int.MaxValue;
    31.                 int currentTaskInFrame;
    32.                 for (int i = 0; i < 50; i++)
    33.                 {
    34.                     currentTaskInFrame = 0;
    35.                     foreach (BehaviorRecord r in tasks)
    36.                     {
    37.                         if (r.frequenz % (i + r.phase) == 0)
    38.                             currentTaskInFrame++;
    39.                     }
    40.  
    41.                     if (currentTaskInFrame < minimum)
    42.                     {
    43.                         minimum = currentTaskInFrame;
    44.                         bestPhase = i;
    45.                     }
    46.                     if (currentTaskInFrame == 0)
    47.                         break;
    48.                 }
    49.             }
    50.             tasks.Add(new BehaviorRecord(task, frequenz, bestPhase));
    51.         }
    52.  
    53.         void Update()
    54.         {
    55.             List<AITask> runThese = new List<AITask>(10);
    56.             foreach (BehaviorRecord r in tasks)
    57.             {
    58.                 if ((Time.frameCount + r.phase) % r.frequenz == 0)
    59.                     runThese.Add(r.task);
    60.             }
    61.  
    62.             int runTheseCount = runThese.Count;
    63.             long timeToRun;
    64.             Stopwatch stopwatch = Stopwatch.StartNew();
    65.             for (int i = 0; i < runTheseCount; i++)
    66.             {
    67.                 timeToRun = (timePerFrameMS - stopwatch.ElapsedMilliseconds) / (runTheseCount - i);
    68.                 runThese[i].Run(timeToRun);
    69.             }
    70.         }
    71.  
    72.     }
    73.  
    74.     public interface AITask
    75.     {
    76.         void Run(long timeToRun);
    77.     }
     
    DroidifyDevs likes this.
  10. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    Here's another quick fix to stagger updates. I had originally typed this into my code example above, but I deleted it to keep the code simple:

    Code (csharp):
    1. IEnumerator Refresh() {
    2.     yield return new WaitForSeconds(Random.value); //<--ADD THIS.
    3.     while (true) {
    4.         // (your code here)
    5.         yield return refreshRate;
    6.     }
    7. }
    It's not as sophisticated as @Oribow's scheduler, but in practice it works well and it's only one extra line of code.
     
    DroidifyDevs likes this.