StarShadow

Unity Task Handler

Mar 6th, 2021 (edited)
947
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 26.81 KB | None | 0 0
  1. /*
  2.  * Based on: https://forum.unity.com/threads/a-more-flexible-coroutine-interface.94220/
  3.  *
  4.  * This is a wrapper for Unity coroutines that adds some extra functionality.
  5.  * Wrapped coroutines are known as tasks.
  6.  *
  7.  * Features:
  8.  * - Pause/Unpause a task while it executes.
  9.  * - Callback event that occurs when a task is finished.
  10.  * - Creating a sequence of tasks that run one after another.
  11.  * - Creating a batch of tasks that run in parallel.
  12.  * -   = Pausing affects all tasks in the batch.
  13.  * - Animation task to go from one value to another.
  14.  */
  15.  
  16. using System;
  17. using System.Collections;
  18. using System.Collections.Generic;
  19. using System.Linq;
  20. using UnityEngine;
  21.  
  22. public class Tasks : MonoBehaviour
  23. {
  24.     private static Tasks _instance;
  25.  
  26.     private static Tasks GetInstance()
  27.     {
  28.         if (_instance) return _instance;
  29.         var g = new GameObject("Tasks");
  30.         _instance = g.AddComponent<Tasks>();
  31.         return _instance;
  32.     }
  33.  
  34.     /// <summary>
  35.     /// Get an action where tasks can be submitted. Tasks given to this action will
  36.     /// be executed by the main task GameObject.
  37.     /// </summary>
  38.     /// <returns>Task action.</returns>
  39.     public static Action<IEnumerator> TaskRunner()
  40.     {
  41.         return work => GetInstance().StartCoroutine(work);
  42.     }
  43.  
  44.     /// <summary>
  45.     /// Get a task for a coroutine.
  46.     /// </summary>
  47.     /// <param name="task">A coroutine.</param>
  48.     /// <returns>New task.</returns>
  49.     public static Task Wrap(IEnumerator task)
  50.     {
  51.         return new Task(task, TaskRunner());
  52.     }
  53.  
  54.     /// <summary>
  55.     /// Get a task that runs the coroutines in parallel.
  56.     /// </summary>
  57.     /// <param name="work">Coroutines.</param>
  58.     /// <returns>Batch task.</returns>
  59.     public static BatchTask Batch(IEnumerable<IEnumerator> work)
  60.     {
  61.         return Batch(work.Select(Wrap));
  62.     }
  63.  
  64.     /// <summary>
  65.     /// Get a task that runs multiple tasks in parallel.
  66.     /// </summary>
  67.     /// <param name="tasks">Tasks.</param>
  68.     /// <returns>Batch task.</returns>
  69.     public static BatchTask Batch(IEnumerable<ITask> tasks)
  70.     {
  71.         return new BatchTask(tasks, TaskRunner());
  72.     }
  73.  
  74.     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 = null)
  75.     {
  76.         return new AnimateTask<T>(from, to, update, speed, interpolate, TaskRunner()).WithEase(ease);
  77.     }
  78.  
  79.     /// <summary>
  80.     /// Create a task builder.
  81.     /// </summary>
  82.     /// <returns>New task builder.</returns>
  83.     public static TaskBuilder Create() => new TaskBuilder();
  84.  
  85.     /// <summary>
  86.     /// Create an animation builder.
  87.     /// </summary>
  88.     /// <returns>New animation builder.</returns>
  89.     public static AnimationBuilder CreateAnimation() => new AnimationBuilder();
  90. }
  91.  
  92. /// <summary>
  93. /// Event that is triggered when a task finishes.
  94. /// Argument specifies if .Stop() was called to end the task.
  95. /// </summary>
  96. public delegate void TaskFinishHandler(bool manualStop);
  97.  
  98. public interface ITask
  99. {
  100.     /// <summary>
  101.     /// Event which is called when the task completes.
  102.     /// </summary>
  103.     event TaskFinishHandler OnFinish;
  104.    
  105.     bool Running { get; }
  106.    
  107.     bool Paused { get; }
  108.    
  109.     bool Stopped { get; }
  110.  
  111.     /// <summary>
  112.     /// Start the task.
  113.     /// Behavior is undefined if called more than once.
  114.     /// </summary>
  115.     void Start();
  116.  
  117.     /// <summary>
  118.     /// Stop the task. Execution stops the next time the coroutine yields.
  119.     /// </summary>
  120.     void Stop();
  121.  
  122.     /// <summary>
  123.     /// Pause the task. Execution stops the next time the coroutine yields.
  124.     /// </summary>
  125.     void Pause();
  126.  
  127.     /// <summary>
  128.     /// Unpause the task. Execution continues on the next frame.
  129.     /// </summary>
  130.     void Continue();
  131.  
  132.     /// <summary>
  133.     /// If the task has not run yet, complete this task instantly.
  134.     /// This does not execute the work of the task, but executes
  135.     /// the OnFinish event.
  136.     /// </summary>
  137.     void Complete();
  138. }
  139.  
  140. /// <summary>
  141. /// Represents a task that can be started again once it has finished.
  142. /// </summary>
  143. public interface IMultiRunTask
  144. {
  145.     /// <summary>
  146.     /// Reset a task that has completed so it can run again.
  147.     /// </summary>
  148.     void Reset();
  149. }
  150.  
  151. public static class TaskExtensions
  152. {
  153.     /// <summary>
  154.     /// Toggle playing/pausing a task.
  155.     /// </summary>
  156.     public static void TogglePlay(this ITask task)
  157.     {
  158.         if (task.Paused) task.Continue();
  159.         else task.Pause();
  160.     }
  161. }
  162.  
  163. /// <summary>
  164. /// Build a sequence of tasks.
  165. /// </summary>
  166. public class TaskBuilder
  167. {
  168.     private List<ITask> _tasks = new List<ITask>();
  169.     private bool _delayFrame;
  170.     private bool _delayFrameBetween;
  171.  
  172.     /// <summary>
  173.     /// Get a task representing all the tasks in the builder.
  174.     /// The tasks will run in a sequence.
  175.     /// </summary>
  176.     /// <returns>Built task.</returns>
  177.     public ITask Build()
  178.     {
  179.         var seq = new TaskSequence(_tasks, Tasks.TaskRunner());
  180.         seq.DelayFrame(_delayFrame);
  181.         seq.DelayFrameBetween(_delayFrameBetween);
  182.         return seq;
  183.     }
  184.  
  185.     /// <summary>
  186.     /// Add a coroutine to the sequence.
  187.     /// </summary>
  188.     /// <param name="task">Coroutine to add.</param>
  189.     /// <returns>Builder instance.</returns>
  190.     public TaskBuilder Add(IEnumerator task)
  191.     {
  192.         _tasks.Add(Tasks.Wrap(task));
  193.         return this;
  194.     }
  195.  
  196.     /// <summary>
  197.     /// Add a task to the sequence.
  198.     /// </summary>
  199.     /// <param name="task"></param>
  200.     /// <returns>Builder instance.</returns>
  201.     public TaskBuilder Add(ITask task)
  202.     {
  203.         _tasks.Add(task);
  204.         return this;
  205.     }
  206.  
  207.     /// <summary>
  208.     /// Add an action to the task sequence.
  209.     /// </summary>
  210.     /// <param name="action">Action to run.</param>
  211.     /// <returns>Builder instance.</returns>
  212.     public TaskBuilder Add(Action action)
  213.     {
  214.         _tasks.Add(new ActionTask(action, Tasks.TaskRunner()));
  215.         return this;
  216.     }
  217.  
  218.     /// <summary>
  219.     /// Add an object to the task sequence that will be yielded while
  220.     /// running the internal coroutine.
  221.     /// </summary>
  222.     /// <param name="o">Object to yield.</param>
  223.     /// <returns>Builder instance.</returns>
  224.     public TaskBuilder Add(object o)
  225.     {
  226.         _tasks.Add(new SingleActionTask(o, Tasks.TaskRunner()));
  227.         return this;
  228.     }
  229.  
  230.     /// <summary>
  231.     /// Add a batch of tasks that will execute in parallel.
  232.     /// The next task in the sequence will not begin until every task
  233.     /// in the batch is complete.
  234.     /// </summary>
  235.     /// <param name="tasks">Tasks to add.</param>
  236.     /// <returns>Builder instance.</returns>
  237.     public TaskBuilder AddBatch(IEnumerable<IEnumerator> tasks)
  238.     {
  239.         _tasks.Add(Tasks.Batch(tasks));
  240.         return this;
  241.     }
  242.  
  243.     /// <inheritdoc cref="AddBatch(System.Collections.Generic.IEnumerable{System.Collections.IEnumerator})"/>
  244.     public TaskBuilder AddBatch(IEnumerable<ITask> tasks)
  245.     {
  246.         _tasks.Add(Tasks.Batch(tasks));
  247.         return this;
  248.     }
  249.    
  250.     /// <summary>
  251.     /// Add a batch of tasks given a collection and function to convert each
  252.     /// to a coroutine.
  253.     /// </summary>
  254.     /// <returns>Builder instance.</returns>
  255.     public TaskBuilder AddBatch<T>(IEnumerable<T> ie, Func<T, IEnumerator> workFunc)
  256.     {
  257.         return AddBatch(ie.Select(workFunc));
  258.     }
  259.    
  260.     /// <summary>
  261.     /// Add a batch of tasks given a collection and function to convert each
  262.     /// to a task.
  263.     /// </summary>
  264.     /// <returns>Builder instance.</returns>
  265.     public TaskBuilder AddBatch<T>(IEnumerable<T> ie, Func<T, ITask> workFunc)
  266.     {
  267.         return AddBatch(ie.Select(workFunc));
  268.     }
  269.  
  270.     /// <inheritdoc cref="AddBatch(System.Collections.Generic.IEnumerable{System.Collections.IEnumerator})"/>
  271.     public TaskBuilder AddBatch(params ITask[] tasks)
  272.     {
  273.         return AddBatch((IEnumerable<ITask>) tasks);
  274.     }
  275.  
  276.     /// <summary>
  277.     /// Set whether the delay frame is enabled.
  278.     /// If true, then the tasks will begin on the next frame instead
  279.     /// of the first frame they are started.
  280.     /// </summary>
  281.     /// <param name="delay"></param>
  282.     /// <returns></returns>
  283.     public TaskBuilder DelayFrame(bool delay = true)
  284.     {
  285.         _delayFrame = delay;
  286.         return this;
  287.     }
  288.  
  289.     /// <summary>
  290.     /// Set whether there is a delay frame between each task in the sequence.
  291.     /// If true, then one frame will be skipped before starting the
  292.     /// next task in the sequence.
  293.     /// </summary>
  294.     /// <param name="delay">Whether to enable the delay frame.</param>
  295.     /// <returns>Builder instance.</returns>
  296.     public TaskBuilder DelayFrameBetween(bool delay = true)
  297.     {
  298.         _delayFrameBetween = delay;
  299.         return this;
  300.     }
  301. }
  302.  
  303. /// <summary>
  304. /// Base class which runs a coroutine.
  305. ///
  306. /// Can be paused, unpaused, and stopped.
  307. /// Event can be subscribed to which notifies of task completion.
  308. /// </summary>
  309. public class Task : ITask
  310. {
  311.     private IEnumerator _work;
  312.     private Action<IEnumerator> _taskRunner;
  313.     private bool _delayFrame;
  314.  
  315.     /// <summary>Whether the task is currently executing.</summary>
  316.     public bool Running { get; private set; }
  317.     /// <summary>Whether the task is currently paused.</summary>
  318.     public bool Paused { get; private set; }
  319.     /// <summary>Whether the task has completed execution.</summary>
  320.     public bool Stopped { get; protected set; }
  321.  
  322.     /// <summary>Event which is fired when the task completes.</summary>
  323.     public event TaskFinishHandler OnFinish;
  324.  
  325.     /// <summary>Whether the task is in a state where it can be started.</summary>
  326.     public bool CanStart => !(Running || Stopped);
  327.  
  328.     public Task(IEnumerator work, Action<IEnumerator> taskRunner)
  329.     {
  330.         _work = work;
  331.         _taskRunner = taskRunner;
  332.     }
  333.  
  334.     protected Task(Action<IEnumerator> taskRunner) => _taskRunner = taskRunner;
  335.  
  336.     /// <summary>Called by a subclass to set the work function for this task.</summary>
  337.     protected void SetTask(IEnumerator work) => _work = work;
  338.  
  339.     public void Start()
  340.     {
  341.         if (!CanStart) return;
  342.         Running = true;
  343.         _taskRunner(WorkWrapper());
  344.     }
  345.  
  346.     public virtual void Stop()
  347.     {
  348.         Stopped = true;
  349.         Running = false;
  350.     }
  351.  
  352.     public virtual void Pause() => Paused = true;
  353.  
  354.     public virtual void Continue() => Paused = false;
  355.  
  356.     public virtual void Complete()
  357.     {
  358.         if (!CanStart) return;
  359.         CompleteInternal();
  360.     }
  361.  
  362.     private void CompleteInternal()
  363.     {
  364.         (_work as IDisposable)?.Dispose();
  365.         var stop = Stopped;
  366.         Stopped = true;
  367.         OnFinish?.Invoke(stop);
  368.     }
  369.  
  370.     /// <summary>
  371.     /// Set if a frame is skipped before the start of the coroutine.
  372.     /// </summary>
  373.     /// <param name="delay">Whether to delay a frame before starting the coroutine.</param>
  374.     public void DelayFrame(bool delay = true) => _delayFrame = delay;
  375.  
  376.     private IEnumerator WorkWrapper()
  377.     {
  378.         if (_delayFrame) yield return null;
  379.         while (Running)
  380.         {
  381.             if (Paused) yield return null;
  382.             else if (_work?.MoveNext() == true)
  383.             {
  384.                 yield return _work.Current;
  385.             }
  386.             else Running = false;
  387.         }
  388.         CompleteInternal();
  389.     }
  390. }
  391.  
  392. /// <summary>
  393. /// Runs many tasks at once.
  394. /// </summary>
  395. public class BatchTask : Task
  396. {
  397.     private List<ITask> _tasks;
  398.     private int _working;
  399.  
  400.     public BatchTask(IEnumerable<ITask> tasks, Action<IEnumerator> taskRunner) : base(taskRunner)
  401.     {
  402.         _tasks = tasks.ToList();
  403.         SetTask(HandleTasks());
  404.         foreach (var task in _tasks)
  405.         {
  406.             task.OnFinish += OnTaskFinish;
  407.         }
  408.     }
  409.  
  410.     // Called for each task in the batch.
  411.     private void OnTaskFinish(bool manualStop)
  412.     {
  413.         _working--;
  414.     }
  415.  
  416.     public override void Stop()
  417.     {
  418.         foreach (var task in _tasks) task.Stop();
  419.     }
  420.  
  421.     public override void Pause()
  422.     {
  423.         foreach (var task in _tasks) task.Pause();
  424.     }
  425.  
  426.     public override void Continue()
  427.     {
  428.         foreach (var task in _tasks) task.Continue();
  429.     }
  430.  
  431.     private IEnumerator HandleTasks()
  432.     {
  433.         foreach (var task in _tasks)
  434.         {
  435.             _working++;
  436.             task.Start();
  437.         }
  438.         while (_working > 0) yield return null;
  439.     }
  440. }
  441.  
  442. /// <summary>
  443. /// Run one task after another.
  444. /// </summary>
  445. public class TaskSequence : Task
  446. {
  447.     private List<ITask> _tasks;
  448.     private int _index;
  449.     private bool _executed;
  450.     private bool _delayFrame;
  451.  
  452.     public TaskSequence(IEnumerable<ITask> tasks, Action<IEnumerator> taskRunner) : base(taskRunner)
  453.     {
  454.         _tasks = tasks.ToList();
  455.         SetTask(HandleTasks());
  456.         foreach (var task in _tasks)
  457.         {
  458.             task.OnFinish += OnTaskFinish;
  459.         }
  460.     }
  461.  
  462.     // Called for each task in the sequence.
  463.     private void OnTaskFinish(bool manualStop)
  464.     {
  465.         _index++;
  466.         _executed = false;
  467.     }
  468.  
  469.     public override void Stop()
  470.     {
  471.         base.Stop();
  472.         _tasks[_index].Stop();
  473.     }
  474.  
  475.     public override void Pause()
  476.     {
  477.         base.Pause();
  478.         _tasks[_index].Pause();
  479.     }
  480.  
  481.     public override void Continue()
  482.     {
  483.         base.Continue();
  484.         _tasks[_index].Continue();
  485.     }
  486.  
  487.     /// <summary>
  488.     /// Should an extra frame be waited before starting the next task in the sequence.
  489.     /// If false, the next task might start on the same frame the last one ends.
  490.     /// </summary>
  491.     /// <param name="delay">Whether to delay a frame between tasks.</param>
  492.     public void DelayFrameBetween(bool delay = true) => _delayFrame = delay;
  493.  
  494.     private IEnumerator HandleTasks()
  495.     {
  496.         while (_index < _tasks.Count)
  497.         {
  498.             if (_executed)
  499.             {
  500.                 // Wait because one of the tasks is currently running.
  501.                 yield return null;
  502.                 continue;
  503.             }
  504.             if (_delayFrame) yield return null;
  505.             _executed = true;
  506.             _tasks[_index].Start();
  507.             yield return null;
  508.         }
  509.     }
  510. }
  511.  
  512. /// <summary>
  513. /// Task that executes an action and completes immediately.
  514. /// </summary>
  515. public class ActionTask : Task, IMultiRunTask
  516. {
  517.     private readonly Action _action;
  518.  
  519.     public ActionTask(Action action, Action<IEnumerator> taskRunner) : base(taskRunner)
  520.     {
  521.         _action = action;
  522.         SetTask(RunAction());
  523.     }
  524.  
  525.     public void Reset()
  526.     {
  527.         if (Stopped)
  528.         {
  529.             Stopped = false;
  530.             SetTask(RunAction());
  531.         }
  532.     }
  533.  
  534.     private IEnumerator RunAction()
  535.     {
  536.         _action();
  537.         yield break;
  538.     }
  539. }
  540.  
  541. /// <summary>
  542. /// Task that yields an object to the internal coroutine.
  543. /// </summary>
  544. public class SingleActionTask : Task, IMultiRunTask
  545. {
  546.     private readonly object _object;
  547.    
  548.     public SingleActionTask(object o, Action<IEnumerator> taskRunner) : base(taskRunner)
  549.     {
  550.         _object = o;
  551.         SetTask(RunTask());
  552.     }
  553.  
  554.     public void Reset()
  555.     {
  556.         if (Stopped)
  557.         {
  558.             Stopped = false;
  559.             SetTask(RunTask());
  560.         }
  561.     }
  562.  
  563.     private IEnumerator RunTask()
  564.     {
  565.         yield return _object;
  566.     }
  567. }
  568.  
  569. public interface IAnimationTask : ITask, IMultiRunTask
  570. {
  571.     bool Reverse { get; set; }
  572. }
  573.  
  574. /// <summary>
  575. /// Class for building animation sequences.
  576. /// </summary>
  577. public class AnimationBuilder
  578. {
  579.     private List<IAnimationTask> _parts = new List<IAnimationTask>();
  580.  
  581.     /// <summary>
  582.     /// Get the animation representing this sequence of animations.
  583.     /// </summary>
  584.     /// <returns>Full animation.</returns>
  585.     public AnimationSequence Build()
  586.     {
  587.         return new AnimationSequence(_parts, Tasks.TaskRunner());
  588.     }
  589.  
  590.     /// <summary>
  591.     /// Add an animation to the sequence.
  592.     /// </summary>
  593.     /// <returns>Animation builder.</returns>
  594.     public AnimationBuilder Add(IAnimationTask animation)
  595.     {
  596.         _parts.Add(animation);
  597.         return this;
  598.     }
  599.  
  600.     /// <summary>
  601.     /// Append animations to the end of the sequence.
  602.     /// </summary>
  603.     /// <returns>Animation builder.</returns>
  604.     public AnimationBuilder Add(IEnumerable<IAnimationTask> animations)
  605.     {
  606.         _parts.AddRange(animations);
  607.         return this;
  608.     }
  609.  
  610.     /// <summary>
  611.     /// Add a set of animations to the end of the sequence that
  612.     /// will execute in parallel. The next animation in the sequence
  613.     /// will begin on the frame after all animations in the batch
  614.     /// have completed.
  615.     /// </summary>
  616.     /// <returns>Animation builder.</returns>
  617.     public AnimationBuilder AddBatch(List<IAnimationTask> animations)
  618.     {
  619.         if (animations.Count == 0) return this;
  620.         return Add(new AnimationBatch(animations, Tasks.TaskRunner()));
  621.     }
  622.  
  623.     /// <inheritdoc cref="AddBatch(System.Collections.Generic.List{IAnimationTask})"/>
  624.     public AnimationBuilder AddBatch(IEnumerable<IAnimationTask> animations)
  625.     {
  626.         return AddBatch(animations.ToList());
  627.     }
  628.  
  629.     /// <inheritdoc cref="AddBatch(System.Collections.Generic.List{IAnimationTask})"/>
  630.     public AnimationBuilder AddBatch(params IAnimationTask[] animations)
  631.     {
  632.         return AddBatch(animations.ToList());
  633.     }
  634. }
  635.  
  636. /// <summary>
  637. /// Sequence of animations. The next animation will begin to play on the
  638. /// frame after the current one ends.
  639. /// </summary>
  640. public class AnimationSequence : Task, IAnimationTask
  641. {
  642.     private readonly List<IAnimationTask> _animations;
  643.     private bool _executing;
  644.     private IAnimationTask _currentAnimation;
  645.  
  646.     public bool Reverse { get; set; }
  647.     public ILoopControl LoopControl { get; set; }
  648.  
  649.     public AnimationSequence(List<IAnimationTask> animations, Action<IEnumerator> taskRunner) : base(taskRunner)
  650.     {
  651.         _animations = animations;
  652.         SetTask(Animate());
  653.     }
  654.  
  655.     private void AnimationFinished(bool manualStop)
  656.     {
  657.         _executing = false;
  658.     }
  659.  
  660.     public override void Stop()
  661.     {
  662.         base.Stop();
  663.         _currentAnimation?.Stop();
  664.     }
  665.  
  666.     public override void Pause()
  667.     {
  668.         base.Pause();
  669.         _currentAnimation?.Pause();
  670.     }
  671.  
  672.     public override void Continue()
  673.     {
  674.         base.Continue();
  675.         _currentAnimation?.Continue();
  676.     }
  677.  
  678.     public void Reset()
  679.     {
  680.         if (Stopped)
  681.         {
  682.             Stopped = false;
  683.             SetTask(Animate());
  684.         }
  685.     }
  686.  
  687.     private IEnumerator Animate()
  688.     {
  689.         while (true)
  690.         {
  691.             var reverse = Reverse;
  692.             var animations = _animations;
  693.             for (var i = 0; i < animations.Count; i++)
  694.             {
  695.                 var index = Reverse ? animations.Count - 1 - i : i;
  696.                 var animation = animations[index];
  697.                 var reversed = animation.Reverse;
  698.                 if (reverse) animation.Reverse = !reversed;
  699.  
  700.                 animation.OnFinish += AnimationFinished;
  701.                 animation.Reset();
  702.                 _currentAnimation = animation;
  703.                 animation.Start();
  704.                 _executing = true;
  705.                 while (_executing) yield return null;
  706.                 _currentAnimation = null;
  707.                 animation.OnFinish -= AnimationFinished;
  708.  
  709.                 animation.Reverse = reversed;
  710.             }
  711.             if (LoopControl?.TryLoop(this) != true) yield break;
  712.         }
  713.     }
  714. }
  715.  
  716. /// <summary>
  717. /// Execute multiple animations at the same time.
  718. /// This task completes on the frame after all contained animations have completed.
  719. ///
  720. /// All animations are started at the same time, even when reversed. Which could
  721. /// cause strange behavior if animations have different lengths and are played
  722. /// in reverse.
  723. /// </summary>
  724. public class AnimationBatch : Task, IAnimationTask
  725. {
  726.     private readonly List<IAnimationTask> _animations;
  727.     private int _running;
  728.  
  729.     public bool Reverse { get; set; }
  730.     public ILoopControl LoopControl { get; set; }
  731.  
  732.     public AnimationBatch(List<IAnimationTask> animations, Action<IEnumerator> taskRunner) : base(taskRunner)
  733.     {
  734.         _animations = animations;
  735.         SetTask(Animate());
  736.     }
  737.  
  738.     private void AnimationFinished(bool manualStop)
  739.     {
  740.         _running--;
  741.     }
  742.  
  743.     public void Reset()
  744.     {
  745.         if (Stopped)
  746.         {
  747.             Stopped = false;
  748.             SetTask(Animate());
  749.         }
  750.     }
  751.  
  752.     private IEnumerator Animate()
  753.     {
  754.         var reverse = Reverse;
  755.         var reversed = new bool[_animations.Count];
  756.         _running = _animations.Count;
  757.         for (var i = 0; i < _animations.Count; i++)
  758.         {
  759.             var animation = _animations[i];
  760.             if (reverse)
  761.             {
  762.                 reversed[i] = animation.Reverse;
  763.                 animation.Reverse = !animation.Reverse;
  764.             }
  765.             animation.OnFinish += AnimationFinished;
  766.             animation.Reset();
  767.             animation.Start();
  768.         }
  769.         while (_running > 0) yield return null;
  770.         for (var i = 0; i < _animations.Count; i++)
  771.         {
  772.             var animation = _animations[i];
  773.             if (reverse) animation.Reverse = reversed[i];
  774.             animation.OnFinish -= AnimationFinished;
  775.         }
  776.     }
  777. }
  778.  
  779. /// <summary>
  780. /// Task that executes an animation from one value to another.
  781. ///
  782. /// Required values:
  783. /// - Start and ending value
  784. /// - Function that uses the interpolated value
  785. /// - Speed of the animation (in seconds)
  786. /// -
  787. /// </summary>
  788. /// <typeparam name="T"></typeparam>
  789. public class AnimateTask<T> : Task, IAnimationTask
  790. {
  791.     public T From { get; set; }
  792.     public T To { get; set; }
  793.     public Action<T> Update { get; set; }
  794.     public float Speed { get; set; }
  795.     public Func<T, T, float, T> Interpolate { get; set; }
  796.     public Func<float, float, float, float> Ease { get; set; }
  797.     public bool Reverse { get; set; }
  798.     public float Delay { get; set; }
  799.  
  800.     public ILoopControl<AnimateTask<T>> LoopControl { get; set; }
  801.  
  802.     /// <summary>
  803.     /// Create an animation task.
  804.     /// </summary>
  805.     /// <param name="from">Starting value</param>
  806.     /// <param name="to">Ending value</param>
  807.     /// <param name="update">Function that uses the interpolated value</param>
  808.     /// <param name="speed">Animation speed (in seconds)</param>
  809.     /// <param name="interpolate">Function to interpolate between the start and end (this should be a linear interpolation).</param>
  810.     /// <param name="taskRunner">Task runner.</param>
  811.     public AnimateTask(T from, T to, Action<T> update, float speed, Func<T, T, float, T> interpolate, Action<IEnumerator> taskRunner) : base(taskRunner)
  812.     {
  813.         From = from;
  814.         To = to;
  815.         Update = update;
  816.         Speed = speed;
  817.         Interpolate = interpolate;
  818.         Ease = (a, b, t) => t;
  819.         SetTask(Animate());
  820.         OnFinish += _ => { Update(Reverse ? From : To); };
  821.     }
  822.  
  823.     /// <summary>
  824.     /// Set the loop controller for this animation.
  825.     /// </summary>
  826.     /// <param name="loopControl">Loop controller.</param>
  827.     /// <returns>Current task.</returns>
  828.     public AnimateTask<T> WithLoopControl(ILoopControl<AnimateTask<T>> loopControl)
  829.     {
  830.         LoopControl = loopControl;
  831.         return this;
  832.     }
  833.  
  834.     /// <summary>
  835.     /// Set the easing function that is used when animating forwards.
  836.     /// </summary>
  837.     /// <param name="easeFunction">Easing function.</param>
  838.     /// <returns>Current task.</returns>
  839.     public AnimateTask<T> WithEase(Func<float, float, float, float> easeFunction)
  840.     {
  841.         Ease = easeFunction ?? Ease;
  842.         return this;
  843.     }
  844.  
  845.     /// <inheritdoc cref="WithEase(System.Func{float,float,float,float})"/>
  846.     public AnimateTask<T> WithEase(Func<float, float> easeFunction)
  847.     {
  848.         return WithEase((a, b, t) => easeFunction(t));
  849.     }
  850.  
  851.     /// <summary>
  852.     /// If true, the animation will animate from the final value to the initial value.
  853.     /// </summary>
  854.     /// <param name="reverse">Whether to reverse the animation.</param>
  855.     /// <returns>Current task.</returns>
  856.     public AnimateTask<T> Reversed(bool reverse = true)
  857.     {
  858.         Reverse = reverse;
  859.         return this;
  860.     }
  861.  
  862.     /// <summary>
  863.     /// Set the delay in seconds that occurs before the animation.
  864.     /// This delay occurs before each playback of the animation, and occurs after
  865.     /// the animation when played in reverse.
  866.     /// </summary>
  867.     /// <param name="delay">Delay time in seconds.</param>
  868.     /// <returns>Current task.</returns>
  869.     public AnimateTask<T> WithDelay(float delay)
  870.     {
  871.         Delay = delay;
  872.         return this;
  873.     }
  874.  
  875.     void IMultiRunTask.Reset()
  876.     {
  877.         if (Stopped)
  878.         {
  879.             Stopped = false;
  880.             SetTask(Animate());
  881.         }
  882.     }
  883.  
  884.     private IEnumerator Animate()
  885.     {
  886.         var t = 0f;
  887.         while (true)
  888.         {
  889.             var speed = Speed;
  890.             var ease = Ease;
  891.             var update = Update;
  892.             var interpolate = Interpolate;
  893.             var from = From;
  894.             var to = To;
  895.             var reverse = Reverse;
  896.             var delay = Delay;
  897.             if (reverse) (from, to) = (to, from);
  898.  
  899.             if (!reverse && delay > 0)
  900.             {
  901.                 while (t < delay)
  902.                 {
  903.                     yield return null;
  904.                     t += Time.deltaTime;
  905.                 }
  906.                 t -= delay;
  907.             }
  908.            
  909.             for (; t < 1f; t += speed * Time.deltaTime)
  910.             {
  911.                 var u = ease(0, 1, t);
  912.                 update(interpolate(from, to, u));
  913.                 yield return null;
  914.             }
  915.             t -= 1f;
  916.  
  917.             if (reverse && delay > 0)
  918.             {
  919.                 while (t < delay)
  920.                 {
  921.                     yield return null;
  922.                     t += Time.deltaTime;
  923.                 }
  924.                 t -= delay;
  925.             }
  926.            
  927.             if (LoopControl?.TryLoop(this) != true) yield break;
  928.         }
  929.     }
  930. }
  931.  
  932. public interface ILoopControl<in TAnimation>
  933. {
  934.     /// <summary>
  935.     /// Update the animation and return whether the animation should play again.
  936.     /// </summary>
  937.     /// <param name="animation">Current animation.</param>
  938.     /// <returns>True if the animation should play again, false otherwise.</returns>
  939.     bool TryLoop(TAnimation animation);
  940. }
  941.  
  942. public interface ILoopControl : ILoopControl<IAnimationTask> { }
  943.  
  944. public class LoopForever : ILoopControl
  945. {
  946.     public bool TryLoop(IAnimationTask task) => true;
  947. }
  948.  
  949. public class LoopReverse : ILoopControl
  950. {
  951.     public bool TryLoop(IAnimationTask task)
  952.     {
  953.         task.Reverse = !task.Reverse;
  954.         return true;
  955.     }
  956. }
Add Comment
Please, Sign In to add comment