I am building a Coroutine wrapper system. I would like users to be able to jump to the Coroutine's last yield statement, or "state".
The wrapper is implemented as an Enumerator wrapper, that is passed to StartCoroutine. I have access to the iterator object, its current value and the source MonoBehaviour.
Here's the gist of it
public static CoroutineWrapper StartCoroutineWrapped(this MonoBehaviour component, IEnumerator coroutineEnumerator)
{
CoroutineWrapper wrapper = new(component, coroutineEnumerator);
component.StartCoroutine(wrapper);
return wrapper;
}
public class CoroutineWrapper: IEnumerator
{
public readonly MonoBehaviour Source;
public readonly IEnumerator Child;
internal CoroutineWrapper(MonoBehaviour source, IEnumerator coroutine)
{
Source = source;
Child = coroutine;
}
public bool MoveNext() => Child.MoveNext();
public object Current => Child.Current;
}
Currently, the only ways I have found to be able to do this:
- force the user to wrap their return value inside an object. In Debug mode, that object stores a stack trace in its constructor, which can be recovered.
yield return new YieldWrapper(null);
yield return new YieldWrapper(new WaitForEndOfFrame());
//etc
In Current or MoveNext, I check for the type of the yielded value, and store the frame if it is a YieldWrapper.
object Current {
get {
object current = Child.Current
if (current is not YieldWrapper wrapper) return current;
Trace = wrapper.Trace;
return wrapper.Value;
}
}
-
allow the user to set a flag on the coroutine that will break on the next MoveNext call. Users can then move into the MoveNext call to see the current state.
-
display the hidden "<>1__state" value of the iterator, which gives a rough idea of the state of the coroutine, but cannot be mapped to any line code.
Obviously they all have drawbacks
-
Any yield statement that's not wrapped cannot be jumped to. Lots of needless allocations even in Release mode. Otherwise the best hope for a solution.
-
Cannot see current state, only next one. Useless if a "WaitWhile" instruction is blocked and you want to find which one.
-
Cannot actually be used as is, only by comparing it to others. Can tell you if a coroutine is actually changing state, or staying in the same one.
Potential ideas:
-
Find some way to get the stacktrace of a method before or after calling the method. If that were possible, I could easily do this with MoveNext and get a file name and line.
-
Find some hidden reflection magic to turn that "<>1__state" into a file position. Current research tells me "<>1__state" is not documented, not guaranteed to exist and might kill my dog if I rely on it in any way shape or form.
-
Instead of displaying said state, store it over multiple iterations to build a map of the iterator. But since I can never get a guaranteed stack trace inside the user coroutine, not actually that helpful. Could be used to infer that some states yield to more stalling maybe.
-
Simply use UniRx instead
Aucun commentaire:
Enregistrer un commentaire