Last active
March 3, 2022 19:20
-
-
Save darbotron/296017d6dce649fee3ed732ec3d77be2 to your computer and use it in GitHub Desktop.
StoppableCoroutine et al. - a bunch of handy classes for managing coroutines in Unity
This file contains hidden or 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
// | |
// License: https://opensource.org/licenses/unlicense | |
// TL;DR: | |
// 1) you may do what you like with it... | |
// 2) ...except blame me for any consequence of acting on rule 1) | |
// | |
using System; | |
using System.Collections; | |
using UnityEngine; | |
// | |
// interface to add a bunch of useful QOL stuff for coroutines in Unity | |
// | |
public interface IStoppable | |
{ | |
bool IsRunning(); // returns true from construction til Stop() or ForceStop() are called, or IEnumerator MoveNext has returned false | |
void Stop(); // attempts to stop the enumeration, if IsStopping() returns true after this call additional calls to MoveNext() are required to complete the enumeration | |
bool IsStopping(); // returns true after Stop() if requires additional calls to MoveNext() to complete the enumeration | |
bool HasStopped(); // returns true when enumeration has stopped (i.e. if this is true MoveNext() must return false when called) | |
bool IsProcessing(); // returns true when ( ! HasStopped() ) | |
void ForceStop(); // tries to instantly stop the IEnumerator - allowed to throw System.NotSupportedException | |
} | |
// | |
// combine IStoppable with IEnumerator | |
// | |
public interface IStoppableEnumerator : IEnumerator, IStoppable | |
{ | |
// IEnumerator: void Reset(); - MS docs say it's officially part of the IEnumerator spec to throw new NotSupportedException() if this operation isn't supported | |
// IEnumerator: object Current { get; } | |
// IEnumerator: bool MoveNext(); | |
} | |
// | |
// Wraps a single IEnumerator / MonoBehaviour pairing | |
// | |
// The IEnumerator is run as a coroutine on the paired MonoBehaviour at creation using MonoBehaviour.StartCoroutine() | |
// | |
// Provides the following super useful extensions to the basic coroutine stuff: | |
// | |
// * call InstantStopCoroutine.StartCoroutineOn() to create an instance and start it as a coroutine | |
// * check if the IEnumerator is running by calling IsRunning() | |
// * check if the IEnumerator has finished by calling HasStopped() | |
// * stop at any time by calling Stop() (calls MonoBehaviour.StopCoroutine() on the paired MonoBehaviour) - this instantly transitions from IsRunning() to HasStopped() | |
// * call a callback Action if/when Stop() is called | |
// * asserts on operations done in invalid states | |
// | |
// CAVEATS | |
// * DOES NOT support IEnumerator.Reset() - throws NotSupportedException if called | |
// * WILL NOT call the callback if the coroutine is stopped via the Monobehaviour Coroutine API on m_coroutineMonobehaviour | |
// * stores a reference to the running monobehaviour (m_coroutineMonobehaviour) so be careful of keeping static collections of these | |
// | |
public abstract class InstantStopCoroutineBase : IStoppableEnumerator | |
{ | |
////////////////////////////////////////////////////////////////////////// | |
#region IEnumerator interface | |
public void Reset() | |
{ | |
throw new NotSupportedException( "InstantStopCoroutine doesn't support Reset()" ); | |
} | |
public object Current => m_managedEnumerator.Current; | |
public bool MoveNext() | |
{ | |
if( HasStopped() ) | |
{ | |
return false; | |
} | |
if( m_managedEnumerator.MoveNext() ) | |
{ | |
return true; | |
} | |
CurrentState = State.Stopped; | |
return false; | |
} | |
#endregion IEnumerator interface | |
////////////////////////////////////////////////////////////////////////// | |
#region IStoppableEnumerator interface | |
public bool IsRunning() => ( State.Running == CurrentState ); | |
public bool IsStopping() => false; // stops instantly | |
public bool HasStopped() => ( State.Stopped == CurrentState ); | |
public bool IsProcessing() => ( ! HasStopped() ); | |
public void Stop() | |
{ | |
if( State.Stopped == CurrentState ) Debug.LogWarning( $"can't stop again, already stopped" ); | |
switch( CurrentState ) | |
{ | |
case State.Running: | |
m_coroutineMonobehaviour.StopCoroutine( this ); | |
CurrentState = State.Stopped; | |
m_cbOnWasManuallyStopped?.Invoke(); | |
break; | |
} | |
} | |
public void ForceStop() => Stop(); | |
#endregion IStoppableEnumerator interface | |
////////////////////////////////////////////////////////////////////////// | |
#region private | |
private enum State | |
{ | |
Running, | |
Stopped | |
} | |
private State CurrentState { get; set; } | |
//------------------------------------------------------------------------ | |
private readonly MonoBehaviour m_coroutineMonobehaviour = null; | |
private readonly IEnumerator m_managedEnumerator = null; | |
private readonly Action m_cbOnWasManuallyStopped = null; | |
//------------------------------------------------------------------------ | |
// note: protected to allow deriving classes | |
//------------------------------------------------------------------------ | |
protected InstantStopCoroutineBase( MonoBehaviour coroutineMonobehaviour, IEnumerator managedEnumerator, Action cbOnWasManuallyStopped ) | |
{ | |
Debug.Assert( ( null != managedEnumerator ), $"{nameof(managedEnumerator)} must be a valid IEnumerator" ); | |
m_coroutineMonobehaviour = coroutineMonobehaviour; | |
m_managedEnumerator = managedEnumerator; | |
m_cbOnWasManuallyStopped = cbOnWasManuallyStopped; | |
CurrentState = State.Running; | |
m_coroutineMonobehaviour.StartCoroutine( this ); | |
} | |
#endregion private | |
////////////////////////////////////////////////////////////////////////// | |
} | |
// | |
// basic no-frills concrete implementation of InstantStopCoroutineBase | |
// | |
public class InstantStopCoroutine : InstantStopCoroutineBase | |
{ | |
//------------------------------------------------------------------------ | |
public static InstantStopCoroutine StartCoroutineOn( MonoBehaviour runningMonobehaviour, IEnumerator enumeratorToRun, Action cbOnWasManuallyStopped ) => new InstantStopCoroutine( runningMonobehaviour, enumeratorToRun, cbOnWasManuallyStopped ); | |
//------------------------------------------------------------------------ | |
public static InstantStopCoroutine StartCoroutineOn( MonoBehaviour runningMonobehaviour, IEnumerator enumeratorToRun ) => new InstantStopCoroutine( runningMonobehaviour, enumeratorToRun, null ); | |
//------------------------------------------------------------------------ | |
protected InstantStopCoroutine( MonoBehaviour coroutineMonobehaviour, IEnumerator managedEnumerator, Action cbOnWasManuallyStopped ) : base( coroutineMonobehaviour, managedEnumerator, cbOnWasManuallyStopped ) | |
{} | |
} | |
// | |
// awesome concrete version of InstantStopCoroutineBase which allows cllient code to | |
// add data to their coroutines which is accessible via the coroutine and to the coroutine | |
// essentially making the couroutine into a "lite" class object | |
// | |
// can be used for stuff like: | |
// * keeping tabs on running coroutines | |
// * changing behaviour of running coroutines | |
// * giving each coroutine its own state accessible from outside | |
// | |
public class InstantStopCoroutineWithData< T > : InstantStopCoroutineBase | |
{ | |
//------------------------------------------------------------------------ | |
public static InstantStopCoroutineWithData< T > StartCoroutineOn( MonoBehaviour runningMonobehaviour, Func< T, IEnumerator > enumeratorFunction, T dataForCoroutine, Action cbOnWasManuallyStopped ) | |
{ | |
return new InstantStopCoroutineWithData< T >( runningMonobehaviour, enumeratorFunction( dataForCoroutine ), dataForCoroutine, cbOnWasManuallyStopped ); | |
} | |
//------------------------------------------------------------------------ | |
public static InstantStopCoroutineWithData< T > StartCoroutineOn( MonoBehaviour runningMonobehaviour, Func< T, IEnumerator > enumeratorFunction, T dataForCoroutine ) | |
{ | |
return new InstantStopCoroutineWithData< T >( runningMonobehaviour, enumeratorFunction( dataForCoroutine ), dataForCoroutine, null ); | |
} | |
//------------------------------------------------------------------------ | |
public T Data { get; protected set; } | |
//------------------------------------------------------------------------ | |
protected InstantStopCoroutineWithData( MonoBehaviour coroutineMonobehaviour, IEnumerator managedEnumerator, T dataForCoroutine, Action cbOnWasManuallyStopped ) : base( coroutineMonobehaviour, managedEnumerator, cbOnWasManuallyStopped ) | |
{ | |
Data = dataForCoroutine; | |
} | |
} | |
// | |
// Wraps a pair of IEnumerators and associates with a MonoBehaviour | |
// | |
// Two IEnumerators are needed for this class, one enumerated to 'run' the coroutine, and another enumerated used for 'stopping' it | |
// | |
// The 'run' IEnumerator is run on the paired MonoBehaviour at creation using MonoBehaviour.StartCoroutine() | |
// IsRunning() will now return true | |
// | |
// If Stop() is called before the 'run' enumerator has completed, then the 'stopping' enumerator will be enumerated | |
// by IEnumerator.MoveNext() (which is what Unity uses to update Coroutines) | |
// | |
// | |
// * call StoppableCoroutineBase.StartCoroutineOn() to create an instance and start it as a coroutine | |
// * check if the IEnumerator is running by calling IsRunning() - if this is true then the 'run' IEnumerator is being enumerated when IEnumerator.MoveNext() is called | |
// * stop at any time by calling Stop() (calls MonoBehaviour.StopCoroutine() on the paired MonoBehaviour) - this instantly transitions from IsRunning() to HasStopped() | |
// * check if the IEnumerator is stopping by calling IsStopping() - if this is true then the 'stopping' IEnumerator is being enumerated when IEnumerator.MoveNext() is called | |
// * check if the IEnumerator has finished by calling HasStopped() - if this is true then either the 'run' or 'stopping' enumerators enumerated to completion | |
// * can force an instant transition from IsRunning() to HasStopped() with ForceStop() - however this is not guaranteed to be safe, will depend on what the IEnumerators are actually doing | |
// * asserts on operations done in invalid states | |
// | |
// CAVEATS | |
// * DOES NOT support IEnumerator.Reset() - throws NotSupportedException if called | |
// * behaviour will break if the coroutine is stopped via the Monobehaviour Coroutine API on m_coroutineMonobehaviour | |
// * stores a reference to the running monobehaviour (m_coroutineMonobehaviour) so be careful of keeping static collections of these | |
// | |
public abstract class StoppableCoroutineBase : IStoppableEnumerator | |
{ | |
////////////////////////////////////////////////////////////////////////// | |
#region IEnumerator interface | |
public void Reset() | |
{ | |
throw new NotSupportedException( "StoppableCoroutineBase doesn't support Reset()" ); | |
} | |
public object Current => CurrentEnumerator.Current; | |
public bool MoveNext() | |
{ | |
if( HasStopped() ) | |
{ | |
return false; | |
} | |
if( CurrentEnumerator.MoveNext() ) | |
{ | |
return true; | |
} | |
CurrentState = State.Stopped; | |
return false; | |
} | |
#endregion IEnumerator interface | |
////////////////////////////////////////////////////////////////////////// | |
#region IStoppableEnumerator interface | |
public bool IsRunning() => ( State.Running == CurrentState ); | |
public bool IsStopping() => ( State.Stopping == CurrentState ); | |
public bool HasStopped() => ( State.Stopped == CurrentState ); | |
public bool IsProcessing() => ( ! HasStopped() ); | |
public void Stop() | |
{ | |
Debug.Assert( ( State.Stopped != CurrentState ), $"can't stop again, already stopped" ); | |
Debug.Assert( ( State.Stopping != CurrentState ), $"can't stop again, already stopping" ); | |
switch( CurrentState ) | |
{ | |
case State.Running: | |
// swap to the stopping enumerator - MoveNext must then be called til it returns false | |
CurrentEnumerator = m_funcToGetEnumeratorForStopping(); | |
CurrentState = State.Stopping; | |
break; | |
} | |
} | |
public void ForceStop() | |
{ | |
m_coroutineMonobehaviour.StopCoroutine( this ); | |
CurrentState = State.Stopped; | |
} | |
#endregion IStoppableEnumerator interface | |
////////////////////////////////////////////////////////////////////////// | |
#region private | |
private enum State | |
{ | |
Running, | |
Stopping, | |
Stopped | |
} | |
private State CurrentState { get; set; } | |
private IEnumerator CurrentEnumerator {get; set;} | |
private readonly MonoBehaviour m_coroutineMonobehaviour = null; | |
private readonly IEnumerator m_managedEnumeratorRunning = null; | |
private readonly Func< IEnumerator > m_funcToGetEnumeratorForStopping = null; | |
protected StoppableCoroutineBase( MonoBehaviour coroutineMonobehaviour, IEnumerator managedEnumeratorRunning, Func< IEnumerator > funcToGetEnumeratorForStopping ) | |
{ | |
Debug.Assert( ( null != managedEnumeratorRunning ), $"{nameof(managedEnumeratorRunning)} must be a valid {managedEnumeratorRunning.GetType().Name}" ); | |
Debug.Assert( ( null != funcToGetEnumeratorForStopping ), $"{nameof(funcToGetEnumeratorForStopping)} must be a valid {funcToGetEnumeratorForStopping.GetType().Name}" ); | |
m_coroutineMonobehaviour = coroutineMonobehaviour; | |
m_managedEnumeratorRunning = managedEnumeratorRunning; | |
m_funcToGetEnumeratorForStopping = funcToGetEnumeratorForStopping; | |
CurrentState = State.Running; | |
CurrentEnumerator = m_managedEnumeratorRunning; | |
m_coroutineMonobehaviour.StartCoroutine( this ); | |
} | |
#endregion private | |
////////////////////////////////////////////////////////////////////////// | |
} | |
// | |
// basic no-frills concrete implementation of StoppableCoroutine | |
// | |
public class StoppableCoroutine : StoppableCoroutineBase | |
{ | |
//------------------------------------------------------------------------ | |
// NOTE: no override for null enumeratorToStop as you wouldn't use this | |
// StoppableCoroutine unless you needed an enumerator to stop the task | |
public static StoppableCoroutine StartCoroutineOn( MonoBehaviour runningMonobehaviour, IEnumerator enumeratorToRun, Func< IEnumerator > funcToGetEnumeratorToStop ) => new StoppableCoroutine( runningMonobehaviour, enumeratorToRun, funcToGetEnumeratorToStop ); | |
//------------------------------------------------------------------------ | |
protected StoppableCoroutine( MonoBehaviour runningMonobehaviour, IEnumerator enumeratorToRun, Func< IEnumerator > funcToGetEnumeratorToStop ) : base( runningMonobehaviour, enumeratorToRun, funcToGetEnumeratorToStop ) | |
{} | |
} | |
// | |
// awesome concrete version of StoppableCoroutineBase which allows cllient code to | |
// add data to their coroutines which is accessible via the coroutine and to the coroutine | |
// essentially making the couroutine into a "lite" class object | |
// | |
// can be used for stuff like: | |
// * keeping tabs on running coroutines | |
// * changing behaviour of running coroutines | |
// * giving each coroutine its own state accessible from outside | |
// | |
public class StoppableCoroutineWithData< T > : StoppableCoroutineBase | |
{ | |
//------------------------------------------------------------------------ | |
public static StoppableCoroutineWithData< T > StartCoroutineOn( MonoBehaviour runningMonobehaviour, Func< T, IEnumerator > funcToRunCoroutine, T dataForCoroutine, Func< T, IEnumerator > funcToStopCoroutine ) | |
{ | |
return new StoppableCoroutineWithData< T >( runningMonobehaviour, funcToRunCoroutine( dataForCoroutine ), dataForCoroutine, () => funcToStopCoroutine( dataForCoroutine ) ); | |
} | |
//------------------------------------------------------------------------ | |
public T Data { get; protected set; } | |
//------------------------------------------------------------------------ | |
protected StoppableCoroutineWithData( MonoBehaviour coroutineMonobehaviour, IEnumerator enumeratorToRun, T dataForCoroutine, Func< IEnumerator > functionToGetEnumeratorToStop ) : base( coroutineMonobehaviour, enumeratorToRun, functionToGetEnumeratorToStop ) | |
{ | |
Data = dataForCoroutine; | |
} | |
} | |
// | |
// wrapper over the inputs to StoppableCoroutine which allows a StoppableCoroutine | |
// to be explicitly started an arbitrary time later | |
// | |
// intended for stuff like for making a command queue using StoppableCoroutines | |
// | |
public abstract class StoppableCommandBase : IStoppable | |
{ | |
////////////////////////////////////////////////////////////////////////// | |
#region IStoppable interface | |
public bool IsRunning() => m_wrappedIStoppableCoroutine?.IsRunning() ?? false; | |
public bool IsStopping() => m_wrappedIStoppableCoroutine?.IsStopping() ?? false; | |
public bool HasStopped() => m_wrappedIStoppableCoroutine?.HasStopped() ?? false; | |
public bool IsProcessing() => ( ! HasStopped() ); | |
public void Stop() => m_wrappedIStoppableCoroutine?.Stop(); | |
public void ForceStop() => Stop(); | |
#endregion IStoppable interface | |
////////////////////////////////////////////////////////////////////////// | |
//------------------------------------------------------------------------ | |
public void Start() | |
{ | |
Debug.Assert( ( null == m_wrappedIStoppableCoroutine ), "already started" ); | |
m_wrappedIStoppableCoroutine = VOnStartCommand( m_paramMonobehaviourToRunOn ); | |
} | |
//------------------------------------------------------------------------ | |
protected StoppableCommandBase( MonoBehaviour monobehaviourToRunCommandOn ) | |
{ | |
m_paramMonobehaviourToRunOn = monobehaviourToRunCommandOn; | |
} | |
//------------------------------------------------------------------------ | |
protected abstract IStoppableEnumerator VOnStartCommand( MonoBehaviour monobehaviourToRunOn ); | |
private MonoBehaviour m_paramMonobehaviourToRunOn = null; | |
private IStoppableEnumerator m_wrappedIStoppableCoroutine = null; | |
} | |
// | |
// InstantStopCoroutine as a command | |
// | |
public class InstantStopCommand : StoppableCommandBase | |
{ | |
//------------------------------------------------------------------------ | |
public static InstantStopCommand CreateCommandFor( MonoBehaviour monobehaviourToRunCommandOn, Func< IEnumerator > funcGetEnumeratorToRun ) => CreateCommandFor( monobehaviourToRunCommandOn, funcGetEnumeratorToRun, null ); | |
public static InstantStopCommand CreateCommandFor( MonoBehaviour monobehaviourToRunCommandOn, Func< IEnumerator > funcGetEnumeratorToRun, Action cbOnWasManuallyStopped ) => new InstantStopCommand( monobehaviourToRunCommandOn, funcGetEnumeratorToRun, cbOnWasManuallyStopped ); | |
//------------------------------------------------------------------------ | |
protected InstantStopCommand( MonoBehaviour monobehaviourToRunCommandOn, Func< IEnumerator > funcGetEnumeratorToRun, Action cbOnWasManuallyStopped ) : base( monobehaviourToRunCommandOn ) | |
{ | |
m_paramFuncGetEnumeratorToRun = funcGetEnumeratorToRun; | |
m_paramActionOnWasStopped = cbOnWasManuallyStopped; | |
} | |
//------------------------------------------------------------------------ | |
protected override IStoppableEnumerator VOnStartCommand( MonoBehaviour monobehaviourToRunOn ) => InstantStopCoroutine.StartCoroutineOn( monobehaviourToRunOn, m_paramFuncGetEnumeratorToRun(), m_paramActionOnWasStopped ); | |
private Func< IEnumerator > m_paramFuncGetEnumeratorToRun = null; | |
private Action m_paramActionOnWasStopped = null; | |
} | |
// | |
// InstantStopCoroutineWithData as a command | |
// | |
public class InstantStopCommandWithData< T > : StoppableCommandBase | |
{ | |
//------------------------------------------------------------------------ | |
public static InstantStopCommandWithData< T > CreateCommandFor( MonoBehaviour monobehaviourToRunCommandOn, Func< T, IEnumerator > funcGetEnumeratorToRun, T data ) => CreateCommandFor( monobehaviourToRunCommandOn, funcGetEnumeratorToRun, data, null ); | |
public static InstantStopCommandWithData< T > CreateCommandFor( MonoBehaviour monobehaviourToRunCommandOn, Func< T, IEnumerator > funcGetEnumeratorToRun, T data, Action cbOnWasManuallyStopped ) => new InstantStopCommandWithData< T >( monobehaviourToRunCommandOn, funcGetEnumeratorToRun, data, cbOnWasManuallyStopped ); | |
//------------------------------------------------------------------------ | |
protected InstantStopCommandWithData( MonoBehaviour monobehaviourToRunCommandOn, Func< T, IEnumerator > funcGetEnumeratorToRun, T data, Action cbOnWasManuallyStopped ) : base( monobehaviourToRunCommandOn ) | |
{ | |
m_paramFuncGetEnumeratorToRun = funcGetEnumeratorToRun; | |
m_paramActionOnWasStopped = cbOnWasManuallyStopped; | |
m_paramData = data; | |
} | |
//------------------------------------------------------------------------ | |
protected override IStoppableEnumerator VOnStartCommand( MonoBehaviour monobehaviourToRunOn ) => InstantStopCoroutineWithData< T >.StartCoroutineOn( monobehaviourToRunOn, m_paramFuncGetEnumeratorToRun, m_paramData, m_paramActionOnWasStopped ); | |
private Func< T, IEnumerator > m_paramFuncGetEnumeratorToRun = null; | |
private Action m_paramActionOnWasStopped = null; | |
private T m_paramData = default( T ); | |
} | |
// | |
// StoppableCoroutine as a command | |
// | |
public class StoppableCommand : StoppableCommandBase | |
{ | |
//------------------------------------------------------------------------ | |
public static StoppableCommand CreateCommandFor( MonoBehaviour monobehaviourToRunCommandOn, Func< IEnumerator > funcGetEnumeratorToRun, Func< IEnumerator > funcGetEnumeratorToStop ) => new StoppableCommand( monobehaviourToRunCommandOn, funcGetEnumeratorToRun, funcGetEnumeratorToStop ); | |
//------------------------------------------------------------------------ | |
protected StoppableCommand( MonoBehaviour monobehaviourToRunCommandOn, Func< IEnumerator > funcGetEnumeratorToRun, Func< IEnumerator > funcGetEnumeratorToStop ) : base( monobehaviourToRunCommandOn ) | |
{ | |
m_paramFuncGetEnumeratorToRun = funcGetEnumeratorToRun; | |
m_paramFuncGetEnumeratorToStop = funcGetEnumeratorToStop; | |
} | |
//------------------------------------------------------------------------ | |
protected override IStoppableEnumerator VOnStartCommand( MonoBehaviour monobehaviourToRunOn ) => StoppableCoroutine.StartCoroutineOn( monobehaviourToRunOn, m_paramFuncGetEnumeratorToRun(), m_paramFuncGetEnumeratorToStop ); | |
private Func< IEnumerator > m_paramFuncGetEnumeratorToRun = null; | |
private Func< IEnumerator > m_paramFuncGetEnumeratorToStop = null; | |
} | |
// | |
// StoppableCoroutineWithData< T > as a command | |
// | |
public class StoppableCommandWithData< T > : StoppableCommandBase | |
{ | |
//------------------------------------------------------------------------ | |
public static StoppableCommandWithData< T > CreateCommandFor( MonoBehaviour monobehaviourToRunCommandOn, Func< T, IEnumerator > funcGetEnumeratorToRun, T data, Func< T, IEnumerator > funcGetEnumeratorToStop ) => new StoppableCommandWithData< T >( monobehaviourToRunCommandOn, funcGetEnumeratorToRun, data, funcGetEnumeratorToStop ); | |
//------------------------------------------------------------------------ | |
protected StoppableCommandWithData( MonoBehaviour monobehaviourToRunCommandOn, Func< T, IEnumerator > funcGetEnumeratorToRun, T data, Func< T, IEnumerator > funcGetEnumeratorToStop ) : base( monobehaviourToRunCommandOn ) | |
{ | |
m_paramFuncGetEnumeratorToRun = funcGetEnumeratorToRun; | |
m_paramFuncGetEnumeratorToStop = funcGetEnumeratorToStop; | |
m_paramData = data; | |
} | |
//------------------------------------------------------------------------ | |
protected override IStoppableEnumerator VOnStartCommand( MonoBehaviour monobehaviourToRunOn ) => StoppableCoroutineWithData< T >.StartCoroutineOn( monobehaviourToRunOn, m_paramFuncGetEnumeratorToRun, m_paramData, m_paramFuncGetEnumeratorToStop ); | |
private Func< T, IEnumerator > m_paramFuncGetEnumeratorToRun = null; | |
private Func< T, IEnumerator > m_paramFuncGetEnumeratorToStop = null; | |
private T m_paramData = default( T ); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment