How to force IEnumerator methods to be called only with StartCoroutine
I have a simple coroutine:
IEnumerator DoThingsOvertime () { yield return new WaitForSeconds(2); //Do stuff }
But often I forget to use StartCoroutine(DoThingsOvertime)(); and instead use DoThingsOvertime();
How can I make sure the caller of this function must write StartCoroutine() before it?
Answer by Dave-Carlile · Nov 07, 2015 at 08:48 PM
You can create a wrapper function for that. Expose it as public while keeping the Coroutine private. Start the Coroutine inside the wrapper function...
public Coroutine DoThingsOverTime()
{
return StartCoroutine(DoThingsOverTimeInternal());
}
private IEnumerator DoThingsOverTimeInternal()
{
yield return WaitForSeconds(2);
// do stuff...
}
Answer by Statement · Nov 07, 2015 at 09:21 PM
How can I make sure the caller of this function must write StartCoroutine() before it?
I'll mention some topics here like:
Naming Conventions
Argument Conventions
Run-time code analysis
Compile-time code analysis
Assembly post-processing
Alternatives to coroutines
Well, this is probably a far fetch but I thought I'd give you a rather silly answer that could answer the question but it's likely going to be a hassle to set it up. This is not the only way to grab the attention of a developer but it should be the cleanest. I'll throw in concerns to various approaches where I can forecast them.
There is nothing that makes a Coroutine method any different from a plain old method just returning an IEnumerator, so you need some way to declare that your method must be used in a certain way. One way would be to adopt a naming convention to alert the developer of intended usage. Another way would be to require the caller to pass an argument that allows you to start the coroutine from inside the method. If you for any reason want to keep your names intact and readable and get the message across to the developer, you could write your own Attribute that you put on the method.
[CoroutineAttribute]
public IEnumerator DoThings() { ...
Now, on its own it does nothing. But you can use System.Reflection to scour through the assembly and look for methods that use this attribute. Now you have a list of MethodInfos for which you want to apply a rule to.
The second pass would be to reflect into all the IL instructions of all methods of all assemblies and see if any of them invoke a CoroutineAttribute method. If it does, you have to track the variable and see if the result never ends up as an argument to StartCoroutine.
Although, this poses a problem. What if I want to do something like this:
StartCoroutine(RandomCoroutine());
public IEnumerator RandomCoroutine()
{
if (Random.Range(0, 2) == 0)
return Coroutine1(); // error, must StartCoroutine
else
return Coroutine2(); // error, must StartCoroutine
}
[CoroutineAttribute]
public IEnumerator Coroutine1() { ... }
[CoroutineAttribute]
public IEnumerator Coroutine2() { ... }
Now, RandomCoroutine will be a violation of your rule, although it is perfectly fine for coroutines to be used this way, although perhaps it's a rare case. Also, you can invoke a Coroutine, store the reference to the IEnumerator somewhere, and later pass it to StartCoroutine. Now it becomes impossible to keep track of it because if any other method has access to that variable, it could be transported all across the system. But it is possible to enforce rules usage and you could also create an extra attribute [IgnoreCoroutineAttribute]
that can be placed on the caller, in case the rules are too strict.
You can run the code analysis every time the assemblies have been reloaded, or look into if it's possible to add extra rules to the compiler to actually generate compiler warnings or errors.
You could also post process the compiled assembly such that if it's a nested IEnumerator call, inject code to StartCoroutine(var). This is possible with Mono.Cecil. I used Mono.Cecil in the past to generate warnings if a transform was moved which had a collider but no rigidbody as part of an exploration of catching silly mistakes before the profiler had the warning triangles in it. This will however feel strange as it'll start to behave like JavaScript (UnityScript) in the manner that you no longer need to call yield return StartCoroutine(Foo()) but can write yield return Foo() or just Foo() directly to start it.
I wouldn't recommend it, but it is within the realms of possibility. It's a little cool to post-process compiled assemblies. Your developers would probably hate you for breaking conventions and shattering concepts that everyone knew how they worked until this magic came along. Also a very good source to introduce new bu..features to your project!
But, in the end you can also throw coroutines out of the window and adopt your own system which mimics coroutines. In the end, it's little more than using IEnumerator.MoveNext and testing if the current value is a YieldInstruction or a Coroutine (from StartCoroutine). It's possible to use post processing with your custom types or setting up a small framework for chaining routines together. You could also think about creating a custom DSL, although this is likely a very specialized route of attack and doesn't really apply to your general question.
Your answer
Follow this Question
Related Questions
[C#] Coroutine just won't stop! 2 Answers
"Can't add script behaviour AICharacterControl. The script needs to derive from MonoBehaviour!" ? 0 Answers
What is Coroutine, Yiel and IENumerator? 1 Answer
Instantiate object through a coroutine problem 1 Answer
"Can't add script behaviour AICharacterControl. The script needs to derive from MonoBehaviour!" ? 2 Answers