16
ロードオブナイツで使ってる Unity非同期処理デザイン ++C++; 岩永信之 2012/10/12

Async design with Unity3D

Embed Size (px)

DESCRIPTION

UnityでHTTP通信をするときに割りと困りがちな非同期処理の設計についてロードオブナイツで使っているデザインを公開します。スライド作成は++c++;の中の人。

Citation preview

Page 1: Async design with Unity3D

ロードオブナイツで使ってる

Unity非同期処理デザイン

++C++; 岩永信之 

2012/10/12

Page 2: Async design with Unity3D

コルーチンイテレーター構文とUnityのコルーチン

Page 3: Async design with Unity3D

ゲーム ループ

● ゲームでは、どこか大元でループが回ってる

while (isAlive){ // 固定 FPS なら、所定の時間が来るまで Sleep

gameTime = … // ゲーム時間を進める

foreach (var obj in gameObjects) { obj.Update (gameTime); }}

1フレームに1回よばれる処理

Page 4: Async design with Unity3D

重たい処理

● フレームレートよりも時間がかかる処理をしちゃダメ

string Load(string path){ // 30ミリ秒くらいかかるものとする

var data = ファイルからバイナリロード(path);

    // これも30ミリ秒くらいかかるものとする

return デシリアライズ(data);

    // 30 FPSだと、このメソッドは33ミリ秒以内に終えないと処理落ち}

2回に分けたい

ダメな例

Page 5: Async design with Unity3D

そこで、イテレーター

● イテレーターをコルーチンとして使う

IEnumerator Load(string path, Action<string> callback){ var data = ファイルからバイナリロード(path);

    yield return null;

callback(デシリアライズ(data));

    yield return null;}

ゲーム ループ中で、毎フレームMoveNextを呼んでもらう

returnの代わりにコールバック呼び出し

1フレーム目

2フレーム目

Page 6: Async design with Unity3D

before/after (1)

● メソッド宣言

● 戻り値はIEnumerator固定● 本来の戻り値はcallbackごしに返す

string Load(string path)

IEnumerator Load(string path, Action<string> callback)

before

after

Page 7: Async design with Unity3D

before/after (2)

● return

● returnステートメントの代わりにcallback呼び出し

before

after

return result;

callback(result);

Page 8: Async design with Unity3D

before/after (3)

● 呼び出し側

● 戻り値を直接受け取れない● 形式上の戻り値(IEnumerator)はコルーチン起動のた

めに使う● 匿名関数を使って後続の処理をつなぐ

var x = Load("path"); …(後続の処理)

StartCoroutine(Load("path", x => { …後続の処理 });

before

after

Page 9: Async design with Unity3D

TASKクラス

.NET Framework 4のTaskクラスを参考に、Unityコルーチンをラッピング

Page 10: Async design with Unity3D

問題

● 複数のコルーチンを扱いにくい

StartCoroutine(A);StartCoroutine(B);

● A, B両方が完了するのを待ちたいときはどうする?● Aの完了後にBを開始したいときはどうする?

● 特に、Aの(本来の)戻り値をBで使いたいときは?● エラーの伝播を考えるとさらに面倒

Page 11: Async design with Unity3D

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);}

Page 12: Async design with Unity3D

実例

● ロードオブナイツのマップ● 64万要素程度の配列をJSONで受け取ってた

● 通信もコルーチン● 通信エラーが発生する可能性あり

● デコードもそこそこ高負荷なのでコルーチン化したい● (行単位でデコード、1フレームに1行ずつとか)● 通信の結果を使う● デコード エラーが発生する可能性あり

● ローカル ストレージにキャッシュしたい● IOエラーが発生する可能性あり

※ 最新バージョンではデータを小分けで受信するように改善され、デコード処理はコルーチンではなくなっている

Page 13: Async design with Unity3D

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);

Page 14: Async design with Unity3D

同期処理と比べて

● 同期処理との対比

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()));

Page 15: Async design with Unity3D

Task利用例(例外処理)

● コルーチン内で発生した例外も受け取れる

var t = new Task<int>(A) .ContinueWith<string>(B) .ContinueWith(C) .OnComplete(t => {

if (t.Error != null) … });

StartCoroutine(t);

● A, B, C のどこかで例外が発生した場合、そこでコルーチンの実行は中断。

● 発生した例外はErrorプロパティにセットされる

Page 16: Async design with Unity3D

以上