Start as a Coroutine - Intended behaviour?

I say “Intended behaviour” with a ‘?’. I’m sure it is intended but I don’t think it’s a very intuitive behaviour given the stated execution order of Start in the reference and it’s purpose.

The behaviour is demostrated by this script.

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour {

    bool firstCall = true;

    IEnumerator Start () {
        print("Start of Start");
        yield return StartCoroutine(DoStuff());
        print("End of Start");
    }


    public IEnumerator DoStuff()
    {
        for (int i = 0; i < 3; i++)
        {
            print("Do Stuff " + i);
            yield return null;
        }
    }


    void Update()
    {
        if (firstCall)
        {
            firstCall = false;
            print("First Update call");
        }
    }
}

This script results in

Start of Start
Do Stuff 0
First Update call
Do Stuff 1
Do Stuff 2
End of Start

I would expect

Start of Start
Do Stuff 0
Do Stuff 1
Do Stuff 2
End of Start
First Update call

Update really shouldn’t be called before Start has completed given that Start is for initialisation.

Thats exactly why you should NEVER yield start / awake.
The rest in the startup chain gives a “damned” on if you yield it or not.
The startup logic of the component will call awake, then start and then update.
if your wait forces it to wait beyond its frame border in which it was called … well then you can do that but it will be your problem, the engine does use SendMessage to call start, not “yield Start” to ensure it has fully terminated.

Unfortunately my psychic powers aren’t of sufficient level for me to be able to just know these things by magic.

It’s possible to use Start as a coroutine and there is nothing in the documentation to indicate that you shouldn’t do it. Indeed the documentation only says you can’t use Update and FixedUpdate as coroutines.

You can’t use Awake as a coroutine (in 3.0 at least) as Unity will give you a “Script error: Awake() can not be a coroutine.” message. You get no such message if you use Start as a coroutine.

It either needs to be prevented from happening in the same way as Awake or the documentation needs updating to reflect the way Start as a coroutine is handled.

Start is always called before Update, however Update is always called every frame; it can’t be delayed in any way. I wouldn’t logically expect your second scenario, given how Update is described in the docs. Every frame does literally mean every frame. There’s no problem yielding in Start, as long as you understand that Update will never be delayed, so anything after a yield in Start will happen after Update. If you need Update to be delayed, then you don’t want to use Update at all. Perhaps you’re looking for something like

function Start () {
    // do some initialization stuff
    yield SomeCoroutineThatDelays();

    while (true) {
        // do something 
        yield;
    }
}

–Eric

I would, guess it’s a difference in how we read it. (edit: well reading it 10 more times I guess I may have been making assumptions based on what I thought was logical more than what it actually says ;))

I’d disagree with the first part based on the second part. Updating a component shouldn’t be able to occur before initialisation is complete in my opinion.

I was looking at it from the point of starting a long processing task in a coroutine that would eventually result in a callback with some data from that coroutine. I was thinking of waiting for the coroutine to finish in Start as the component doesn’t need updating till after it has the data.

My quick tests to see if it would work revealed the behaviour above which I thought was counter intuitive. Once I got past that thought I realised that I wouldn’t normally do that sort of long running task in a constructor so I probably shouldn’t be doing it in a component init methods either anyway. Which makes the above irrelevant to what I was doing but an interesting new thing i’ve learnt and will be aware of in the future.

Well you can use Start as a Coroutine all you want, you just have to expect a Coroutine to be the result.

The Docs say Start is called before Update, but they say nothing about Update waiting for Start to finish (though that is assumed in any code not using Coroutines). Start was called first, and then registered as a Coroutine and updated under the Coroutine scheduler (which doesn’t really care what the initial function was). Update was called after Start was processed.

Although as an interesting little idea, to get the logic you were expecting you could do this:

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour {

    bool firstCall = true;

    IEnumerator Start () {
        print("Start of Start");
        enabled = false;
        yield return StartCoroutine(DoStuff());
        print("End of Start");
        enabled = true;
    }


    public IEnumerator DoStuff()
    {
        for (int i = 0; i < 3; i++)
        {
            print("Do Stuff " + i);
            yield return null;
        }
    }


    void Update()
    {
        if (firstCall)
        {
            firstCall = false;
            print("First Update call");
        }
    }
}

Even more fun Behaviour that is initially Counter-Intuitive, but makes sense once you understand the inner workings is this:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MyClass : MonoBehaviour 
{
	void Start()
	{
		transform.StartCoroutine(MyCoroutine());
	}
	
	public IEnumerator MyCoroutine()
	{
		while(this != null)
		{
			yield return new WaitForSeconds(1.0f);
		}
	}
}

This doesn’t return a null reference Exception, and this will equal null and exit the loop.