StarShadow

Unity Task Handler

Mar 6th, 2021 (edited)
657
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * Based on: https://forum.unity.com/threads/a-more-flexible-coroutine-interface.94220/
  3.  *
  4.  * This version adds task variations such as batch tasks to run tasks in parallel
  5.  * and task sequences to run one task after another.
  6.  */
  7.  
  8. using System;
  9. using System.Collections;
  10. using System.Collections.Generic;
  11. using System.Linq;
  12. using UnityEngine;
  13.  
  14. /// <summary>
  15. /// Task manager which handles all tasks.
  16. /// </summary>
  17. public class Tasks : MonoBehaviour
  18. {
  19.     private static Tasks _instance;
  20.  
  21.     private static Tasks GetInstance()
  22.     {
  23.         if (_instance) return _instance;
  24.         var g = new GameObject("Tasks");
  25.         _instance = g.AddComponent<Tasks>();
  26.         return _instance;
  27.     }
  28.  
  29.     /// <summary>
  30.     /// Get an action where tasks can be submitted to be run by the main tasks GameObject.
  31.     /// </summary>
  32.     /// <returns>Task action.</returns>
  33.     public static Action<IEnumerator> TaskRunner()
  34.     {
  35.         return work => GetInstance().StartCoroutine(work);
  36.     }
  37.  
  38.     /// <summary>
  39.     /// Get a task for a coroutine.
  40.     /// </summary>
  41.     /// <param name="task">A coroutine.</param>
  42.     /// <returns>New task.</returns>
  43.     public static Task Wrap(IEnumerator task)
  44.     {
  45.         return new Task(task, TaskRunner());
  46.     }
  47.  
  48.     /// <summary>
  49.     /// Get a task that runs the coroutines in parallel.
  50.     /// </summary>
  51.     /// <param name="work">Coroutines.</param>
  52.     /// <returns>Batch task.</returns>
  53.     public static BatchTask Batch(IEnumerable<IEnumerator> work)
  54.     {
  55.         return Batch(work.Select(Wrap));
  56.     }
  57.  
  58.     /// <inheritdoc cref="Batch(System.Collections.Generic.IEnumerable{System.Collections.IEnumerator})"/>
  59.     public static BatchTask Batch(IEnumerable<ITask> tasks)
  60.     {
  61.         return new BatchTask(tasks, TaskRunner());
  62.     }
  63.  
  64.     /// <summary>
  65.     /// Return a task which animates a value at a given speed.
  66.     /// </summary>
  67.     /// <param name="from">Starting value.</param>
  68.     /// <param name="to">Target value.</param>
  69.     /// <param name="update">Action that applies the animation value on each frame.</param>
  70.     /// <param name="speed">Animation speed (for deltaTime).</param>
  71.     /// <param name="interpolate">Linear interpolation function for the animation value type.</param>
  72.     /// <param name="ease">Easing function for the animation.</param>
  73.     /// <typeparam name="T">Type of the value to animate.</typeparam>
  74.     /// <returns>Animation task.</returns>
  75.     public static AnimateTask<T> Animate<T>(T from, T to, Action<T> update, float speed, Func<T, T, float, T> interpolate, Func<float, float, float, float> ease)
  76.     {
  77.         return new AnimateTask<T>(from, to, update, speed, interpolate, ease, TaskRunner());
  78.     }
  79.  
  80.     /// <summary>
  81.     /// Create a task builder.
  82.     /// </summary>
  83.     /// <returns>New task builder.</returns>
  84.     public static TaskBuilder Create() => new TaskBuilder();
  85. }
  86.  
  87. public delegate void TaskFinishHandler(bool manualStop);
  88.  
  89. public interface ITask
  90. {
  91.     /// <summary>
  92.     /// Event which is called when the task completes.
  93.     /// </summary>
  94.     event TaskFinishHandler OnFinish;
  95.  
  96.     /// <summary>
  97.     /// Start the task.
  98.     /// Behavior is undefined if called more than once.
  99.     /// Ideally a task should ignore this after the first call.
  100.     /// </summary>
  101.     void Start();
  102.  
  103.     /// <summary>
  104.     /// Stop the task. Execution stops the next time the coroutine yields.
  105.     /// </summary>
  106.     void Stop();
  107.  
  108.     /// <summary>
  109.     /// Pause the task. Execution stops the next time the coroutine yields.
  110.     /// </summary>
  111.     void Pause();
  112.  
  113.     /// <summary>
  114.     /// Unpause the task. Execution continues on the next frame.
  115.     /// </summary>
  116.     void Continue();
  117.  
  118.     /// <summary>
  119.     /// Starts and completes the task instantly. This should run the OnFinish
  120.     /// event without starting the task.
  121.     /// </summary>
  122.     void Complete();
  123. }
  124.  
  125. /// <summary>
  126. /// Build a sequence of tasks.
  127. /// </summary>
  128. public class TaskBuilder
  129. {
  130.     private readonly List<ITask> _tasks = new List<ITask>();
  131.     private bool _delayFrame;
  132.     private bool _delayFrameBetween;
  133.  
  134.     /// <summary>
  135.     /// Get the task representing all the tasks in the builder.
  136.     /// </summary>
  137.     /// <returns>Built task.</returns>
  138.     public ITask Build()
  139.     {
  140.         var seq = new TaskSequence(_tasks, Tasks.TaskRunner());
  141.         seq.DelayFrame(_delayFrame);
  142.         seq.DelayFrameBetween(_delayFrameBetween);
  143.         return seq;
  144.     }
  145.  
  146.     /// <summary>
  147.     /// Add a task to the sequence.
  148.     /// </summary>
  149.     /// <param name="task">Task to add.</param>
  150.     /// <returns>Builder instance.</returns>
  151.     public TaskBuilder Add(IEnumerator task)
  152.     {
  153.         _tasks.Add(Tasks.Wrap(task));
  154.         return this;
  155.     }
  156.  
  157.     /// <inheritdoc cref="Add(System.Collections.IEnumerator)"/>
  158.     public TaskBuilder Add(ITask task)
  159.     {
  160.         _tasks.Add(task);
  161.         return this;
  162.     }
  163.  
  164.     /// <inheritdoc cref="Add(System.Collections.IEnumerator)"/>
  165.     public TaskBuilder Add(Action action)
  166.     {
  167.         _tasks.Add(new ActionTask(action, Tasks.TaskRunner()));
  168.         return this;
  169.     }
  170.  
  171.     /// <inheritdoc cref="Add(System.Collections.IEnumerator)"/>
  172.     public TaskBuilder Add(object o)
  173.     {
  174.         _tasks.Add(new YieldTask(o, Tasks.TaskRunner()));
  175.         return this;
  176.     }
  177.  
  178.     /// <summary>
  179.     /// Add a tasks using a function that will not be executed until it
  180.     /// is time for the task to start.
  181.     /// </summary>
  182.     /// <param name="lazyTask"></param>
  183.     /// <returns></returns>
  184.     public TaskBuilder AddLazy(Func<ITask> lazyTask)
  185.     {
  186.         _tasks.Add(new LazyTask(lazyTask));
  187.         return this;
  188.     }
  189.  
  190.     /// <summary>
  191.     /// Add a batch of tasks that will execute in parallel.
  192.     /// Every task in the batch must finish before the next task
  193.     /// in the sequence will begin.
  194.     /// </summary>
  195.     /// <param name="tasks">Tasks to add.</param>
  196.     /// <returns>Builder instance.</returns>
  197.     public TaskBuilder AddBatch(IEnumerable<IEnumerator> tasks)
  198.     {
  199.         _tasks.Add(Tasks.Batch(tasks));
  200.         return this;
  201.     }
  202.  
  203.     /// <inheritdoc cref="AddBatch(System.Collections.Generic.IEnumerable{System.Collections.IEnumerator})"/>
  204.     public TaskBuilder AddBatch(IEnumerable<ITask> tasks)
  205.     {
  206.         _tasks.Add(Tasks.Batch(tasks));
  207.         return this;
  208.     }
  209.  
  210.     /// <summary>
  211.     /// Add a batch of tasks given some elements and a function to convert them to coroutines.
  212.     /// </summary>
  213.     /// <param name="ie">Elements to process</param>
  214.     /// <param name="workFunc">Get a coroutine from each element.</param>
  215.     /// <typeparam name="T">Element type.</typeparam>
  216.     /// <returns>Builder instance.</returns>
  217.     /// <seealso cref="AddBatch(System.Collections.Generic.IEnumerable{System.Collections.IEnumerator})"/>
  218.     public TaskBuilder AddBatch<T>(IEnumerable<T> ie, Func<T, IEnumerator> workFunc)
  219.     {
  220.         return AddBatch(ie.Select(workFunc));
  221.     }
  222.    
  223.     /// <inheritdoc cref="AddBatch{T}(System.Collections.Generic.IEnumerable{T}, System.Func{T, System.Collections.IEnumerator})"/>
  224.     public TaskBuilder AddBatch<T>(IEnumerable<T> ie, Func<T, ITask> workFunc)
  225.     {
  226.         return AddBatch(ie.Select(workFunc));
  227.     }
  228.  
  229.     /// <inheritdoc cref="AddBatch(System.Collections.Generic.IEnumerable{System.Collections.IEnumerator})"/>
  230.     public TaskBuilder AddBatch(params ITask[] tasks)
  231.     {
  232.         return AddBatch((IEnumerable<ITask>) tasks);
  233.     }
  234.  
  235.     /// <summary>
  236.     /// Set whether a frame is waited before starting the tasks.
  237.     /// </summary>
  238.     /// <param name="delay">If true, waits a frame before starting the tasks.</param>
  239.     /// <returns>Builder instance.</returns>
  240.     public TaskBuilder DelayFrame(bool delay = true)
  241.     {
  242.         _delayFrame = delay;
  243.         return this;
  244.     }
  245.  
  246.     /// <summary>
  247.     /// If true, will insert an extra frame between when one ends and the next one begins.
  248.     /// </summary>
  249.     /// <param name="delay">If true, waits a frame before starting the next task in the sequence.</param>
  250.     /// <returns>Builder instance.</returns>
  251.     public TaskBuilder DelayFrameBetween(bool delay = true)
  252.     {
  253.         _delayFrameBetween = delay;
  254.         return this;
  255.     }
  256. }
  257.  
  258. /// <summary>
  259. /// Runs a coroutine, can be paused, unpaused, and stopped.
  260. /// Event can be subscribed to which notifies of task completion.
  261. /// </summary>
  262. public class Task : ITask
  263. {
  264.     private IEnumerator _work;
  265.     private readonly Action<IEnumerator> _taskRunner;
  266.     private bool _delayFrame;
  267.  
  268.     public bool Running { get; private set; }
  269.     public bool Paused { get; private set; }
  270.     public bool Stopped { get; private set; }
  271.  
  272.     public event TaskFinishHandler OnFinish;
  273.  
  274.     public bool CanStart => !(Running || Stopped);
  275.  
  276.     public Task(IEnumerator work, Action<IEnumerator> taskRunner)
  277.     {
  278.         _work = work;
  279.         _taskRunner = taskRunner;
  280.     }
  281.  
  282.     protected Task(Action<IEnumerator> taskRunner) => _taskRunner = taskRunner;
  283.  
  284.     protected void SetTask(IEnumerator work) => _work = work;
  285.  
  286.     protected object DefaultYield => null;
  287.  
  288.     public void Start()
  289.     {
  290.         if (!CanStart) return;
  291.         Running = true;
  292.         _taskRunner(WorkWrapper());
  293.     }
  294.  
  295.     public virtual void Stop()
  296.     {
  297.         Stopped = true;
  298.         Running = false;
  299.     }
  300.  
  301.     public virtual void Pause() => Paused = true;
  302.  
  303.     public virtual void Continue() => Paused = false;
  304.  
  305.     public virtual void Complete()
  306.     {
  307.         if (!CanStart) return;
  308.         CompleteInternal();
  309.     }
  310.  
  311.     private void CompleteInternal()
  312.     {
  313.         (_work as IDisposable)?.Dispose();
  314.         var stop = Stopped;
  315.         Stopped = true;
  316.         OnFinish?.Invoke(stop);
  317.     }
  318.  
  319.     /// <summary>
  320.     /// Set if a frame is waited before the start of the coroutine.
  321.     /// </summary>
  322.     /// <param name="delay">Whether to delay a frame before starting the coroutine.</param>
  323.     public void DelayFrame(bool delay = true) => _delayFrame = delay;
  324.  
  325.     private IEnumerator WorkWrapper()
  326.     {
  327.         if (_delayFrame) yield return DefaultYield;
  328.         while (Running)
  329.         {
  330.             if (Paused) yield return DefaultYield;
  331.             else if (_work?.MoveNext() == true)
  332.             {
  333.                 yield return _work.Current;
  334.             }
  335.             else Running = false;
  336.         }
  337.         CompleteInternal();
  338.     }
  339. }
  340.  
  341. /// <summary>
  342. /// Runs many tasks at once.
  343. /// </summary>
  344. public class BatchTask : Task
  345. {
  346.     private readonly List<ITask> _tasks;
  347.     private int _working;
  348.  
  349.     public BatchTask(IEnumerable<ITask> tasks, Action<IEnumerator> taskRunner) : base(taskRunner)
  350.     {
  351.         _tasks = tasks.ToList();
  352.         SetTask(HandleTasks());
  353.         foreach (var task in _tasks)
  354.         {
  355.             task.OnFinish += OnTaskFinish;
  356.         }
  357.     }
  358.  
  359.     private void OnTaskFinish(bool manualStop)
  360.     {
  361.         _working--;
  362.     }
  363.  
  364.     public override void Stop()
  365.     {
  366.         foreach (var task in _tasks) task.Stop();
  367.     }
  368.  
  369.     public override void Pause()
  370.     {
  371.         foreach (var task in _tasks) task.Pause();
  372.     }
  373.  
  374.     public override void Continue()
  375.     {
  376.         foreach (var task in _tasks) task.Continue();
  377.     }
  378.  
  379.     public override void Complete()
  380.     {
  381.         foreach (var task in _tasks) task.Complete();
  382.         base.Complete();
  383.     }
  384.  
  385.     private IEnumerator HandleTasks()
  386.     {
  387.         foreach (var task in _tasks)
  388.         {
  389.             _working++;
  390.             task.Start();
  391.         }
  392.         while (_working > 0) yield return DefaultYield;
  393.     }
  394. }
  395.  
  396. /// <summary>
  397. /// Run one task after another.
  398. /// </summary>
  399. public class TaskSequence : Task
  400. {
  401.     private readonly List<ITask> _tasks;
  402.     private int _index;
  403.     private bool _executed;
  404.     private bool _delayFrame;
  405.  
  406.     public TaskSequence(IEnumerable<ITask> tasks, Action<IEnumerator> taskRunner) : base(taskRunner)
  407.     {
  408.         _tasks = tasks.ToList();
  409.         SetTask(HandleTasks());
  410.         foreach (var task in _tasks)
  411.         {
  412.             task.OnFinish += OnTaskFinish;
  413.         }
  414.     }
  415.  
  416.     private void OnTaskFinish(bool manualStop)
  417.     {
  418.         _index++;
  419.         _executed = false;
  420.     }
  421.  
  422.     public override void Stop()
  423.     {
  424.         base.Stop();
  425.         _tasks[_index].Stop();
  426.     }
  427.  
  428.     public override void Pause()
  429.     {
  430.         base.Pause();
  431.         _tasks[_index].Pause();
  432.     }
  433.  
  434.     public override void Continue()
  435.     {
  436.         base.Continue();
  437.         _tasks[_index].Continue();
  438.     }
  439.  
  440.     /// <summary>
  441.     /// Should an extra frame be waited before starting the next task in the sequence.
  442.     /// If false, the next task might start on the same frame the last one ends.
  443.     /// </summary>
  444.     /// <param name="delay">Whether to delay a frame between tasks.</param>
  445.     public void DelayFrameBetween(bool delay = true) => _delayFrame = delay;
  446.  
  447.     private IEnumerator HandleTasks()
  448.     {
  449.         while (_index < _tasks.Count)
  450.         {
  451.             if (_executed)
  452.             {
  453.                 yield return DefaultYield;
  454.                 continue;
  455.             }
  456.             if (_delayFrame) yield return DefaultYield;
  457.             _executed = true;
  458.             _tasks[_index].Start();
  459.             yield return DefaultYield;
  460.         }
  461.     }
  462. }
  463.  
  464. /// <summary>
  465. /// Simple tasks that executes an action.
  466. /// </summary>
  467. public class ActionTask : Task
  468. {
  469.     private readonly Action _action;
  470.  
  471.     public ActionTask(Action action, Action<IEnumerator> taskRunner) : base(taskRunner)
  472.     {
  473.         _action = action;
  474.         SetTask(RunAction());
  475.     }
  476.    
  477.     private IEnumerator RunAction()
  478.     {
  479.         _action();
  480.         yield break;
  481.     }
  482. }
  483.  
  484. /// <summary>
  485. /// Simple task that yields an object in the coroutine.
  486. /// E.g. can yield a WaitForSeconds() in a task sequence.
  487. /// </summary>
  488. public class YieldTask : Task
  489. {
  490.     private readonly object _object;
  491.    
  492.     public YieldTask(object o, Action<IEnumerator> taskRunner) : base(taskRunner)
  493.     {
  494.         _object = o;
  495.         SetTask(RunTask());
  496.     }
  497.  
  498.     private IEnumerator RunTask()
  499.     {
  500.         yield return _object;
  501.     }
  502. }
  503.  
  504. /// <summary>
  505. /// Animates a value.
  506. /// </summary>
  507. /// <typeparam name="T">Value type to animate.</typeparam>
  508. public class AnimateTask<T> : Task
  509. {
  510.     private readonly T _from;
  511.     private readonly T _to;
  512.     private readonly Action<T> _update;
  513.     private readonly float _speed;
  514.     private readonly Func<T, T, float, T> _interpolate;
  515.     private readonly Func<float, float, float, float> _ease;
  516.  
  517.     public AnimateTask(T from, T to, Action<T> update, float speed, Func<T, T, float, T> interpolate, Func<float, float, float, float> ease, Action<IEnumerator> taskRunner) : base(taskRunner)
  518.     {
  519.         _from = from;
  520.         _to = to;
  521.         _update = update;
  522.         _speed = speed;
  523.         _interpolate = interpolate;
  524.         _ease = ease;
  525.         SetTask(Animate());
  526.         OnFinish += _ => _update(_to);
  527.     }
  528.  
  529.     private IEnumerator Animate()
  530.     {
  531.         for (var t = 0f; t < 1f; t += _speed * Time.deltaTime)
  532.         {
  533.             var u = _ease(0, 1, t);
  534.             _update(_interpolate(_from, _to, u));
  535.             yield return DefaultYield;
  536.         }
  537.     }
  538. }
  539.  
  540. /// <summary>
  541. /// Wraps a function that yield a task. Does not compute the task
  542. /// to execute until it is time to start.
  543. /// </summary>
  544. public class LazyTask : ITask
  545. {
  546.     private readonly Func<ITask> _taskFunc;
  547.  
  548.     private ITask _task;
  549.  
  550.     public ITask GetTask
  551.     {
  552.         get
  553.         {
  554.             if (_task != null) return _task;
  555.             _task = _taskFunc();
  556.             _task.OnFinish += b => OnFinish?.Invoke(b);
  557.             return _task;
  558.         }
  559.     }
  560.  
  561.     public LazyTask(Func<ITask> func)
  562.     {
  563.         _taskFunc = func;
  564.     }
  565.  
  566.     public event TaskFinishHandler OnFinish;
  567.    
  568.     public void Start()
  569.     {
  570.         GetTask.Start();
  571.     }
  572.  
  573.     public void Stop()
  574.     {
  575.         GetTask.Stop();
  576.     }
  577.  
  578.     public void Pause()
  579.     {
  580.         GetTask.Pause();
  581.     }
  582.  
  583.     public void Continue()
  584.     {
  585.         GetTask.Continue();
  586.     }
  587.  
  588.     public void Complete()
  589.     {
  590.         GetTask.Complete();
  591.     }
  592. }
RAW Paste Data