Async design with Unity3D

79,486
-1

Published on

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

Published in: Technology
1 Comment
69 Likes
Statistics
Notes
No Downloads
Views
Total Views
79,486
On Slideshare
0
From Embeds
0
Number of Embeds
27
Actions
Shares
0
Downloads
140
Comments
1
Likes
69
Embeds 0
No embeds

No notes for slide

Async design with Unity3D

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

    Clipping is a handy way to collect important slides you want to go back to later.

×