Async design with Unity3D
-
Upload
kouji-hosoda -
Category
Technology
-
view
72.457 -
download
1
description
Transcript of Async design with Unity3D
ロードオブナイツで使ってる
Unity非同期処理デザイン
++C++; 岩永信之
2012/10/12
コルーチンイテレーター構文とUnityのコルーチン
ゲーム ループ
● ゲームでは、どこか大元でループが回ってる
while (isAlive){ // 固定 FPS なら、所定の時間が来るまで Sleep
gameTime = … // ゲーム時間を進める
foreach (var obj in gameObjects) { obj.Update (gameTime); }}
1フレームに1回よばれる処理
重たい処理
● フレームレートよりも時間がかかる処理をしちゃダメ
string Load(string path){ // 30ミリ秒くらいかかるものとする
var data = ファイルからバイナリロード(path);
// これも30ミリ秒くらいかかるものとする
return デシリアライズ(data);
// 30 FPSだと、このメソッドは33ミリ秒以内に終えないと処理落ち}
2回に分けたい
ダメな例
そこで、イテレーター
● イテレーターをコルーチンとして使う
IEnumerator Load(string path, Action<string> callback){ var data = ファイルからバイナリロード(path);
yield return null;
callback(デシリアライズ(data));
yield return null;}
ゲーム ループ中で、毎フレームMoveNextを呼んでもらう
returnの代わりにコールバック呼び出し
1フレーム目
2フレーム目
before/after (1)
● メソッド宣言
● 戻り値はIEnumerator固定● 本来の戻り値はcallbackごしに返す
string Load(string path)
IEnumerator Load(string path, Action<string> callback)
before
after
before/after (2)
● return
● returnステートメントの代わりにcallback呼び出し
before
after
return result;
callback(result);
before/after (3)
● 呼び出し側
● 戻り値を直接受け取れない● 形式上の戻り値(IEnumerator)はコルーチン起動のた
めに使う● 匿名関数を使って後続の処理をつなぐ
var x = Load("path"); …(後続の処理)
StartCoroutine(Load("path", x => { …後続の処理 });
before
after
TASKクラス
.NET Framework 4のTaskクラスを参考に、Unityコルーチンをラッピング
問題
● 複数のコルーチンを扱いにくい
StartCoroutine(A);StartCoroutine(B);
● A, B両方が完了するのを待ちたいときはどうする?● Aの完了後にBを開始したいときはどうする?
● 特に、Aの(本来の)戻り値をBで使いたいときは?● エラーの伝播を考えるとさらに面倒
Taskクラス
● こういうクラスを用意
● 継続処理の登録● (本来の)戻り値やエラーの伝播
public class Task<T> : IEnumerator{ IEnumerator Routine; public T Result { get; } public Exception Error { get; }
public void OnComplete(… callback);
public Task<T> ContinueWith<U>(… continuation);}
実例
● ロードオブナイツのマップ● 64万要素程度の配列をJSONで受け取ってた
● 通信もコルーチン● 通信エラーが発生する可能性あり
● デコードもそこそこ高負荷なのでコルーチン化したい● (行単位でデコード、1フレームに1行ずつとか)● 通信の結果を使う● デコード エラーが発生する可能性あり
● ローカル ストレージにキャッシュしたい● IOエラーが発生する可能性あり
※ 最新バージョンではデータを小分けで受信するように改善され、デコード処理はコルーチンではなくなっている
Task利用例(戻り値)
● (本来の)戻り値の伝播
IEnumerator A(Action<int> callback);IEnumerator B(int x, Action<string> callback);IEnumerator C(string s);
A, B, C の順で実行したいA の戻り値(int)を B で、B の戻り値(string)を C で使いたい同期処理なら C(B(A()); だけで書けるもの
var t = new Task<int>(A) .ContinueWith<string>(B) .ContinueWith(C);
StartCoroutine(t);
同期処理と比べて
● 同期処理との対比
IEnumerator A(Action<int> callback);IEnumerator B(int x, Action<string> callback);IEnumerator C(string s);
var t = new Task<int>(A) .ContinueWith<string>(B) .ContinueWith(C);
StartCoroutine(t);
int A();string B(int x);void C(string s);
var x = A();var s = B(x);C(s);
あるいは
C(B(A()));
Task利用例(例外処理)
● コルーチン内で発生した例外も受け取れる
var t = new Task<int>(A) .ContinueWith<string>(B) .ContinueWith(C) .OnComplete(t => {
if (t.Error != null) … });
StartCoroutine(t);
● A, B, C のどこかで例外が発生した場合、そこでコルーチンの実行は中断。
● 発生した例外はErrorプロパティにセットされる
以上