Brain exploding ... Problem with an internal Coroutine manager

##IEnumerator’s MoveNext() going crazy##


Hello,

calling all C# superbrains, as I smell a deep and complex processing logic problem here :slight_smile:

I’m trying to build an internal Coroutine manager, so that I can make sure of being able to use a home made version of StopCoroutine() without being restricted to one single argument.

Things go pretty well, except one big detail.

Architecture of the (small) manager##


  1. Uses an extension of IEnumerator (let’s call it “MyCoroutine”), which just takes a reference into its constructor to the real IEnumerator function I want to use. It also adds a “stop” bool value, and overrides the MoveNext() C# function, inserting a Delete function when “stop” is set to true.

  2. Each time I want to create a new instance of MyCoroutine, I put it into a Dictionary

  3. Each time I call the MyCoroutine Delete() function, it just override its MoveNext() function for it to return False (which ends its Unity life cycle basically), and then deletes the instantiated MyCoroutine class into that Dictionary.

##The extended class##


Here’s the class (inspired by : forum.unity3d.com/threads/102817-Coroutine-Control-and-StartCoroutine()-Coroutine) :

public class UniqueCoroutine : IEnumerator {

public bool stop;
public bool _moveNext;
string _name;
IEnumerator enumerator;
MonoBehaviour behaviour;

public readonly Coroutine coroutine;

public UniqueCoroutine(MonoBehaviour _behaviour, IEnumerator _enumerator, string _refName)
{
	stop = false;
    behaviour = _behaviour;
    enumerator = _enumerator;
	_name = _refName;
    coroutine = this.behaviour.StartCoroutine(this);
}

public void DeleteCoroutine(){
	//called from any other script, launch a Deletion process
	// (can't delete it immediately, it would mess up the Unity process)
	stop = true;
}

bool KillCoroutine(){
	try{
		return false;	
	} finally{
	//Delete the Dictionary's entry, "finally" used to ensure 
	//that the reference is killed right after Unity kills the process
		_model.KillCoroutine(behaviour, _name);
	}
}

public object Current {  get { return enumerator.Current; } }
public bool MoveNext() { 
	if (stop) {
		return KillCoroutine();
	} else {
		_moveNext = enumerator.MoveNext();
		if (stop) {
			return KillCoroutine();
		} else if (!_moveNext) {
			DeleteCoroutine();
			return false;
		} else {
			return true; 
		}
	}
}
public void Reset() { enumerator.Reset(); }

And that’s it. It works so far, and let me have full control over how many instances of one Coroutine are running, and moreover allows me to stop these at will, witout being limited to Unity’s StopCoroutine() single parameter restriction.

But … Something weird also happens…

#The problem#


If I decide to stop-delete one IEnumerator from a block of code which is nested inside that very IEnumerator, and if there was still some other code blocks remaining after that deletion point, the following happens :

  • IEnumerator properly stops and deletes itself
  • But the next time I launch a MyCoroutine from the same IEnumerator reference function, then as soon as a yield statement is happening, the MoveNext() litterally jumps toward the point where the last MyCoroutine instance was left before its deletion.

example :

IEnumerator Test(){
//step 1
(pseudocode : init variables)
//step 2
yield return new WaitForSeconds(1f);
//step 3
while (condition != true){
blablablabla
//step 4
yield return new WaitForEndOfFrame();
}

//step 5
(pseudocode : bleh bleh bleh bleh)

//step 6
if (condition == true)
	DeleteThisCoroutine();
	
//step 7
(pseudocode : yadda yadda yadda yadda)

}

So, practically :

  • the first instance of this IEnumerator would go well. I decide to stop it in step 6, leaving the step 7 undone.

  • if I call the same IEnumerator via another MyCoroutine right after its deletion, the code would perform everything up to step 2, and then would jump right toward step 7.
    Yes, it would have skipped step 3, 4, 5 and 6 automagically.

##Guesses and theories##


Theory #1 :
Even if I make sure of forcing MoveNext() to return False when I want to delete a MyCoroutine, so that Unity detects its end and deletes its process (right before deleting its very existing instance), the process still runs and therefore makes the next call for the exact same IEnumerator reference completely messed up.

Theory#2 :
The MyCoroutine constructor IEnumerator parameter is not creating a new instance of it, but just pointing a reference. That would be the most plausible explanation imho.

#Experimented solutions#

  • tried to never delete a MyCoroutine instance manually, but letting the engine end its life cycle naturally by forcing MoveNext() to return false. Calling any new instance of the same IEnumerator reference would add a new stack in a List instead of just replacing it (and other operations of List cleaning when necessary).
    This should be the solution in theory, as no IEnumerator is deleted while it’s still active in Unity. But the problem remains.

  • IEnumerator.Dispose() function would have been ideal, but it’s not supported in .NET 2.0 … So I tried to convert the constructor’s IEnumerator reference to IEnumerator, which supports Dispose(), but Unity.StartCoroutine() doesn’t support that type.

#Conclusion#
I’m helpless right now. The only remaining solution would be to rebuild the manager around StopCoroutine restrictions, by creating single lined coroutines which would call a yield return StartCoroutine(anyIenumeratorReference). Calling a StopCoroutine would stop it properly at any time, but I’m not sure it would stop the running IEnumerator instantly.
I will try that.

But, if anyone got an idea about that MoveNext() “bug” above, I’d really appreciate as it would save this architecture (which is better I guess, because giving more control).

You left out the most important thing: how do you create an instance of your coroutine? You know that when calling a generator function (a function with a yield statement) it returns an object instance which represents your coroutine as an IEnumerator. It doesn’t work like a delegate or function pointer. You have to create a NEW instance of your wrapper class as well as getting a new IEnumerator instance of your actual generator / coroutine to start a new one.

The true magic happens inside the class that is returned by your generator function. In this class .NET / Mono stores the actual state of the coroutine. If you want to start a new one you have to call your coroutine again to create a new instance. If you work with the old enumerator object you will of course just continue the enumeration where you have stopped the last time.

To start a new one you have to do something like:

IEnumerator myCoroutine()  // That's our generator function / coroutine
{
    yield return new WaitForSeconds(5.0f);
}

// Now start a new instance:
new UniqueCoroutine(myScript, myCoroutine(), "bla");
//                                |
//                                |
//   This call to your generator will return a new IEnumerator object

Hopefully Unity will include a public Stop() function in their Coroutine-class. That would be the easiest way to stop it from outside. StartCoroutine returns an instance of Unitys internal used class Coroutine but all you can do with it at the moment is to use it inside yield statement.

ps. I’m a bit confused about where your dictionary is located and where that _model variable comes from… How do you actually stop a coroutine? The overall concept of your little “hack” is well planned (at least from what i can see) and it actually should work, so it would help to see how you use it :wink:

#I made a try… and finally :)#


Ok, sorry for the pun :stuck_out_tongue:
I realized after turning the problem around several times that I was trying to achieve something extremely complicated from the start : to perform an “conditionally atomic” MoveNext().

Explanation : in the example I provided, I wanted my coroutine to stop parsing itself whenever I was destroying it.
Which basically means : As long as coroutine exists, MoveNext() up to the next YieldInstruction, and if it is null, treat the deletion point as a yield itself without having to write a “yield”.

That could have some use in some very complicated scenarii, when you want to stop a coroutine line-by-line processing immediately, without having to put a yield at each line.
But this mindset is not correct from the start imho. That would mean “I’m too lazy to encapsulate my events so I want an automagic script stopper”. That would also contradict with the whole “yield” philosophy (aka. creating precise breakpoints).

So in the end, I abandonned this idea and came back to forcing myself to use yield for coroutine breakpoints.
Result is a very cleaner workaround that still allows me to Stop coroutines without being restricted to StopCoroutine’s single argument. It also stops the coroutine at its next yield whenever its instance is deleted. You don’t necessarly have to create a “yield break” anymore after deleting it from an external script, just have any yield (preserving the coroutine workflow, in fact).

It also ensures that Unity’s coroutine triggered by UniqueCoroutine is not lost into dark processing hells when you’re destroying the UniqueCoroutine instance (which was causing the MoveNext() uncontrollable behaviour in the first place). It will now last itself cleanly.

#Solution#


##The centralized Start/Stop MyCoroutine functions##

I still have my Model centralized class (“_model” in the examples above), and it still have its StartMyCoroutine / StopMyCoroutine functions :

public static void StartMyCoroutine(MonoBehaviour behaviour, IEnumerator enumerator, string _name){
	StopUCoroutine(behaviour, _name);
	
	_coroutines.Add(_name, new UniqueCoroutine(behaviour, enumerator, _name)); 
}


public static void StopMyCoroutine(MonoBehaviour behaviour, string _name){
	
	_coroutines.Remove(_name);	
	
}

Except now _coroutines is a simplier Dictionary, of type Dictionary<string, UniqueCoroutine>.

##The UniqueCoroutine final state##
It is now much simplier, and doesn’t need any “stop” boolean anymore. It doesn’t even need the “_name” and “behaviour” reference, but I’ll keep 'em “just in case of” one moment where I’d want more control over the class. But you can remove them.

The new thing is this little “_coroutineManager” inserted inside the constructor. I’ll explain it in the chapter below.

Only thing to know is that it can’t be called just with this pointer, as it is not static. It’s up to the coder to pull whatever way he wants to call it.
(I used a more complicated way, but it would have been messy to explain it here, so this mini-pseudocode is more fitting).

	public class UniqueCoroutine : IEnumerator {

	public string _name;
	public IEnumerator enumerator;
	public _baseClass behaviour;

	public readonly Coroutine coroutine;
	
	public UniqueCoroutine(MonoBehaviour _behaviour, IEnumerator _enumerator, string _refName)
	{
		behaviour = _behaviour;
		enumerator = _enumerator;
		_name = _refName;
		
		coroutine = _coroutineManager.RunUniqueCoroutine(this);	
		
		
	}
	
	public object Current {  get { return enumerator.Current; } }
	public bool MoveNext() { 

			return enumerator.MoveNext();

	}
	public void Reset() { enumerator.Reset(); }
   
}

##The new _coroutineManager class##
This is just one unique MonoBehaviour attached to any gameObject you want, which will serve as some headquarters for all the UniqueCoroutine you create.

Here is the _coroutineManager’s function called by UniqueCoroutine constructor :

public Coroutine RunUniqueCoroutine(UniqueCoroutine uniqueCoroutine){
	return StartCoroutine(_uniqueCoroutine(uniqueCoroutine));
}

public IEnumerator _uniqueCoroutine(UniqueCoroutine uniqueCoroutine){

	while(uniqueCoroutine != null && uniqueCoroutine.enumerator.MoveNext()){
		yield return uniqueCoroutine.enumerator.Current;	
	}
	
}

Simple as that.
As long as the UniqueCoroutine instance is existing, Unity is parsing it. As soon as it is destroyed, Unity registers it as an ended coroutine.

The enumerator.Current returns whatever your “yield” statements do inside the original IEnumerator function, and therefore _uniqueCoroutine function will wait for it before performing the next MoveNext().

Thanks Bunny83 for your help, you contributed quite effectively in making me understand how simple could it be in the end.

I hope this mini framework could help some people around. Cheers, see ya.

Oh, I may not having expressed myself very well, but I do create a new instance by calling the generator function, and also do create a new instance of my wrapper class :slight_smile:

Sorry I should have written the whole creation process. Here it is :

  1. I’m calling _model’s creation function :

    _model.StartMyCoroutine(monoBehaviourWhereTheGeneratorResides, myCoroutine(), “myCoroutineName”);

  2. inside _model class, the StartMyCoroutine() function looks like this :

    public static void StartMyCoroutine(MonoBehaviour behaviour, IEnumerator enumerator, string _name){
    StopUCoroutine(behaviour, _name);

     if (!myDictionary.ContainsKey(_name)){
     	_coroutines.Add(_name, new List<UniqueCoroutine>()); 
     } 
     _coroutines[_name].Add(new UniqueCoroutine(behaviour, enumerator, _name));
    

    }

    public static void StopUCoroutine(MonoBehaviour behaviour, string _name){
    //orders the oldest instance of UniqueCoroutine named “_named” to stop
    //DeleteCoroutine() just force MoveNext() to return False
    if (_coroutines.ContainsKey(_name) && _coroutines[_name].Count > 0){
    _coroutines[_name][0].DeleteCoroutine();
    }

    }

Where myDictionary is of type Dictionary<string, List<UniqueCoroutine>>.

Each key is the name of one generator I want to start, and each entry’s List is a chronological succession of instances of this generator.

This list is used so that each time I call StartMyCoroutine(), if an instance of the generator it’s asking for is already running, the function doesn’t have to wait for its coroutine lifecycle to stop in order to trigger a new version of it (while still ensuring that the old version will stop as soon as its MoveNext() is called).