Last active
May 20, 2022 21:42
-
-
Save modyari/a2c2b44a5810e296fd8eedaec3466be1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using UnityEngine; | |
namespace RoutineUtility | |
{ | |
/// <summary> | |
/// A routine utility that tracks whether a routine is still active or not | |
/// </summary> | |
public class Routine | |
{ | |
/// <summary> | |
/// The halting type of the routine | |
/// </summary> | |
public enum RoutineHaltType | |
{ | |
Error, | |
Disabled, | |
} | |
/// <summary> | |
/// Triggers when the routine finished execution | |
/// </summary> | |
public event Action<Routine> OnRoutineFinished; | |
/// <summary> | |
/// Triggers when the routine unexpectedly halts | |
/// </summary> | |
public event Action<Routine, RoutineHaltType> OnRoutineHalted; | |
private readonly IEnumerator routineEnumerator; | |
private readonly MonoBehaviour owner; | |
private Coroutine coroutine = null; | |
/// <summary> | |
/// Indicates whether the routine is still running | |
/// </summary> | |
public bool IsRunning => coroutine != null; | |
// Private so we can only start it with the static method | |
private Routine(MonoBehaviour owner, IEnumerator routineEnumerator) | |
{ | |
this.owner = owner; | |
this.routineEnumerator = routineEnumerator; | |
} | |
/// <summary> | |
/// Creates a new routine object and starts it | |
/// </summary> | |
/// <param name="owner">The owner of the coroutine</param> | |
/// <param name="routineEnumerator">The routine enumerator</param> | |
/// <returns>The newly created Routine reference</returns> | |
public static Routine StartRoutine(MonoBehaviour owner, IEnumerator routineEnumerator) | |
{ | |
Routine routine = new Routine(owner, routineEnumerator); | |
routine.StartInternal(); | |
return routine; | |
} | |
/// <summary> | |
/// Creates a new routine object and starts it, using the RoutineManager as the owner | |
/// </summary> | |
/// <param name="routineEnumerator">The routine enumerator</param> | |
/// <returns>The newly created Routine reference</returns> | |
public static Routine StartRoutine(IEnumerator routineEnumerator) | |
{ | |
return StartRoutine(RoutineManager.Instance, routineEnumerator); | |
} | |
/// <summary> | |
/// Stops the coroutine if it's running | |
/// </summary> | |
public void StopCoroutine() | |
{ | |
if (coroutine != null) | |
{ | |
owner.StopCoroutine(coroutine); | |
coroutine = null; | |
} | |
} | |
private void StopInternal(bool reportFinished = false) | |
{ | |
StopCoroutine(); | |
if (reportFinished) | |
{ | |
OnRoutineFinished?.Invoke(this); | |
} | |
} | |
private void StartInternal() | |
{ | |
if (coroutine != null) | |
{ | |
return; | |
} | |
coroutine = owner.StartCoroutine(ProcessRoutineAsync(routineEnumerator)); | |
RoutineManager.Instance.AddTrackedRoutine(this, owner); | |
} | |
/// <summary> | |
/// Notifies the routine that it was halted, providing the halt type | |
/// </summary> | |
/// <param name="haltType">The reported halt type</param> | |
public void NotifyHalted(RoutineHaltType haltType) | |
{ | |
Debug.Log($"Routine halted. Halt type: {haltType.ToString()}"); | |
OnRoutineHalted?.Invoke(this, haltType); | |
if (coroutine != null) | |
{ | |
owner.StopCoroutine(coroutine); | |
coroutine = null; | |
} | |
} | |
private IEnumerator ProcessRoutineAsync(IEnumerator enumerator) | |
{ | |
while (true) | |
{ | |
try | |
{ | |
// No next steps, that's when we stop the current routine | |
if (!enumerator.MoveNext()) | |
{ | |
// This is the root routine? Then stop it and and report that it's finished | |
if (enumerator == routineEnumerator) | |
{ | |
StopInternal(true); | |
} | |
break; | |
} | |
} | |
// Indicate end of coroutine and fire halting due to an error event | |
catch (Exception e) | |
{ | |
Debug.LogError(e); | |
NotifyHalted(RoutineHaltType.Error); | |
break; | |
} | |
// Four possibilities here: either Current is null (yield return null in the instructions) | |
// It's a YieldInstruction (a Unity instruction like WaitUntil()) | |
// Or it's a CustomYieldInstruction, which is a custom-specified instruction (like this class)..in all cases, yield return it. | |
if (enumerator.Current is null or YieldInstruction or CustomYieldInstruction) | |
{ | |
yield return enumerator.Current; | |
continue; | |
} | |
// The fourth case is that it's an IEnumerator (yield routine SomeRoutine()), | |
// in which case we need to process its instructions within our loop | |
yield return ProcessRoutineAsync(enumerator.Current as IEnumerator); | |
} | |
} | |
#region Comparison | |
// To ensure routine == null will return true if it's not running | |
public static bool operator == (Routine a, Routine b) | |
{ | |
if (b is null) | |
{ | |
if (a is null) | |
{ | |
return true; | |
} | |
return !a.IsRunning; | |
} | |
if (a is null) | |
{ | |
return !b.IsRunning; | |
} | |
return a.Equals(b); | |
} | |
public static bool operator != (Routine a, Routine b) | |
{ | |
return !(a == b); | |
} | |
public override bool Equals(object obj) | |
{ | |
if (obj is null) | |
{ | |
return IsRunning; | |
} | |
return base.Equals(obj); | |
} | |
public override int GetHashCode() | |
{ | |
return HashCode.Combine(owner.GetHashCode(), routineEnumerator.GetHashCode(),RoutineManager.Instance.CreatedRoutineCount); | |
} | |
#endregion | |
} | |
public static class RoutineExtensions | |
{ | |
public static Routine StartRoutine(this MonoBehaviour owner, IEnumerator routineEnumerator) | |
{ | |
return Routine.StartRoutine(owner, routineEnumerator); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEngine; | |
namespace RoutineUtility | |
{ | |
/// <summary> | |
/// Manages routines created through Routine | |
/// </summary> | |
public class RoutineManager : Singleton<RoutineManager> | |
{ | |
private class RoutineInfo | |
{ | |
public Routine Routine { get; set; } | |
public MonoBehaviour Owner { get; set; } | |
} | |
private List<RoutineInfo> routineInfo = new(); | |
public int CreatedRoutineCount { get; private set; } | |
/// <summary> | |
/// Adds a routine to be tracked | |
/// </summary> | |
/// <param name="routine">The routine to track</param> | |
/// <param name="owner">Its owner</param> | |
public void AddTrackedRoutine(Routine routine, MonoBehaviour owner) | |
{ | |
CreatedRoutineCount++; | |
if (owner == this) | |
{ | |
return; | |
} | |
routineInfo.Add(new RoutineInfo(){Routine = routine, Owner = owner}); | |
} | |
private void Update() | |
{ | |
UpdateRoutines(); | |
} | |
private void UpdateRoutines() | |
{ | |
for (int i = routineInfo.Count - 1; i >= 0; i--) | |
{ | |
RoutineInfo info = routineInfo[i]; | |
if (info.Owner == null || !info.Routine.IsRunning) | |
{ | |
routineInfo.RemoveAt(i); | |
continue; | |
} | |
if (!info.Owner.isActiveAndEnabled) | |
{ | |
info.Routine.NotifyHalted(Routine.RoutineHaltType.Disabled); | |
routineInfo.RemoveAt(i); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment