複数のIEnumratorを時間管理しつつ処理するCoroutine

# 作ったもの
[TaskScheduler](https://gist.github.com/divide-by-zero/5b2e7f333978c0f501cd)

# 作ろうと思ったきっかけ
ステージクリア型のパズルゲームのステージセレクト画面で縮小したマップを一気に3x4の12個動的に読み込みつつ、表示。みたいなことをStartCoroutineでやっていたらFPSがガタ落ちでUIの反応が悪くなってしまったので。
![TaskScheduler.PNG](https://qiita-image-store.s3.amazonaws.com/0/37184/dc4189ca-f104-df91-c617-bd367cee8049.png)



# 例えば
あるGameObjectはあるPrefabを10個`Instantiate`して子要素として付けておく必要があるとする。
しかしUnityの`Instantiate`は重い。　なので、`StartCoroutine`を使って、`yield return null`しながら1個づつ`Instantiate`することにした。（よくある）

```csharp
public class HogeObject : MonoBehaviour
{
	public GameObject prefab;
	public void Start()
	{
		StartCoroutine(CreateIterator());
	}

	private IEnumrator CreateIterator()
	{
		for(int i = 0;i < 10;++i)
		{
			var go = Instantiate(prefab) as GameObject;//heavy work
			go.transform.SetParent(this.transform,false);
			yield return null;
		}	
	}
}
```

しかし、この`HogeObject`は画面上に100個あった！（よくある（？））
すると、`StartCoroutine`が100回呼ばれ、1フレームに100個`Instantiate`されることになってプチフリーズのような状態に。

# 何が悪いかって
Unityの`StartCoroutine`の仕組みには横の繋がりがなく、独立して動いているので、**何個`StartCoroutine`されてようが知ったこっちゃない**ということ。

# なので
冒頭に戻る。　自前StartCoroutineの作成。
上記例はこうなる。

```csharp
public class HogeObject : MonoBehaviour
{
	public GameObject prefab;
	public void Start()
	{
		//StartCoroutine(CreateIterator());//この1行が
		TaskScheduler.Instance.AddIterator(CreateIterator());//これになる
	}

	private IEnumrator CreateIterator()
	{
		for(int i = 0;i < 10;++i)
		{
			var go = Instantiate(prefab) as GameObject;//heavy work
			go.transform.SetParent(this.transform,false);
			yield return null;
		}	
	}
}
```

`StartCoroutine`だった箇所を`TaskScheduler.Instance.AddIterator`に変える事で、TaskScheduler(Singletone)で1本だけ走っている`StartCoroutine`が時間を計測しつつよしなにCreateIteratorを処理するので、HogeObject が100個あっても1000個あっても処理落ちはしない（はず）。

# 仕組み
* Unityの`StartCoroutine`は`IEnumerator`を渡して実行しているが、仕組みとしては`IEnumerator#MoveNext`を呼ぶ事で処理を中断→継続させている。この`MoveNext`を呼ぶ処理を自分で作れば自前`StartCoroutine`みたいな事が出来る。
* そこで、複数の`IEnumrator`を受け取って、順次`MoveNext`を呼んでいく＋処理時間が一定時間を超えていたら`yield return null;`する`IEnumrator`を`StartCoroutine`しておく（何言ってんだ）
* すなわち、Unityの`StartCoroutine`の拡張をUnityの`StartCoroutine`を使うことで実装した感じ（何言ってんだその２）。

```csharp
        public void Start()
        {
            StartCoroutine(Iterator());
        }
        private IEnumerator Iterator()
        {
            while (true)
            {
                var ts = Time.realtimeSinceStartup;
                do
                {
                    if (iteratorList.Count <= 0) break;
                    foreach (var itr in iteratorList.ToList())
                    {
                        if (itr.Value.MoveNext() == false)
                        {
                            iteratorList.Remove(itr.Key);
                        }
                        if (Time.realtimeSinceStartup - ts > targetTime) yield return null;
                    }
                } while (Time.realtimeSinceStartup - ts <= targetTime);
                yield return null;
            }
        }
```

# 補足
* Action 渡せるものもついでに作ったので、`TaskScheduler.Instance.AddAction(()=>Instantiate(hogeObj));`のようにラムダ式渡したりなんかも。
* `yield return new WaitForSeconds(ms);` などには未対応。　というか、**処理の負荷分散目的**であって、順次処理目的でのStartCoroutineは普通にStartCoroutineでやってくれれば良いのでは。
* `WWW www;`で　`yield return www;` とかは価値があるけど未対応。　`while(www.isDone == false){yield return null;}`にすれば使えると思う（試してない）
* 基本は独立のStartCoroutineに横のつながりをもたせたなら、Priorityの概念を入れるのが筋だとは思うが未実装。必要に駆られたら作ります。