diff --git a/Internal/Pool.cs b/Internal/Pool.cs index 64427bf..dd3ff44 100644 --- a/Internal/Pool.cs +++ b/Internal/Pool.cs @@ -1,98 +1,98 @@ -//MIT License -// -//Copyright (c) 2018 Tom Blind -// -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. - -using System.Collections.Generic; -using System; - -namespace AsyncRoutines.Internal -{ - public class Pool where T : class, new() - { - private readonly Stack pool = new Stack(); - private int liveCount = 0; - - public T Get() - { - ++liveCount; - return (pool.Count > 0) ? (pool.Pop() as T) : new T(); - } - - public void Release(T obj) - { - --liveCount; - pool.Push(obj); - } - - public void Clear() - { - pool.Clear(); - } - - public string Report() { return string.Format("{0}/{1}", liveCount, pool.Count); } - } - - public class TypedPool - { - private readonly Dictionary> pools = new Dictionary>(); - private int liveCount = 0; - - public T Get() where T : class, I, new() - { - ++liveCount; - var pool = GetPool(typeof(T)); - return (pool.Count > 0) ? (pool.Pop() as T) : new T(); - } - - public void Release(I obj) - { - --liveCount; - var pool = GetPool(obj.GetType()); - pool.Push(obj); - } - - public void Clear() - { - pools.Clear(); - } - - public string Report() - { - var c = 0; - foreach (var t in pools.Values) - { - c += t.Count; - } - return string.Format("{0}/{1}", liveCount, c); - } - - private Stack GetPool(Type type) - { - if (!pools.ContainsKey(type)) - { - var pool = new Stack(); - pools[type] = pool; - return pool; - } - return pools[type]; - } - } -} +//MIT License +// +//Copyright (c) 2018 Tom Blind +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +using System.Collections.Generic; +using System; + +namespace AsyncRoutines.Internal +{ + public class Pool where T : class, new() + { + private readonly Stack pool = new Stack(); + private int liveCount = 0; + + public T Get() + { + ++liveCount; + return (pool.Count > 0) ? (pool.Pop() as T) : new T(); + } + + public void Release(T obj) + { + --liveCount; + pool.Push(obj); + } + + public void Clear() + { + pool.Clear(); + } + + public string Report() { return string.Format("{0}/{1}", liveCount, pool.Count); } + } + + public class TypedPool + { + private readonly Dictionary> pools = new Dictionary>(); + private int liveCount = 0; + + public T Get() where T : class, I, new() + { + ++liveCount; + var pool = GetPool(typeof(T)); + return (pool.Count > 0) ? (pool.Pop() as T) : new T(); + } + + public void Release(I obj) + { + --liveCount; + var pool = GetPool(obj.GetType()); + pool.Push(obj); + } + + public void Clear() + { + pools.Clear(); + } + + public string Report() + { + var c = 0; + foreach (var t in pools.Values) + { + c += t.Count; + } + return string.Format("{0}/{1}", liveCount, c); + } + + private Stack GetPool(Type type) + { + if (!pools.ContainsKey(type)) + { + var pool = new Stack(); + pools[type] = pool; + return pool; + } + return pools[type]; + } + } +} diff --git a/Routine.cs b/Routine.cs index d1d27e5..016ecc4 100644 --- a/Routine.cs +++ b/Routine.cs @@ -23,9 +23,12 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System; +using System.Diagnostics; +using System.Runtime.ExceptionServices; using UnityEngine; using UnityEngine.Assertions; using AsyncRoutines.Internal; +using UnityEngine.ResourceManagement.AsyncOperations; //This needs to be explicitly defined for the compiler namespace System.Runtime.CompilerServices @@ -43,7 +46,7 @@ public AsyncMethodBuilderAttribute(Type builderType) namespace AsyncRoutines { -#if DEBUG +#if DEBUG_ROUTINES public class RoutineException : Exception { public RoutineException(string message, Exception innerException) : base(message, innerException) {} @@ -53,8 +56,15 @@ public RoutineException(string message, Exception innerException) : base(message //Routines do triple duty as task-likes, task-builders, and awaiters in order to keep internal access/pooling easy public abstract class RoutineBase : INotifyCompletion { + public RoutineManager Manager => manager; + /// Enable stack tracing for debugging. Off by default due to performance implications. public static bool TracingEnabled { get; set; } +#if DEBUG_ROUTINES + = true; +#else + = false; +#endif /// The running instance id of the routine. If the routine is stopped, will return zero. public UInt64 Id { get { return id; } } @@ -65,7 +75,7 @@ public abstract class RoutineBase : INotifyCompletion /// Internal use only. Required for awaiter. public bool IsCompleted { get { return state == State.Finished; } } -#if DEBUG +#if DEBUG_ROUTINES /// The current async/await stack trace for this routine public string StackTrace { @@ -79,16 +89,16 @@ string formatFrame(System.Diagnostics.StackFrame frame) } var filePath = frame.GetFileName(); - if (filePath != null) + if (filePath != null) { filePath = filePath.Replace("\\", "/"); var assetsIndex = filePath.IndexOf("/Assets/"); - if (assetsIndex >= 0) + if (assetsIndex >= 0) { filePath = filePath.Substring(assetsIndex + 1); } } - else + else { filePath = ""; } @@ -129,9 +139,22 @@ protected interface IStateMachineRef protected class StateMachineRef : IStateMachineRef where T : IAsyncStateMachine { public T value; + + [HideInCallstack] public void MoveNext() { value.MoveNext(); } } + public static void ResetStatics() + { + stateMachinePool = new TypedPool(); + nextId = 1; + steppingStack = new Stack(); + + pool = new TypedPool(); + resumerPool = new TypedPool(); + awaiterListPool = new Pool>(); + } + protected UInt64 id = 0; //Used to verify a routine is still the same instance and hasn't been recycled protected State state = State.NotStarted; protected bool stopChildrenOnStep = false; //Kill children when stepping. Used by WaitForAny @@ -145,26 +168,32 @@ protected class StateMachineRef : IStateMachineRef where T : IAsyncStateMachi protected readonly Action stepAction; protected Action stepAnyAction; protected Action stepAllAction; -#if DEBUG + + protected static Exception GetThrownException(RoutineBase routine) + { + return routine.thrownException; + } + +#if DEBUG_ROUTINES protected System.Diagnostics.StackFrame stackFrame = null; //Track where the routine was created for debugging #endif //Top-most routine currently being stepped - protected static RoutineBase Current { get { return (steppingStack.Count > 0) ? steppingStack.Peek() : null; } } + public static RoutineBase Current { get { return (steppingStack.Count > 0) ? steppingStack.Peek() : null; } } //State machines are pooled by keeping a wrapped class version. This is pointless in debug where the state //machines are generated as classes, but useful in release where they are structs. - protected static readonly TypedPool stateMachinePool = new TypedPool(); + protected static TypedPool stateMachinePool = new TypedPool(); private static UInt64 nextId = 1; //Id generator. 64bits should be enough, right? //Tracks actively stepping routines - private static readonly Stack steppingStack = new Stack(); + private static Stack steppingStack = new Stack(); //Pools - private static readonly TypedPool pool = new TypedPool(); - private static readonly TypedPool resumerPool = new TypedPool(); - private static readonly Pool> awaiterListPool = new Pool>(); + private static TypedPool pool = new TypedPool(); + private static TypedPool resumerPool = new TypedPool(); + private static Pool> awaiterListPool = new Pool>(); //Is routine active? private bool IsRunning { get { return !IsDead && !IsCompleted; }} @@ -174,6 +203,8 @@ public RoutineBase() stepAction = Step; } + public abstract object GetBoxedResult(); + /// Stop the routine. public void Stop() { @@ -189,6 +220,7 @@ public void Stop() } } + [HideInCallstack] /// Internal use only. Executes to the next await or end of the async method. public void Step() { @@ -294,10 +326,10 @@ public void Throw(Exception exception) } /// Internal use only. Store the current stack frame for debugging. - [System.Diagnostics.Conditional("DEBUG")] + [System.Diagnostics.Conditional("DEBUG_ROUTINES")] public void Trace(int frame) { -#if DEBUG +#if DEBUG_ROUTINES if (TracingEnabled) { stackFrame = new System.Diagnostics.StackFrame(frame + 1, true); @@ -316,10 +348,10 @@ public static void ClearPools() public static void Report() { - Debug.LogFormat("stateMachinePool = {0}", stateMachinePool.Report()); - Debug.LogFormat("pool = {0}", pool.Report()); - Debug.LogFormat("resumerPool = {0}", resumerPool.Report()); - Debug.LogFormat("awaiterListPool = {0}", awaiterListPool.Report()); + UnityEngine.Debug.LogFormat("stateMachinePool = {0}", stateMachinePool.Report()); + UnityEngine.Debug.LogFormat("pool = {0}", pool.Report()); + UnityEngine.Debug.LogFormat("resumerPool = {0}", resumerPool.Report()); + UnityEngine.Debug.LogFormat("awaiterListPool = {0}", awaiterListPool.Report()); } /// Get a routine from the pool. If yield is false routine will resume immediately from await. @@ -580,7 +612,7 @@ public static Routine WaitForAny(params Routine[] routines) public static async Routine WaitForSeconds(float seconds) { var endTime = Time.time + seconds; - while (Time.time < endTime) + while (Time.time < endTime) { await WaitForNextFrame(); } @@ -634,7 +666,7 @@ public static async Routine WaitForCustomYieldInstruction(CustomYieldInstruction } } - protected void Start() + public void Start() { if (manager == null) { @@ -646,6 +678,7 @@ protected void Start() } } + [HideInCallstack] protected void Finish() { state = State.Finished; @@ -673,9 +706,10 @@ protected virtual void Reset() manager = null; } + [HideInCallstack] protected void OnException(Exception exception) { -#if DEBUG +#if DEBUG_ROUTINES if (TracingEnabled && !(exception is RoutineException) && !(exception is AggregateException)) { exception = new RoutineException( @@ -694,14 +728,13 @@ protected void OnException(Exception exception) protected void ThrowPendingException() { - if (thrownException == null) - { + if (thrownException == null) { return; } var exception = thrownException; thrownException = null; - throw exception; + ExceptionDispatchInfo.Capture(exception).Throw(); } private void Setup(bool yield, RoutineBase parent) @@ -784,7 +817,15 @@ public void SetManager(RoutineManager manager, Action onStop) /// Internal use only. Required for awaiter. public void GetResult() { - ThrowPendingException(); + if (thrownException != null) { + ThrowPendingException(); + } + } + + public override object GetBoxedResult() + { + GetResult(); + return null; } /// Internal use only. Required for task-like. @@ -851,7 +892,7 @@ public void StepAll() //Propagate exceptions foreach (var child in children) { - var childException = (child as Routine).thrownException; + var childException = GetThrownException(child); if (childException != null) { thrownException = (thrownException != null) @@ -870,9 +911,8 @@ public void StepAny() //Propagate exception foreach (var child in children) { - if (child.IsCompleted) - { - var childException = (child as Routine).thrownException; + if (child.IsCompleted) { + var childException = GetThrownException(child); thrownException = childException; break; } @@ -904,10 +944,17 @@ public Routine() : base() /// Internal use only. Required for awaiter. public T GetResult() { - ThrowPendingException(); + if (thrownException != null) { + ThrowPendingException(); + } return result; } + public override object GetBoxedResult() + { + return GetResult(); + } + /// Internal use only. Required for task-like. public Routine GetAwaiter() { diff --git a/RoutineHandle.cs b/RoutineHandle.cs index 46e7329..b68eeaf 100644 --- a/RoutineHandle.cs +++ b/RoutineHandle.cs @@ -21,11 +21,14 @@ //SOFTWARE. using System; +using UnityEngine; namespace AsyncRoutines { public struct RoutineHandle - { + { + private static RoutineStoppedException StopException = new(); + private UInt64 id; private Routine routine; @@ -41,11 +44,12 @@ public RoutineHandle(Routine routine) /// Stop the routine. public void Stop() { + // Stopping a routine does not allow for any cleanup of objects that might be in running async methods, so we want to stop routines by throwing an exception. if (routine != null && id == routine.Id) { - routine.Stop(); + routine.Throw(StopException); } - } + } /// Throw an exception in the routine. public void Throw(Exception exception) diff --git a/RoutineManager.cs b/RoutineManager.cs index eeaccfb..e161c1e 100644 --- a/RoutineManager.cs +++ b/RoutineManager.cs @@ -1,147 +1,155 @@ -//MIT License -// -//Copyright (c) 2018 Tom Blind -// -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. - -using System.Collections.Generic; -using System; -using UnityEngine; -using AsyncRoutines.Internal; - -namespace AsyncRoutines -{ - public class RoutineManager - { - private List nextFrameResumers = new List(); - private List pendingNextFrameResumers = new List(); - private readonly List roots = new List(); - private int maxRoot = -1; - - /// Resumers managed routines that are waiting for next frame. - public void Update() - { - foreach (var resumer in nextFrameResumers) - { - resumer.Resume(); - } - nextFrameResumers.Clear(); - - //Cleanup dead routines - var _maxRoot = maxRoot; - maxRoot = -1; - for (var i = 0; i <= _maxRoot; ++i) - { - var root = roots[i]; - if (root == null) - { - continue; - } - if (root.IsDead) - { - roots[i] = null; - Routine.Release(root); - } - else - { - maxRoot = i; - } - } - } - - /// Prepares next-frame resumers. Should be called in LateUpdate. - public void Flush() - { - var temp = nextFrameResumers; - nextFrameResumers = pendingNextFrameResumers; - pendingNextFrameResumers = temp; - } - - /// Stops all managed routines. - public void StopAll() - { - for (var i = 0; i < roots.Count; ++i) - { - if (roots[i] == null) - { - continue; - } - Routine.Release(roots[i]); - roots[i] = null; - } - } - - /// Throws an exception in all managed routines. - public void ThrowAll(Exception exception) - { - for (var i = 0; i < roots.Count; ++i) - { - var root = roots[i]; - if (root != null) - { - root.Throw(exception); - } - } - } - - /// Manages and runs a routine. - public RoutineHandle Run(Routine task, Action onStop = null) - { - task.SetManager(this, onStop ?? DefaultOnStop); - - var added = false; - for (var i = 0; i < roots.Count; ++i) - { - if (roots[i] == null) - { - maxRoot = Mathf.Max(maxRoot, i); - roots[i] = task; - added = true; - break; - } - } - if (!added) - { - maxRoot = roots.Count; - roots.Add(task); - } - - task.Step(); - return new RoutineHandle(task); - } - - /// - /// Internal use only. Schedules a lightweight resumer to be called next frame. - /// - public void AddNextFrameResumer(ref LightResumer resumer) - { - pendingNextFrameResumers.Add(resumer); - } - - private static void DefaultOnStop(Exception exception) - { - if (exception != null) - { - Debug.LogError(exception.ToString()); - if (exception is AggregateException a) { Debug.LogError(a.Flatten().ToString()); } - Debug.LogException(exception); - } - } - } -} +//MIT License +// +//Copyright (c) 2018 Tom Blind +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +using System.Collections.Generic; +using System; +using UnityEngine; +using AsyncRoutines.Internal; + +namespace AsyncRoutines +{ + public class RoutineManager + { + private List nextFrameResumers = new List(); + private List pendingNextFrameResumers = new List(); + private readonly List roots = new List(); + private int maxRoot = -1; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + public static void ResetStatics() + { + RoutineBase.ResetStatics(); + } + + /// Resumers managed routines that are waiting for next frame. + public void Update() + { + foreach (var resumer in nextFrameResumers) + { + resumer.Resume(); + } + nextFrameResumers.Clear(); + + //Cleanup dead routines + var _maxRoot = maxRoot; + maxRoot = -1; + for (var i = 0; i <= _maxRoot; ++i) + { + var root = roots[i]; + if (root == null) + { + continue; + } + if (root.IsDead) + { + roots[i] = null; + Routine.Release(root); + } + else + { + maxRoot = i; + } + } + } + + /// Prepares next-frame resumers. Should be called in LateUpdate. + public void Flush() + { + var temp = nextFrameResumers; + nextFrameResumers = pendingNextFrameResumers; + pendingNextFrameResumers = temp; + } + + /// Stops all managed routines. + public void StopAll() + { + for (var i = 0; i < roots.Count; ++i) + { + if (roots[i] == null) + { + continue; + } + Routine.Release(roots[i]); + roots[i] = null; + } + } + + /// Throws an exception in all managed routines. + public void ThrowAll(Exception exception) + { + for (var i = 0; i < roots.Count; ++i) + { + var root = roots[i]; + if (root != null) + { + root.Throw(exception); + } + } + } + + /// Manages and runs a routine. + public RoutineHandle Run(Routine task, Action onStop = null) + { + task.SetManager(this, onStop ?? DefaultOnStop); + + var added = false; + for (var i = 0; i < roots.Count; ++i) + { + if (roots[i] == null) + { + maxRoot = Mathf.Max(maxRoot, i); + roots[i] = task; + added = true; + break; + } + } + if (!added) + { + maxRoot = roots.Count; + roots.Add(task); + } + + task.Step(); + return new RoutineHandle(task); + } + + /// + /// Internal use only. Schedules a lightweight resumer to be called next frame. + /// + public void AddNextFrameResumer(ref LightResumer resumer) + { + pendingNextFrameResumers.Add(resumer); + } + + private static void DefaultOnStop(Exception exception) + { + if (exception != null && exception is not RoutineStoppedException) + { + if (exception is AggregateException a) { + Debug.LogError(a.Flatten().ToString()); + } + + Debug.LogException(exception); + } + } + } +} diff --git a/RoutineManagerBehavior.cs b/RoutineManagerBehavior.cs index 1db9ba6..199ea37 100644 --- a/RoutineManagerBehavior.cs +++ b/RoutineManagerBehavior.cs @@ -1,67 +1,67 @@ -//MIT License -// -//Copyright (c) 2018 Tom Blind -// -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. - -using System; -using UnityEngine; - -namespace AsyncRoutines -{ - public class RoutineManagerBehavior : MonoBehaviour - { - public RoutineManager Manager { get { return routineManager; } } - - private RoutineManager routineManager = new RoutineManager(); - - public virtual void Update() - { - routineManager.Update(); - } - - public virtual void LateUpdate() - { - routineManager.Flush(); - } - - public void OnDestroy() - { - routineManager.StopAll(); - } - - /// Manages and runs a routine. - public RoutineHandle Run(Routine routine, Action onStop = null) - { - return routineManager.Run(routine, onStop); - } - - /// Stops all managed routines. - public void StopAll() - { - routineManager.StopAll(); - } - - /// Throws an exception in all managed routines. - public void ThrowAll(Exception exception) - { - routineManager.ThrowAll(exception); - } - } -} +//MIT License +// +//Copyright (c) 2018 Tom Blind +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +using System; +using UnityEngine; + +namespace AsyncRoutines +{ + public class RoutineManagerBehavior : MonoBehaviour + { + public RoutineManager Manager { get { return routineManager; } } + + private RoutineManager routineManager = new RoutineManager(); + + public virtual void Update() + { + routineManager.Update(); + } + + public virtual void LateUpdate() + { + routineManager.Flush(); + } + + public void OnDestroy() + { + routineManager.StopAll(); + } + + /// Manages and runs a routine. + public RoutineHandle Run(Routine routine, Action onStop = null) + { + return routineManager.Run(routine, onStop); + } + + /// Stops all managed routines. + public void StopAll() + { + routineManager.StopAll(); + } + + /// Throws an exception in all managed routines. + public void ThrowAll(Exception exception) + { + routineManager.ThrowAll(exception); + } + } +} diff --git a/RoutineStoppedException.cs b/RoutineStoppedException.cs new file mode 100644 index 0000000..b5f25d0 --- /dev/null +++ b/RoutineStoppedException.cs @@ -0,0 +1,9 @@ +using System; + +namespace AsyncRoutines +{ + public sealed class RoutineStoppedException : OperationCanceledException + { + + } +} \ No newline at end of file diff --git a/RoutineStoppedException.cs.meta b/RoutineStoppedException.cs.meta new file mode 100644 index 0000000..3e41a96 --- /dev/null +++ b/RoutineStoppedException.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 74a1b19446b944cba5fe48e8e0493c58 +timeCreated: 1689799395 \ No newline at end of file