diff --git a/TaskManager.cs b/TaskManager.cs index 7822ca0..72d28fe 100644 --- a/TaskManager.cs +++ b/TaskManager.cs @@ -1,191 +1,259 @@ -/// TaskManager.cs -/// -/// This is a convenient coroutine API for Unity. -/// -/// Example usage: -/// IEnumerator MyAwesomeTask() -/// { -/// while(true) { -/// // ... -/// yield return null; -//// } -/// } -/// -/// IEnumerator TaskKiller(float delay, Task t) -/// { -/// yield return new WaitForSeconds(delay); -/// t.Stop(); -/// } -/// -/// // From anywhere -/// Task my_task = new Task(MyAwesomeTask()); -/// new Task(TaskKiller(5, my_task)); -/// -/// The code above will schedule MyAwesomeTask() and keep it running -/// concurrently until either it terminates on its own, or 5 seconds elapses -/// and triggers the TaskKiller Task that was created. -/// -/// Note that to facilitate this API's behavior, a "TaskManager" GameObject is -/// created lazily on first use of the Task API and placed in the scene root -/// with the internal TaskManager component attached. All coroutine dispatch -/// for Tasks is done through this component. - -using UnityEngine; using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/// +/// Addendum to krockot's original task manager +/// Add possibility to pool a bunch of tasks together, so they can be started, stopped,... together +/// +public class TaskPool { + public delegate void AllFinishedHandler(); + + private readonly List tasks = new(); + private int finishedTaskCounter; + + public bool AllRunning => CheckIfAllAreRunning(); + public bool AllPaused => CheckIfAllArePaused(); + private bool PoolLocked { get; set; } + private bool finished = false; + + /// + /// Gets triggered when all tasks finished. + /// + public event AllFinishedHandler AllFinished; + + /// + /// Add a task to the pool + /// + /// The task, that should be added to the pool + /// + /// Indicates if a task could be added successfully to the pool. + /// If pool is already running, it gets locked and no further task can be added. + /// + public bool Add(PoolTask _task) { + if (PoolLocked) { + return false; + } + + tasks.Add(_task); + _task.Finished += ATaskFinished; + return true; + } + + /// + /// Start all tasks within the pool together + /// + public void StartAll() { + PoolLocked = true; + foreach (Task task in tasks) { + task.Start(); + } + } + + /// + /// Stop all the tasks within the pool together + /// + public void StopAll() { + foreach (Task task in tasks) { + task.Stop(); + } + } + + /// + /// Pause all the tasks in the pool + /// + public void PauseAll() { + foreach (Task task in tasks) { + task.Pause(); + } + } + /// + /// Resume all the tasks that are paused in the pool + /// + public void UnpauseAll() { + foreach (Task task in tasks) { + task.Unpause(); + } + } + + private bool CheckIfAllAreRunning() { + bool allRunning = true; + foreach (Task unused in tasks.Where(_task => !_task.Running)) { + allRunning = false; + } + + return allRunning; + } + + private bool CheckIfAllArePaused() { + bool allPaused = true; + foreach (Task unused in tasks.Where(_task => !_task.Paused)) { + allPaused = false; + } + + return allPaused; + } + + private void ATaskFinished(bool _manual) { + finishedTaskCounter++; + if (!finished && finishedTaskCounter == tasks.Count) { + finished = true; + AllFinishedHandler handler = AllFinished; + handler?.Invoke(); + } + } +} + +/// /// A Task object represents a coroutine. Tasks can be started, paused, and stopped. -/// It is an error to attempt to start a task that has been stopped or which has -/// naturally terminated. -public class Task -{ - /// Returns true if and only if the coroutine is running. Paused tasks - /// are considered to be running. - public bool Running { - get { - return task.Running; - } - } - - /// Returns true if and only if the coroutine is currently paused. - public bool Paused { - get { - return task.Paused; - } - } - - /// Delegate for termination subscribers. manual is true if and only if - /// the coroutine was stopped with an explicit call to Stop(). - public delegate void FinishedHandler(bool manual); - - /// Termination event. Triggered when the coroutine completes execution. - public event FinishedHandler Finished; - - /// Creates a new Task object for the given coroutine. - /// - /// If autoStart is true (default) the task is automatically started - /// upon construction. - public Task(IEnumerator c, bool autoStart = true) - { - task = TaskManager.CreateTask(c); - task.Finished += TaskFinished; - if(autoStart) - Start(); - } - - /// Begins execution of the coroutine - public void Start() - { - task.Start(); - } - - /// Discontinues execution of the coroutine at its next yield. - public void Stop() - { - task.Stop(); - } - - public void Pause() - { - task.Pause(); - } - - public void Unpause() - { - task.Unpause(); - } - - void TaskFinished(bool manual) - { - FinishedHandler handler = Finished; - if(handler != null) - handler(manual); - } - - TaskManager.TaskState task; +/// It is an error to attempt to start a task that has been stopped or which has naturally terminated. +/// +public class Task { + + /// + /// Delegate for termination subscribers. Manual is true if and only if + /// the coroutine was stopped with an explicit call to Stop(). + /// + public delegate void FinishedHandler(bool _manual); + + private readonly TaskManager.TaskState task; + + /// + + /// + + + /// + /// Creates a new Task object for the given coroutine. + /// If autoStart is true (default) the task is automatically started upon construction. + /// + public Task(IEnumerator _task, bool _autoStart = true) { + task = TaskManager.CreateTask(_task); + task.Finished += TaskFinished; + if (_autoStart) { + Start(); + } + } + + + /// + /// Returns true if and only if the coroutine is running. Paused tasks are considered to be running. + /// + public bool Running => task.Running; + + /// + /// Returns true if and only if the coroutine is currently paused. + /// + public bool Paused => task.Paused; + + /// + /// Termination event. Triggered when the coroutine completes execution. + /// + public event FinishedHandler Finished; + + /// + /// Begins execution of the coroutine + /// + public void Start() { + task.Start(); + } + + /// + /// Discontinues execution of the coroutine at its next yield. + /// + public void Stop() { + task.Stop(); + } + + public void Pause() { + task.Pause(); + } + + public void Unpause() { + task.Unpause(); + } + + private void TaskFinished(bool _manual) { + FinishedHandler handler = Finished; + handler?.Invoke(_manual); + } } -class TaskManager : MonoBehaviour -{ - public class TaskState - { - public bool Running { - get { - return running; - } - } - - public bool Paused { - get { - return paused; - } - } - - public delegate void FinishedHandler(bool manual); - public event FinishedHandler Finished; - - IEnumerator coroutine; - bool running; - bool paused; - bool stopped; - - public TaskState(IEnumerator c) - { - coroutine = c; - } - - public void Pause() - { - paused = true; - } - - public void Unpause() - { - paused = false; - } - - public void Start() - { - running = true; - singleton.StartCoroutine(CallWrapper()); - } - - public void Stop() - { - stopped = true; - running = false; - } - - IEnumerator CallWrapper() - { - yield return null; - IEnumerator e = coroutine; - while(running) { - if(paused) - yield return null; - else { - if(e != null && e.MoveNext()) { - yield return e.Current; - } - else { - running = false; - } - } - } - - FinishedHandler handler = Finished; - if(handler != null) - handler(stopped); - } - } - - static TaskManager singleton; - - public static TaskState CreateTask(IEnumerator coroutine) - { - if(singleton == null) { - GameObject go = new GameObject("TaskManager"); - singleton = go.AddComponent(); - } - return new TaskState(coroutine); - } +/// +/// Specialized task for pool, with no auto start +/// This way, all tasks within the pool can be started together +/// +public class PoolTask : Task { + public PoolTask(IEnumerator _task) : base(_task, false) { } } + +internal class TaskManager : MonoBehaviour { + private static TaskManager instance; + + public static TaskState CreateTask(IEnumerator _coroutine) { + if (instance != null) { + return new TaskState(_coroutine); + } + + GameObject go = new("TaskManager"); + instance = go.AddComponent(); + + return new TaskState(_coroutine); + } + + public class TaskState { + public delegate void FinishedHandler(bool _manual); + + private readonly IEnumerator coroutine; + private bool stopped; + + public TaskState(IEnumerator _task) { + coroutine = _task; + } + + public bool Running { get; private set; } + + public bool Paused { get; private set; } + + public event FinishedHandler Finished; + + public void Pause() { + Paused = true; + } + + public void Unpause() { + Paused = false; + } + + public void Start() { + Running = true; + instance.StartCoroutine(CallWrapper()); + } + + public void Stop() { + stopped = true; + Running = false; + } + + private IEnumerator CallWrapper() { + yield return null; + IEnumerator e = coroutine; + while (Running) { + if (Paused) { + yield return null; + } else { + if (e != null && e.MoveNext()) { + yield return e.Current; + } else { + Running = false; + } + } + } + + FinishedHandler handler = Finished; + handler?.Invoke(stopped); + } + } +} \ No newline at end of file