##IEnumerator’s MoveNext() going crazy##
Hello,
calling all C# superbrains, as I smell a deep and complex processing logic problem here
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##
-
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.
-
Each time I want to create a new instance of MyCoroutine, I put it into a Dictionary
-
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).