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

Optimization, Multiple Methods

Discussion in 'Scripting' started by Loff, Jun 1, 2019.

  1. Loff

    Loff

    Joined:
    Dec 3, 2012
    Posts:
    81
    Hello,

    I have a panel. Whenever it's open it should refresh constantly if possible, if notI will have to redo it a bit - making it so it only refreshes every X second for example. Even then it would be a lag spike every X second.
    I don't want these methods to lag out the game. They are not super heavy or anything, my computer runs it without issues - but I don't know about older onces. And I see this as a way to discover optimization, and learning a little more about it.
    Curious what the "best" solution would be.



    Method code
    The code isn't heavy etc, it's just me wanting to learn about optimization. I see a few things I can fix already to make the methods simpler. I want the UI to update often for the player, but It doesn't have to be instantly honestly.
    Code (CSharp):
    1.  
    2.     void UpdateOverviewDisplay()
    3.     {
    4.         AllTasksText.text = "All Tasks: " + supportData.totalNumberOfCases;
    5.         SolvedTasksText.text = "Solved Tasks: " + supportData.totalSolvedTasks;
    6.         FailedTasksText.text = "All Failed Tasks " + supportData.totalFailedTasks;
    7.         OpenTasksText.text = "Open Tasks: " + supportData.OpenTasks.Count;
    8.         WeeklySolvedText.text = "Weekly Solved Tasks: " + supportData.weeklySolvedTasks;
    9.         WeeklyFailedText.text = "Weekly Failed Tasks: " + supportData.weeklyFailedTasks;
    10.     }
    11.     void CheckTasksStatus()
    12.     {
    13.         List<Task> failedTasks = new List<Task>();
    14.         foreach (var task in supportData.OpenTasks)
    15.         {
    16.             if (task.DeadlineFailedCheck())
    17.             {
    18.                 failedTasks.Add(task);
    19.             }
    20.         }
    21.  
    22.         foreach (var task in failedTasks)
    23.         {
    24.             supportData.OpenTasks.Remove(task);
    25.         }
    26.     }
    27.     void UpdateTaskDisplay()
    28.     {
    29.         if (supportData.newestTasks.Count > 0)
    30.         {
    31.             for (int i = 0; i < supportData.newestTasks.Count; i++)
    32.             {
    33.                 solvedTasksText[i].text = supportData.newestTasks[i].FullDescription();
    34.             }
    35.         }
    36.  
    37.         HelpFunctions.RemoveChildObjectsFromParent(FailedTasksContent);
    38.         if (supportData.lastFailedTasks.Count > 0) {
    39.             for (int i = 0; i < supportData.lastFailedTasks.Count; i++)
    40.             {
    41.                 GameObject failedGO = Instantiate(FailedTaskObjectPrefab, FailedTasksContent);
    42.                 FailedTaskObjectScript failedScript = failedGO.GetComponent<FailedTaskObjectScript>();
    43.                 failedScript.UpdateObject(supportData.lastFailedTasks[i]);
    44.             }
    45.         }
    46.     }
    47.  
    48.     void UpdateCircleGraph()
    49.     {
    50.         float solved = supportData.weeklySolvedTasks;
    51.         float failed = supportData.weeklyFailedTasks;
    52.         float open = supportData.OpenTasks.Count;
    53.         float total = solved + failed + open;
    54.  
    55.         if(solved > 0) {
    56.             CircleGraph_Green.fillAmount = solved / total;
    57.             CircleGraph_SolvedText.text = ((solved / total) * 100).ToString("0.0") + "%";
    58.         }
    59.         else
    60.         {
    61.             CircleGraph_Green.fillAmount = 0;
    62.             CircleGraph_SolvedText.text = "0%";
    63.         }
    64.         if (open > 0) {
    65.             CircleGraph_Yellow.fillAmount = CircleGraph_Green.fillAmount + open / total;
    66.             CircleGraph_OpenText.text = ((open / total) * 100).ToString("0.0") + "%";
    67.         }
    68.         else
    69.         {
    70.             CircleGraph_Yellow.fillAmount = 0;
    71.             CircleGraph_OpenText.text = "0%";
    72.         }
    73.         if (failed > 0) {
    74.             CircleGraph_Red.fillAmount = CircleGraph_Yellow.fillAmount + failed / total;
    75.             CircleGraph_FailedText.text = ((failed / total) * 100).ToString("0.0") + "%";
    76.         } else
    77.         {
    78.             CircleGraph_Red.fillAmount = 0;
    79.             CircleGraph_FailedText.text = "0%";
    80.         }
    81.     }
    82.  
    83.     void UpdateBarGraph()
    84.     {
    85.         //Set Correct day name
    86.         DateTime dateValue = new DateTime(GameDateTime.Year, GameDateTime.Month, GameDateTime.Day);
    87.         foreach (var day in daysText)
    88.         {
    89.             day.text = dateValue.DayOfWeek.ToString();
    90.             dateValue = dateValue.AddDays(-1);
    91.         }
    92.  
    93.         // Left Side Value. Min and Max range on graph
    94.         int max = supportData.days_newTasks.Max();
    95.         int min = 0;
    96.         minMaxValueBarGraph[0].text = "" + min;
    97.         minMaxValueBarGraph[1].text = "" + Mathf.RoundToInt(max * 0.33f);
    98.         minMaxValueBarGraph[2].text = "" + Mathf.RoundToInt(max * 0.66f);
    99.         minMaxValueBarGraph[3].text = "" + max;
    100.  
    101.         // Bar Graph
    102.         int i = 0;
    103.         foreach (var bar in barGraph)
    104.         {
    105.             if(max != 0 && supportData.days_newTasks[i] != 0) {
    106.                 bar.fillAmount = (supportData.days_newTasks[i] * 1f / max);
    107.             } else
    108.             {
    109.                 bar.fillAmount = 0;
    110.             }
    111.             i++;
    112.         }
    113.    
    114.     }

    I have never used Threads but I just learned about it, so I was curious if it should be used?
    Other then these methods it's not much happening with the panel open.
    Maybe I should use Async on the main method perhaps?
     
    Last edited: Jun 1, 2019
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,146
    Without us knowing what those methods do or why you feel like they need to refresh constantly, we can't tell you much. Also, if you deal with Unity stuff, that still has to be done on the main thread, so using threads may not work for you anyways.

    You may be trying to optimize where you don't need to or thinking you need to run stuff every frame when perhaps you don't.
     
  3. Loff

    Loff

    Joined:
    Dec 3, 2012
    Posts:
    81
    Hey,

    Fair enough :) I will update the thread with the method scripts in a min
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,294
    Look into allocations that you're doing. Those are the main root of evil.
    String concatenation is one of. Try avoiding combining text.
    Use multiple text components to split static vs dynamic text.

    .ToString() allocs as well.

    Alternatively, do not create new strings when the data (e.g. count) doesn't change.

    new List<> is another one. Allocate one list, and then .Clear() it whenever you need it, instead of creating a new one each time.

    Instantiating / Destroying is heavy. Bear in mind.

    I'd bet this:
    Code (CSharp):
    1. HelpFunctions.RemoveChildObjectsFromParent(FailedTasksContent);
    are for destroying objects, right?

    Pooling is the only thing that can save performance in this case. Look into pooling techniques available on the internet / Unity Learn tutorial section.

    Modifying UI also triggers canvas rebuilding which rebuilds a whole canvas.
    If you've got a bunch of objects that are dynamic, try grouping them into sub-canvases.
    (By attaching Canvas component to the root dynamic objects. Don't forget a Graphics Raycaster if they're required to be clickable)

    So they'd rebuild less portion of your UI.
     
    Loff likes this.
  5. Loff

    Loff

    Joined:
    Dec 3, 2012
    Posts:
    81
    Awesome reply. Will redo the code with your tips!
    Thanks.

    Few questions:
    1. Use multiple text components to split static vs dynamic text.
    Code (CSharp):
    1. AllTasksText.text = "All Tasks: " + supportData.totalNumberOfCases;
    By Multiple text components you mean: Add text component that contains: "All Tasks: " and is just static, while having a second one being dynamic for supportData.totalNumberOfCases?
    Resulting in
    Code (CSharp):
    1. AllTasksText.text = supportData.totalNumberOfCases.ToString();
    1.1 .ToString() allocs as well.
    I have to use .ToString(); or " " etc because for example supportData.totalNumberOfCases; is an int, don't I ?

    2. Instantiating / Destroying is heavy. Bear in mind.
    Agree. Most cases I use instantiate, I don't have a choice I think as I have to make 0-X objects.
    In this case, I could create 6 objects and just update them honestly as it's always 0-6 objects showing. I could just update them instead of recreating.
    Or do you have a suggestion for 0-X object situtation ?

    3. Pooling
    No idea what pooling is. I will try to look into it soon!

    4.
    Modifying UI also triggers canvas rebuilding which rebuilds a whole canvas.
    If you've got a bunch of objects that are dynamic, try grouping them into sub-canvases.

    Is sub-canvas a canvas component, atteched to a child GameObject of your main canvas? I split up the Canvas child gameobjects a lot, but nothing more then that currently. I bet 90% is deactivated all the time as I active some and deactivate some depending on what "site" user is on etc.
    Never used multiple canvas!
     
    Last edited: Jun 1, 2019
  6. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Why should it refresh all the time?

    The most important questions with regards to that are:
    1) Do you call "UpdateSupportSystem" in Update?
    2) How often does the data change?

    If the data only changes every now and then, "wait" for the changes to happen and then react with the UI update.

    You cannot access most of Unity's API from other threads. So the only thing you could move off the main thread would be the processing and preparation of your data, but it seems you don't have that much data right now, so that wouldn't make much of a difference, if any at all.
     
  7. Loff

    Loff

    Joined:
    Dec 3, 2012
    Posts:
    81
    I had 2) in mind. I often just update when data changes like you say. However currently data changes very quickly - but I think I will redo that system as well. It's just a prototype of the feature.
    1) I did call it in update for testing, but I tested with a timer as well. Both works fine, with no lag - but again, it doesn't feel right to update it that often :) Trying to stay away from Update() if I can.
     
  8. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    Use a callback system to update the values only when they change, you can have each value listen to its own stuff so you won't have to recalc everything if only one thing changes.

    about the 0-6 objects, what every they may be - you can create 6 of them on the start of the game, when you don't need one - disable it, when you need one - enable it, that's pretty much pooling.
     
    Loff likes this.
  9. Grankor

    Grankor

    Joined:
    Mar 2, 2015
    Posts:
    14
    One thing to keep in mind about threading and Unity.
    Unity builds are supported on many platforms, so manually trying to manipulate threading may have some unintended consequences on various platform.
    Generally speaking, let the OS handle the threading.

    What you MAY be looking to ask for, is tasks.
    When working with the UI, tasks can be very tricky and cause exceptions and race conditions.
    As mentioned earlier, using callbacks is a great way to handle this.

    This can be tricky to get, as it's a different way of thinking, but here's a great article that gives you 2 common callback methods: https://www.c-sharpcorner.com/UploadFile/1c8574/delegate-used-for-callback-operation/

    It has a complete explanation of callbacks using both delegates and interfaces, and if your desire is to get better at coding, leaning both methods is important.

    The short version - Delegates are great if you need to do 1 operation that will always result in a similar thing... Like maybe updating health in a UI.
    However, if you have multiple outcomes to something you want to update, interfaces are are more efficient.


    The short version of tasks, is that tasks are a thing that allow the program to continue without your task completing. So, if the gameplay doesn't require the task to complete, it's a good way to use. However, if there is ANYTHING that depends on this task to complete later in your code, you'll need to figure out a different approach.

    Anyways, here's a callback example. It's 'fairly' simple, but has some ideas displayed which are important in this type of design.

    If you want to play with it a bit, copy/paste this into a console application in Visual Studio (.Net Core or .Net framework, doesn't matter).
    Run it a few times to see what happens. You'll likely have different output each time you run it.
    What happens is, when you use something like Task.Run, you're telling the system "Hey, I need this thing run, but don't make the rest of this program wait. Keep going".

    The while loop is just for illustration, but you should see it loop several times before the tasks complete, and they will complete seemingly at random, because your system is determining how important they are, and when to run them.

    The TL:DR for async/await. This is basically here to keep them from being put on the back of the stack forever, which can and will happen if they aren't used.


    Code (CSharp):
    1. using System;
    2. using System.Threading.Tasks;
    3.  
    4. namespace CallbackTest
    5. {
    6.     class Program
    7.     {
    8.         //Wrap our primitive in a class, so we can pass them to another function
    9.         private static CharacterStats _characterStats= new CharacterStats();
    10.  
    11.         static void Main(string[] args)
    12.         {
    13.             _characterStats.Health = 100;
    14.             _characterStats.Magic = 100;
    15.  
    16.             //Here we make the actual callback
    17.             UIUpdate callBack = CheckStuff;
    18.  
    19.             //Starting a task, which will now run separately from the main thread.
    20.             //Running multiple for illustraion pusposes
    21.             //Async and await are just making sure these complete ;)
    22.             Task.Run(async () => await CheckStuff(_characterStats));
    23.             Task.Run(async () => await CheckStuff(_characterStats));
    24.             Task.Run(async () => await CheckStuff(_characterStats));
    25.             Task.Run(async () => await CheckStuff(_characterStats));
    26.  
    27.             Console.WriteLine($"Health = {_characterStats.Health}\nMagic = {_characterStats.Magic}");
    28.  
    29.  
    30.             //A simple look for illustration purposes
    31.             while (_characterStats.Health != 60)
    32.             {
    33.                 Console.WriteLine($"Health = {_characterStats.Health}\nMagic = {_characterStats.Magic}");
    34.             }
    35.                
    36.             Console.WriteLine("Done!");
    37.             Console.ReadLine();
    38.         }
    39.  
    40.         //Using the same format here as our delegate, return type and parameters
    41.         public async static Task CheckStuff(CharacterStats stats)
    42.         {
    43.             //Notice the updating part here
    44.             Console.WriteLine($"Updating : Health = {stats.Health}\nUpdating : Magic = {stats.Magic}");
    45.             stats.Health -= 10;
    46.             stats.Magic -= 10;
    47.             //Notice the resulting part here
    48.             Console.WriteLine($"Resulting : Health = {stats.Health}\nResulting : Magic = {stats.Magic}");
    49.         }
    50.     }
    51.  
    52.     //Think of this like a single method in an interface
    53.     public delegate Task UIUpdate(CharacterStats stats);
    54.  
    55.  
    56.     //Our wrapper class
    57.     public class CharacterStats
    58.     {
    59.         public int Health{ get; set; }
    60.         public int Magic { get; set; }
    61.     }
    62. }
     
    Loff likes this.
  10. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,294
    Yes, that is correct. At least you'll be allocating only one string vs two.
    There's also an interesting hack I've came up with. Create a lookup of <int, string> and in case you need a number - just check if lookup has a string for it. If it does, then return that string. Otherwise create a new one and push it to the lookup.

    Works like a charm for rapid use of numbers in a range like 0-100.

    Not so good of a solution if your numbers are not intersecting with each other.
    Don't forget to clear that lookup once in a while.

    Yes, see above.

    Simplest solution is to create enough views to display your data, and never destroy them.

    Its just a component. Attaching canvas as a child of a canvas will break them up into multiple "sub-UI" for this matter.
    Although, try not to over do that, as canvases aren't free as well.
     
    Loff likes this.