Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

[RELEASED] Master Scheduler - Task scheduling tool

Discussion in 'Assets and Asset Store' started by Thunderbyte, Oct 6, 2017.

  1. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Master Scheduler is a tool that allows you to spread workload over time.

    Social Media 1200x630.png






    Example usage :

    - AI : Your agents' AI doesn't need to be processed every frame. With Master Scheduler, you can guarantee that only a fixed number of your agents are doing work at any given moment and thus easily and efficiently spreading the AI workload over time.

    - Explosions : Afraid that exploding a car in that parking lot will create an out of control chain reaction of OverlapSpheres ? Master Scheduler can easily spread the explosion events over the few following frames and eliminate frame time spikes.

    - Spawning : you can queue thousands of spawning events and let the system handle the rest.

    - Weapons : Your shotgun shoots multiple pellets ? That means multiple raycasts, impact visual and sound effects, GetComponent() calls and health updates in the same frame. With Master Scheduler you can spread the shooting event over a few imperceptible milliseconds and avoid the fps drop.

    Master Scheduler doesn't create or manage threads. It creates job queues and executes them in the most efficient way over multiple frames.
    You can either constraint the job queue to a maximum number of jobs per frame or to finish all jobs in a set number of frames.

    Documentation and example scenes included.
    Asset Store

    Playmaker FSM and Behavior Designer scripts kindly provided by mdotstrange.
     
    Last edited: Apr 13, 2020
    tcmeric and Teila like this.
  2. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    In the PathFollow example, calling the Update() function 10,000 times has a significant overhead.
    Now all objects are updated from a single Manager class, resulting in a gain of about 3.5ms on the main thread (on an i7 3770 cpu).

    This update only affects the PathFollow example.
     
  3. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Update :
    Fixed a bug in the path finding example that generated incorrect velocities to some agents under certain circumstances.
     
    Last edited: Mar 20, 2018
  4. Banana13

    Banana13

    Joined:
    May 28, 2013
    Posts:
    44
    Does this work on mobile devices ?
     
  5. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Yes it does. It works on all platforms that Unity can handle.
     
  6. neng

    neng

    Joined:
    Sep 6, 2013
    Posts:
    2
    How do I prevent the Followers from walking through each other? I'd like them to avoid each other but adding a box collider didn't seem to work.
     
  7. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    It's not enough to add a collider, you'd have to implement an agent collision logic.
    A super simple and quick way, for example, would be to raycast from each cube and prevent movement if there's a follower ahead.
    I've implemented this simple movement system as an example usage of the scheduler, nothing more.
    If you want a complete movement system, look into this library. It's a unity port of the recast detour library. And It's free and robust.
     
  8. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Hello- this looks really interesting and useful- can you show some code examples? How would we implement it in our projects? Thank you
     
  9. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Hi,
    The usage really is simple.
    You create a queue :
    Code (CSharp):
    1. QueueHub.CreateQueue("QueueName", int NumberOfJobsPerFrame, bool bShouldLoop);
    or
    Code (CSharp):
    1. QueueHub.CreateQueue(string queueName, bool bShouldLoop, int maxFrames);
    And you can add jobs to a queue :
    Code (CSharp):
    1. AddJobToQueue(string queueName, GameObject Instigator, QueueSpotDelegate JobDelegate);
    So let's say you have a shotgun that shoots 10 pellets at once with this code :
    Code (CSharp):
    1. void Fire()
    2. {
    3.   for(int i = 0; i < 10; ++i)
    4.     FireOnePellet();
    5. }
    And you we want to use the scheduler to handle the firing.
    We'd start by creating the queue
    Code (CSharp):
    1. QueueHub.CreateQueue("ShotgunPellets", 2, false);
    Then instead of calling Fire(), we would call NewFire() :
    Code (CSharp):
    1. void NewFire()
    2. {
    3.   for(int i = 0; i < 5; ++i)
    4.     QueueHub.AddJobToQueue("ShotgunPellets", gameObject, FireOnePellet);
    5. }
    Now we're shooting 2 pellets for frame instead of 10 at once.
    This is a simple example, but the principle can be used with any looped heavy processing really, for massive performance gains.

    You can of course manage queues and adjust their properties (create/destroy, add/remove jobs, batch size, looped and max frames) at runtime.
     
    Last edited: Jul 18, 2018
  10. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Thank you, very nice- I will purchase it and give it a try-
     
  11. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    I hope it helps you :)
     
    imaginationrabbit likes this.
  12. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Hello- I purchased the asset and have started to implement it in my project- I did some tests and this asset does indeed greatly increase performance in many cases :)

    I read the doc's and understand how to use it- it would be helpful to have a few more use examples with sample code with perhaps some example numbers as well- when using this on my explosions that can trigger other explosions how many jobs should I allow per frame for example? 2? 200?

    I know that would vary per project but it would be helpful to have some rough/ballpark numbers to help new users get started-

    Also as a newer programmer looking through the asset store for performance solutions I've only used threading solutions before I bought your scheduler- as you know many things in Unity are not thread safe so you have to carefully craft your threaded code to squeeze the performance out- a big upside to your scheduler is that you can run any Unity code with a big increase in performance- it should say that on the box to differentiate it for noob's like me :)

    Thanks-
     
  13. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    I'm really glad you found the tool helpful.

    Regarding an ideal number of events per frame, as you say, it really depends on the project and what's going on in the level, but generally, for explosion events (with lots of physics queries and spawning), I try to keep them at up to 10 per frame or force them to finish in 4-5 frames and I adjust from there.

    And you're right about threading. The scheduler doesn't create/manage threads and that has its advantages.
     
    imaginationrabbit likes this.
  14. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Thanks for the info about the job counts/frames- that sort of info is really useful to someone like me using a scheduler for the first time.

    A requested addition to the docs would be to include example jobs per frame numbers for these uses as this sounds like a great idea that I want to implement but I have no idea what numbers are too high/too low etc-

    AILow:
    AIHigh:
    SpawningHeavy:
    SpawningLight:
    PhysicsQueries:

    Also are Queue's destroyed on scene changes?

    I'm using a Queue successfully in Scene0 then on scene change to Scene1 the Method doesn't execute and I get "couldn't find queue" in the console-

    It seems a "Queue Hub" game object is added to the scene which holds the Queue's but it is not persistent so upon loading a new scene the game object is not present so the queue is not found- what is the proper way to reload this game object? Make new Queue's on scene loading or make the game object persistent?
     
    Last edited: Aug 3, 2018
  15. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    In the examples I use the QueueHub to handle scenes. It uses a singleton pattern, so when you try to create/access a queue for the first time, it gets created.
    The QueueHub is a normal GameObject and it has the same life span as any other GameObject. So when you load a new scene, it gets destroyed.
    You can tell Unity to keep it alive when switching scenes by using the DontDestroyOnLoad function. https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html

    You can call it on your god object or better yet, in the QueueHub's awake or start functions.
    That way you won't lose your queues after changing scenes.

    Constraining queues to a number of frames instead of a number of jobs is really simple. You can either do that when you create the queue using one of these constructors :
    Code (CSharp):
    1. QueueHub.CreateQueue(string queueName, int jobsPerFrame, bool bIsLooping)    //Constrained by number of jobs
    or
    Code (CSharp):
    1. QueueHub.CreateQueue(string queueName, bool bIsLooping, int maxFrames)  //Constrained by number  of frames
    If your queues are already created, you can switch to a number of frames constraint :
    Code (CSharp):
    1. QueueHub.SetMaxFrames(string queueName, int maxFrames)
    or switch back to a number of jobs constraint :
    Code (CSharp):
    1. QueueHub.SetJobBatchSize(string queueName, int newSize)
    And of course, you can adjust the parameters at runtime.

    When i was creating the path follow example, I had no idea what is too much. I just exposed the number of jobs to the inspector and tweaked the values until I saw acceptable cpu times on the profiler.

    If your scenes are very heavy on the cpu that number will be different. You really need to figure out what your target hardware is, and adjust the values for your specific levels.

    I really can't give you a fit all number. You need to profile and adjust accordingly.

    And keep in mind that if the profiler is telling you that a specific piece of code isn't causing performance issues, then it doesn't need optimizing.
    The profiler is your only guide here.
     
    Last edited: Aug 3, 2018
    imaginationrabbit likes this.
  16. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Thank you for the information and for the advice- I now have everything running smoothly :)
     
  17. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Fantastic ! I'm here if you have more questions : )
     
  18. therewillbebrad

    therewillbebrad

    Joined:
    Mar 2, 2018
    Posts:
    151
    Hi have you tested this with A* pathfinding project? Very intrigued by this as you could seemingly do this for most things involving unity. I'm thinking projectiles and even spawning pooled objects.
     
  19. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    I haven't looked at the A* project in particular, but the process is the same : Identify the most expensive tasks and run them through the scheduler.
    In the case of pathfinding it's generally path queries. So if you have N queries, you could run them through the scheduler and have them executed in batches of N/6 queries, with all queries being done in 6 frames for example. At 60Hz that's a tenth of second.

    The principle can be applied for any workload that can be spread over multiple frames, like spawning or pathfinding. Or if you have hundreds of projectiles you could offload all the expensive vector math onto the scheduler and update the positions in realtime.
     
  20. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Hello, Using this a lot and I love it- I have another question :)

    If I add a job to a Queue that repeats and the instigator gameObject becomes inactive(despawned which disables the GameObject) will that job still run in the queue?

    Also in the doc's it says "You can also remove specific jobs from the queue" but there is no parameter for the name of the method that was submitted to the queue- just the instigator- wouldn't this remove all jobs that are running from the instigator gameObject?

    Thank you.
     
  21. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    If the gameObject is destroyed the job will not run because the function that the scheduler calls is tied to that object, that means that if you're destroying objects make sure you remove their jobs from the queue.
    Doing it this way means the scheduler doesn't do checks. It makes things a bit faster.
    And if there are multiple jobs for a given object, the remove job function will remove all jobs related to that object on the given queue.
    I work with the assumption that each queue handles the same kind of jobs.
    Do you often find yourself adding different jobs from the same object to the same queue ?
    I'm open to suggestions :)
     
  22. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Thanks for the quick reply-

    Yes, I have been adding different types of jobs to the same queues- but not for any particular reason so I'll make it so they have their own queue-

    The only idea I had was if there was another parameter added to RemoveJobFromQueue with the name of the function that was added to only remove that job- but I can easily just use separate queues-

    Also I made some scripts for a non coder friend so that he could use Master Scheduler to control Playmaker FSM and Behavior Designer updates- might be useful to some non coders that want to use MasterScheduler https://github.com/mdotstrange/masterSchedulerScripts
     
  23. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    That's awesome. I'll add your github link to the first post if you don't mind.
    Also, I've added a "RemoveJobByName" method. You'll now be able to remove jobs based on their name.
    I've submitted the update and it's in review.
    Shoot me an email if you don't wanna wait ;)
     
  24. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Ah cool- thanks! The repo is public so please feel free to do whatever you want with it :)
     
  25. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Update is live :)
     
    imaginationrabbit likes this.
  26. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Cool! Thanks!
     
  27. greene_tea92

    greene_tea92

    Joined:
    Jun 26, 2017
    Posts:
    22
    Hey there, I bought your Master Scheduler asset a few days ago, and am now trying to implement it into my project. Any tips on how you optimized explosions?? I'm trying to separate the explosion function from the actual collision and I run into all kinds of problems, any help would be greatly appreciated, thank you. :)
     
    Last edited: Oct 18, 2022
  28. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Hi,
    In the car explosion example, I have an Explode() function which does the exploding as well as an overlapSphere to find the cars in its vicinity.
    In order to use the scheduler, i replace the Explode() call with RequestExplode(), which simply does this :

    QueueHub.AddJobToQueue("Explosion", gameObject, Explode);


    That line tells the scheduler "Hey, add this explosion event to the list of tasks". The scheduler then executes the explosions, a few per frame, instead of having a monstrous chain of explosions happen at the same time (basically, a guaranteed hitch/freeze).
    What problems are you having exactly ?
     
    Last edited: Apr 2, 2020
  29. greene_tea92

    greene_tea92

    Joined:
    Jun 26, 2017
    Posts:
    22
    Hey, thanks for the tip - I appreciate it. My explosion works off of collision, and the problem arises from not being able to put a function the Queue Hub will be able to detect inside of another function (I cannot use OnCollisionEnter as a function for the Queue Hub, for example). When I try to make a custom function for the explosion, Unity has trouble saving the collision to be referenced inside said custom function, thus refusing optimization of any kind of explosion in game. :(

    I'm sure it's a simple fix. If you have any tips, your assistance would be much obliged. Thanks again.
     
    Last edited: Oct 18, 2022
  30. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    You can't add OnCollisionEnter but you can use it to add functions to the queue.
    You could for example do this :

    Code (CSharp):
    1. void OnCollisionEnter(Collision collision)
    2. {
    3.  
    4.   PrepareData(collision);
    5.   RequestExplosion();
    6. }
    PrepareData() would write the collision info into a variable (if needed).
    RequestExplosion() would add your Explode() function to the queue. And Explode() would look up the collision info that PrepareData() made.
     
    greene_tea92 likes this.
  31. greene_tea92

    greene_tea92

    Joined:
    Jun 26, 2017
    Posts:
    22
    Alright, I'll try it out. Can't thank you enough. :)
     
  32. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Happy to help.
    Let me know if you have more questions.
     
  33. MOBILIN

    MOBILIN

    Joined:
    May 6, 2017
    Posts:
    15
    Developer, of course, know the pros and cons of this product. May I ask what they are?
     
  34. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Pros : You improve performance and remove frame spikes by spreading work over multiple frames.
    Cons : Since the tool works by splitting the work into chunks and running them over multiple frames, some things will be executed with a delay of a few frames.
    So for systems you need to be responsive, you should set the frame spread to something like 2-5, otherwise you can aim for 10 or higher depending on the target performance.
     
  35. MOBILIN

    MOBILIN

    Joined:
    May 6, 2017
    Posts:
    15
    Your email address, Studiosthunderbytestudios@gmail.com, is not reachable!


    I wrote as the following, but the FindEnemy is not called at all:

    QueueHub.CreateQueue("Find", true, 5);
    QueueHub.AddJobToQueue("Find", gameObject, FindEnemy);


    Because, DesiredNbInterations is 0 by DetermineNumberOfJobs().
    I had expected the FindEnemy would be called every frame or once at the fifth frame.
     
    Last edited: Jan 4, 2021
  36. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Hi superip,
    It looks like you were using the wrong address. My email is thunderbytestudios@gmail.com

    DetermineNumberOfJobs() generates 0 because it sees no jobs. My first assumption is that the CreateQueue and AddJobToQueue code is never reached.
    Could you please confirm that those functions are actually called ? (you could use a breakpoint or a simple Debug.Log())

    Here is a simple sample where I add a logging function to a queue :
    Code (CSharp):
    1. public class SchedulingTest : MonoBehaviour
    2. {
    3.     public int JobCount = 10;
    4.     static int Count = 0;
    5.  
    6.     void Start()
    7.     {
    8.         QueueHub.CreateQueue("LogTest", true, 5);
    9.         for(int i = 0; i < JobCount; ++i)
    10.         {
    11.             QueueHub.AddJobToQueue("LogTest", gameObject, LogTest);
    12.         }
    13.     }
    14.  
    15.     void LogTest()
    16.     {
    17.         Debug.Log("Logging " + Count);
    18.         Count++;
    19.     }
    20. }
    21.  
    I'm using the same settings that you used.
    We can continue the investigation by email if you'd like.

    Cheers,
     
  37. MOBILIN

    MOBILIN

    Joined:
    May 6, 2017
    Posts:
    15

    Regarding the e-mail address, please modify the manual.

    upload_2021-1-4_17-40-10.png

    Please comment out the for statement as in the code below.
    Of course, your sample will work as a result as DesiredNbInterations gets 1 by DeterminNumberOfJobs().

    Code (CSharp):
    1. public class SchedulingTest : MonoBehaviour
    2. {
    3.     public int JobCount = 10;
    4.     static int Count = 0;
    5.  
    6.     void Start()
    7.     {
    8.         QueueHub.CreateQueue("LogTest", true, 5);
    9.         //for(int i = 0; i < JobCount; ++i)
    10.         {
    11.             QueueHub.AddJobToQueue("LogTest", gameObject, LogTest);
    12.         }
    13.     }
    14.  
    15.     void LogTest()
    16.     {
    17.         Debug.Log("Logging " + Count);
    18.         Count++;
    19.     }
    20. }
    My code is for the NPC to search and process the enemy, and there is only one NPC. Since DesiredNbInterations becomes 0, this NPC does nothing.

    That's why I think DetermineNumberOfJobs() should be modified something like this:

    Code (CSharp):
    1. if (maxFrames > 1)
    2.             {
    3.                 DesiredNbInterations = Mathf.FloorToInt(Queue.Count / maxFrames);
    4.  
    5.                 if (Queue.Count > 0 && DesiredNbInterations == 0)
    6.                     DesiredNbInterations = 1;
    7.             }
    8.             else
    9.                 DesiredNbInterations = Mathf.Min(Queue.Count, jobsPerFrame);
     
    Last edited: Jan 4, 2021
  38. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    You're right, that's a bug.
    Here's the fix :

    Code (CSharp):
    1. void DetermineNumberOfJobs()
    2.     {
    3.        
    4.         int tempBatchSize = Mathf.Max(maxFrames, jobsPerFrame);
    5.         DesiredNbInterations = 0;
    6.         if (tempBatchSize > 1 && Queue.Count > tempBatchSize)
    7.             DesiredNbInterations = Mathf.FloorToInt(Queue.Count / tempBatchSize);
    8.         else
    9.             DesiredNbInterations = Mathf.Min(Queue.Count, tempBatchSize);
    10.     }
    Your correction would only work for the case where Queue.Count is 1. Whereas the bug kicks in whenever Queue.Count <= maxFrames.

    Please let me know if you still have issues.
     
    Last edited: Jan 4, 2021
  39. MOBILIN

    MOBILIN

    Joined:
    May 6, 2017
    Posts:
    15
    Works!
     
  40. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    Great !
    The update is available on the store.
     
  41. hellosimplegame

    hellosimplegame

    Joined:
    Jun 13, 2017
    Posts:
    8
    The tool looks nice!
    @Thunderbyte are you planning to use the Unity Job system?
    I am going to buy this now!
     
  42. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    The pairing with the job system would be interesting.
    You could assign a queue to each job and have each job tick its queue.
    I'll try to include an example when I have the time.
     
  43. hellosimplegame

    hellosimplegame

    Joined:
    Jun 13, 2017
    Posts:
    8
    Thank you so much! I just buy and give it five stars!
    Please update to the job system and new tech of Unity so that we can use it with powerful and convenient
     
  44. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    You're right. I'll work on that asap.
     
    one_one likes this.
  45. moD256

    moD256

    Joined:
    Nov 11, 2016
    Posts:
    3
    Hello, does it has local avoidance?
     
  46. Thunderbyte

    Thunderbyte

    Joined:
    Aug 28, 2015
    Posts:
    110
    No it doesn't.
    The crowd scene is just a demo, but the tool can be used with an existing crowd solution to improve its performance.