【Unite Tokyo 2019】たのしいDOTS〜初級から上級まで〜

1.3K Views

September 25, 19

スライド概要

2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。
安原 祐二(ユニティ・テクノロジーズ・ジャパン合同会社)

こんな人におすすめ
・そろそろDOTSを学んでおきたい方
・DOTSに取り組む余裕はないが現状を確認しておきたい方
・Unity.Physicsの概要を知りたい方

受講者が得られる知見
・DOTSの基本
・DOTSの内部構造
・Unity.Physicsの概要

Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

たのしいDOTS 〜初級から上級まで〜 ユニティ・テクノロジーズ・ジャパン 安原 祐二

2.

DOTSとは Data Oriented Technology Stack Unityの新しい基盤

3.

DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? データ指向って?

4.

DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? 現在はプレビュー版 データ指向って? これから使いやすくなる

5.

DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? 現在はプレビュー版 データ指向って? これから使いやすくなる いますぐ始めなくていい あとで見返そう

6.

DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? 現在はプレビュー版 データ指向って? これから使いやすくなる いますぐ始めなくていい あとで見返そう この技術は おもしろい!

7.

前半: メモリの戦い 後半: 驚異のUnity Physics

8.

メモリの戦い 〜その歴史〜

9.

限られた土地を使いまわそう

10.

役所 確保(allocation) 役所は確保状況を把握 ただし中身は関知しない

11.

役所 確保(allocation)

12.

役所 解放を報告 解放(free)

13.

役所 再利用

14.

メモリも ストレージも 事情は同じ

15.

メモリも ストレージも 事情は同じ メモリは 格段に複雑な事情 を抱えている 利用者にアドレスを渡してしまっているので・・・

16.

アドレスが埋まっている 問題は解放

17.

解放! !? 事故発生 歴史上、さまざまな取り組みが

18.

C#「GCにお任せする」 解放!

19.

C#「GCにお任せする」 解放! マネージドメモリ (managed memory) 解放を呼ばせない

20.

ポインタとはこれ のこと マネージドメモリ (managed memory)

21.

ポインタとはこれ のこと int* a; int マネージドメモリ (managed memory)

22.

ゲームでこんな複雑な必要あるだろうか? マネージドメモリ (managed memory)

23.

Unmanaged Memoryが要求される Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory

24.

ネイティブコンテナのトリック 〜Unmanagedに挑戦〜

25.

〜ネイティブコンテナ(NativeContainer)とは〜 Chunk NativeContainer Unmanagedメモリを保持する機構 ただし自分で解放する必要 Unmanaged Memory

26.

〜ネイティブコンテナ(NativeContainer)とは〜 Chunk NativeContainer Unmanagedメモリを保持する機構 ただし自分で解放する必要 Unmanaged Memory オブジェクトではなく、アルゴリズムで確保する (生存期間をアルゴリズムで決める) 秩序は守られる!

27.

代表例:NativeArray NativeArray structの配列 必ずstruct(値型)! class(参照型) はC#の機構により マネージドメモリになってしまう

28.

代表例:NativeArray NativeArray structの配列 必ずstruct(値型)! class(参照型) はC#の機構により マネージドメモリになってしまう var a = NativeArray<float>(8, Allocator.Persistent) structならメモリがひとかたまりに! C#の特徴を利用

29.

ネイティブコンテナもstruct リファレンスマニュアルより ところで struct って恐くないですか?

30.

structの罠 var a = new Vector3(1, 2, 3); func(a); aはコピーされてfuncに渡される funcの中で変更してもaに影響なし

31.

structの罠 var a = new Vector3(1, 2, 3); func(a); aはコピーされてfuncに渡される funcの中で変更してもaに影響なし var a = new NativeArray<float>(32); func(a); aはコピーされてfuncに渡される あれ? これは大丈夫?

32.

ネイティブコンテナの実装は必ずポインタ! unsafe struct NativeArray<T> { void ptr; } * ポインタをコピーしても参照先は同じ var a = new NativeArray<float>(32); func(a); Unmanaged Memory funcの変更が伝わる!

33.

ベテランでも引っかかるstructの罠 よーし ネイティブコンテナ 自作しちゃうぞー struct MyNativeArray<T> { NativeArray<T> na; int Length; }

34.

ベテランでも引っかかるstructの罠 よーし ネイティブコンテナ 自作しちゃうぞー struct MyNativeArray<T> { NativeArray<T> na; int Length; } これは大失敗! なぜでしょう?

35.

struct MyNativeArray<T> { NativeArray<T> na; int Length; } var a = new MyNativeArray<float>(32); func(a); Length Length Unmanaged Memory

36.

struct MyNativeArray<T> { NativeArray<T> na; int Length; } var a = new MyNativeArray<float>(32); func(a); Length Length Lengthが 共有されてない!! Unmanaged Memory コンパイルエラーも出ないし プログラムは正しいけれど 100%バグ になる

37.

struct MyNativeArray<T> { NativeArray<T> na; int Length; } Lengthも ポインタの先に置く Length Unmanaged Memory unsafe struct MyNativeArray<T> { void ptr; * } ネイティブコンテナ の中は必ずポインタ!

38.

最重要!チャンクの設計 〜ゲームのためのメモリ〜

39.

ゲームのオブジェクトを Unmanaged Memory Chunk Chunk で扱いたい NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory

40.

現代はメモリが広い! Unityの登場時(2005)とは事情が異なる Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory

41.

現代はメモリが広い! Unityの登場時(2005)とは事情が異なる Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ Chunk (managed memory) Unmanaged Memory 増えたメモリは主にアセットに利用される ゲームロジックのメモリは大きく変わっていない メモリの再利用をギリギリまで追求する必要はない

42.

ところで・・・ もし、確保するサイズが一律で同じだったら 確保・解放にまつわる 多くの問題が解決

43.

ある物体は3つのコンポーネントで構成されているとする A B C ABCはサイズが異なる

44.

ある物体は3つのコンポーネントで構成されているとする A B C ABCはサイズが異なる この物体専用の領域を用意できれば・・・

45.

ある物体は3つのコンポーネントで構成されているとする A B C ABCはサイズが異なる この物体専用の領域を用意できれば・・・ A B C 確保単位が一定に! ある程度のメモリの無駄は許容する

46.

A B C コンポーネント種別に並べる A struct Entity B C 必ず16KiB これが チャンク の姿

47.

別の物体は4つのコンポーネントで構成 A B C D チャンクサイズ16KiBは変わらない 割り振りがさっきと異なる 格納できる数が変わる このチャンクには3つ格納できる

48.

組み合わせをアーキタイプ(Archetype)と呼ぶ A B C A B C アーキタイプABC D アーキタイプABCD アーキタイプABC用チャンク アーキタイプABCD用チャンク

49.

生成 A B C 専用チャンクに格納(満杯ならもう一本追加) A B C このチャンクはABCD用なので無関係!

50.

あるオブジェクトを削除する場合 削除 削除 削除 削除 たったの2ステップ

51.

ステップ1 チャンクに格納されているオブジェクト数を減らす 3→2

52.

ステップ2 最後の要素を消したい場所に上書きコピー 削除完了!

53.

ステップ2 最後の要素を消したい場所に上書きコピー 削除完了! 決して歯抜けにならない 順序は保たれていない

54.

ねえねえ マネージャ 通して 他のオブジェクトに直接参照させない

55.

ねえねえ マネージャ 通して 他のオブジェクトに直接参照させない Chunk NativeContainer Chunk NativeContainer NativeContainer Chunk マネージド Chunk Unmanaged

56.

Systemの概念 〜データ指向とは〜

57.

データは用意できた Chunk Chunk Chunk Chunk Chunk Chunk Chunk Chunk System どうやって処理させよう?

58.

“System”を記述する Chunk Chunk “System”の3つの働き • チャンクを選ぶ • 入力と出力を決める • 処理する Chunk Chunk Chunk Chunk Chunk Chunk System

59.
[beta]
使用コンポーネントを決める
条件:BDをもつチャンク

アーキタイプABC用チャンク

query = GetEntityQuery(new EntityQueryDesc() {
All = new ComponentType[] {
ComponentType.ReadOnly<B>(),
ComponentType.ReadWrite<D>(),
}, });

アーキタイプBD用チャンク

アーキタイプABCD用チャンク

60.
[beta]
使用コンポーネントを決める
条件:BDをもつチャンク

アーキタイプABC用チャンク

query = GetEntityQuery(new EntityQueryDesc() {
All = new ComponentType[] {
ComponentType.ReadOnly<B>(),
ComponentType.ReadWrite<D>(),
}, });

アーキタイプBD用チャンク

アーキタイプABCD用チャンク

61.
[beta]
使用コンポーネントを決める
条件:BDをもつチャンク

アーキタイプABC用チャンク

query = GetEntityQuery(new EntityQueryDesc() {
All = new ComponentType[] {
ComponentType.ReadOnly<B>(),
ComponentType.ReadWrite<D>(),
}, });

アーキタイプBD用チャンク

アーキタイプABCD用チャンク

62.

Bと Dを 処理する System

63.

Bと Dを 処理する System

64.

処理する System バラバラに処理しているように見えるが きっちりオブジェクト単位で渡っている

65.

Jobを発行 Job Job Job System メモリの連続が考慮されている

66.

Job Job Job B:入力(書き込まない) D:出力(書き込む) 入出力を明示 System

67.

データの流れ B System D

68.

データの流れ B System D System A ネイティブコンテナは アルゴリズムで使う Native Container

69.

データの流れ B System D System A ネイティブコンテナは アルゴリズムで使う Native Container System Rotation

70.

いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; データに注目 B A System D System Native Container System Rotation

71.

いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; B A System テストが 容易! D System Native Container System Rotation

72.

いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; B A テストが 容易! System D System Native Container System Rotation

73.

いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; データに注目 B A System D System Native Container System Rotation

74.

参照はエンティティ 〜ポインタの代わりに〜

75.

参照の問題をいかにして解決するか マネージドメモリ (managed memory)

76.

オブジェクトには必ずEntityがある Entity チャンクにも入っている Entity

77.

他のオブジェクトはEntityで参照する でも、Entityはポインタ ではない

78.

5 8 8 4 5 2 5 Entityの正体はインデクス(ある配列中の位置)

79.

5 8 8 4 5 2 5 Entityの正体はインデクス(ある配列中の位置) Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン 1 2 3 4 5 EntityManagerが持つテーブル

80.

5 8 8 4 5 2 5 Entityの正体はインデクス(ある配列中の位置) →ポインタ同様に高速 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン 1 2 3 4 5 EntityManagerが持つテーブル

81.

削除は? 5 8 8 4 5 2 5 削除されたインデクスが次の生成で使いまわされたらアウトだよね Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン 1 2 3 4 5

82.

削除は? 5 8 8 4 5 2 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン+1 1 2 3 4 5 削除! 5

83.

実はEntityはインデクスとバージョン(計64bit) 5 1 インデクス(Index) バージョン(Version)

84.

生成時に与えられた バージョン1が・・・ 4 5 0 1 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン+1 1 2 3 4 5 5 8 1 1 8 0 2 5 4 1 削除時に増加して2になっているので・・

85.

このEntityの 情報ください 4 5 1 1 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン+1=2 1 2 3 4 5 5 8 1 1 8 1 2 5 1 1 バージョン1? あなたの知っている5番はもういません!

86.

Unmanagedな世界でも・・・ Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory 速度を兼ね備えた見事な管理システムを実現

87.

驚異のUnity Physics

88.

C#で物理シミュレーションを フル実装しちゃおうぜ 正気かしら

89.

Unity Physics 誕生 ・決定的(deterministic) ・ステートレス ・シミュレーションループ独立 こういうのを 待っていた!

90.

キューブ13824個 on high-end PC 通常の物理エンジン 約15fps Unity Physics 約60fps

91.

ワールドを分離して自在にループ実行

92.

https://github.com/Unity-Technologies/DOTS-Shmup3D-sample

94.

Unity Physicsは素晴らしい! が、現時点(Ver.0.2.4)では気配りが足りない けっこう突き放してくる! これから使いやすくなっていきます

95.

注意点その1 静的なコライダーをスクリプトから動かす場合 コリジョンの更新は PhysicsVelocity があることが条件 AddComponentでPhysicsVelocityを付加しよう

96.

注意点その2 外力 (AddForce) がない その代わり力積 (ApplyLinearImpulse) がある dv F= m 外力 dt F dt = mdv 力積 外力にdtを掛けて力積 (ApplyLinearImpulse) を呼べばいい

97.

実装の確認は避けられない(現時点では) public static void ApplyLinearImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Linear += impulse * massData.InverseMass; } 1 つまり dv = Fdt m

98.

実装の確認は避けられない(現時点では) public static void ApplyLinearImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Linear += impulse * massData.InverseMass; } ForceModeの対応: Force Impulse Acceleration VelocityChange 1 つまり dv = Fdt m velocityData.ApplyLinearImpulse(force*deltaTime); velocityData.ApplyLinearImpulse(impulse); velocityData.Linear += acceleration*deltaTime; velocityData.Linear += velocity;

99.

注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル

100.

注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル 直交座標系の回転要素がない慣性テンソル (対角化された状態)

101.

注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル 直交座標系の回転要素がない慣性テンソル (対角化された状態) 慣性テンソル空間でトルクを与える必要

102.

注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル 直交座標系の回転要素がない慣性テンソル (対角化された状態) 慣性テンソル空間でトルクを与える必要 特殊な形状でなければAddRelativeTorqueと考えて良い

103.

参考:慣性テンソルについての解説動画 〜Unity道場〜 物理シミュレーション完全マスター https://learning.unity3d.jp/1167/ 34:20から

104.

注意点その4 軸単位の拘束(Constraint)の実現方法 拘束しないとこうなるよね

105.

物体の位置・姿勢の更新は すべてApply*Impulse経由 という実装になってるので public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; }

106.

物体の位置・姿勢の更新は すべてApply*Impulse経由 という実装になってるので public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } InverseInertia の設定で回転拘束を実現 例: new PhysicsMass { … InverseInertia = new float3(0, 1, 0); … } Y軸以外の角速度を 発生させない 慣性テンソルに回転がある場合は注意

107.

まとめ

108.

DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる

109.

DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる • 現代のCPUを強く意識 64bit CPUを前提にできつつある

110.

DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる • 現代のCPUを強く意識 64bit CPUを前提にできつつある • テスタビリティ・スケーラビリティを強く意識 データの一意性+マルチコア実行

111.

DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる • 現代のCPUを強く意識 64bit CPUを前提にできつつある • テスタビリティ・スケーラビリティを強く意識 データの一意性+マルチコア実行 DOTSはどんどん使いやすくなっていく そのときに始めれば十分 ご期待ください!

112.

おしまい