テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】ECSのEntityの作成やComponentDataの追加等の操作を別スレッドから実行する

今回はECSのEntityを非同期に作成する方法についてです。
主に打規模なシーンのロード等に使用できそうです。

 

 

Entityの生成や破棄、削除はメインスレッド上で行う

ECSでEntityを作成する場合、大抵の場合はEntityManagerを通してEntityを作成したりコンポーネントを追加・削除したりします。EntityManagerは基本的にメインスレッドで動作する設計になっており、ジョブ上からComponentDataやEntityの追加・削除といった処理は実行できません
EntityCommandBufferを利用してジョブから処理の指示を受ける事は可能ですが、処理を実行するのはメインスレッド上で動作するBarrierSystemです。

f:id:tsubaki_t1:20180902194448p:plain

tsubakit1.hateblo.jp

ただし、コレらの処理を非同期で行う方法が用意されていないわけでは無いみたいです。ExclusiveEntityTransactionを使用すれば、別のスレッドでEntityを追加したり削除したりも可能です。

 

ExclusiveEntityTransaction

ExclusiveEntityTransactionは異なるスレッドからEntityを追加・削除する為のAPIです。正確にはEntityManagerを排他(Exclusive)モードにして、他のスレッドからでもアクセス可能にします。

これを実現するために把握しておくAPIは大きく分けて4つあります。

  • BeginExclusiveEntityTransaction
    EntityManagerを排他(Exclusive)モードにする
  • EndExclusiveEntityTransaction
    EntityManagerの排他モードを解除する
  • ExclusiveEntityTransaction
    排他モード時のアクセスに使用する構造体
  • ExclusiveEntityTransactionDependency
    ジョブの依存関係を把握するために便利なメンバ変数
    基本的に排他モードに入ったらコレを設定しておく

 別スレッドで実行する場合、まずBeginExclusiveEntityTransactionで排他モードに移行すると共にExclusiveEntityTransactionを入手します。
後は別スレッドでExclusiveEntityTransaction経由でEntityを追加・削除し、ソレが終わったらEndExclusiveEntityTransactionで通常のモードに戻すという感じです。

なお、基本的に排他モード時にはEntityManagerが使えなくなります。排他時に使用すれば下のようなエラーが出ます。

InvalidOperationException: Access to EntityManager is not allowed after EntityManager.BeginExclusiveEntityTransaction(); has been called.

一応ExclusiveEntityTransactionを使用すればEntityの追加や削除は可能なのですが、あまり効率的じゃなさそうな気がします(主にロックの頻発で)
これを回避するために、EntityManagerとExclusiveEntityTransactionを同時に使用したい場合には別世界の別EntityManagerを使用します

 

ExclusiveEntityTransactionを使用しつつEntityManagerも使いたい

 ECSは基本的に同じシステムは一つしか作られませんが、それは同じ世界に一つという但し書きが付きます。ECSではシステムの上にWorldという概念が存在し、Entityは基本的に何処かのワールドに所属します。
このワールドはStaticを用いなければ保持するバッファが独立しておりパラレルな存在です。…この独立はStaticを使うと破壊されます。Staticはまさに世界の破壊者(ディケイド)か世界の架け橋(ディケイド)な訳です。

f:id:tsubaki_t1:20180902192018j:plain

つまり何が言いたいかというと、この世界のEntityManagerと別世界のEntityManagerは独立しているので、別世界のEntityManagerが排他モードになっても私達の世界のEntityManagerは普通に使えるという事です。
Entityを生産するだけのWorldを用意し、そのWorld運用は別スレッドでのみ行い、その生産物(Entity)をアクティブなWorldが徴収する…という形にすれば、Entityの生産は非同期で行いつつも、普通にEntityの操作が行えるようになるというシナリオです。

f:id:tsubaki_t1:20180902192922g:plain

 

C# Job Systemを使用した例

まずはC# Job Systemを使用した場合の例です。複数フレーム跨ぐ可能性があるので、コルーチンによる遅延実行を使用しています。

gist.github.com

実行すると下のようにEntityが別スレッドで作られます。下のプロファイリング結果はEntityを毎フレーム38,000程作った結果です。開始と終了時(特にEntity移行時)に負荷がかかりますが、Entity生成それ自体は完全に別スレッドで行われています。
また事前にチャンクやアーキタイプを作っておけば、Entityの移し替えもかなり低コストで行えます。

f:id:tsubaki_t1:20180902212707j:plain

内容はジョブにExclusiveEntityTransactionを渡してジョブ内でEntityの作成等を行っているだけです。なおジョブシステムの制約上4フレームを跨ぐような物は作れないので、あまり大規模な物は実は作りづらいです。
そのため、最大個数を決めてジョブを繋いで実行…みたいな形でやることになりそうですが、面倒なら単純にスレッドに投げてしまっても良いのかもしれません。
f:id:tsubaki_t1:20180902202405j:plain

なお非同期ではなく並列で動くか試した所、IJobPralellForは無理そうでしたが、ワールドを複数作って実行したところ普通に動きました。

f:id:tsubaki_t1:20180902220247j:plain

 

Async/Awaitを使用した例

排他モードの際には、Async/Awaitでもちゃんと動作しました。

gist.github.com

 

その他

この機能を使用すれば大規模かつ非同期なシーンのロード等が実現できそうですが、どちらかといえばソレはUnity.Entities.Serialization.SerializeUtility.DeserializeWorldの役目で、本機能はもう少しコンパクトな…例えばプロシージャルに大きな物を作る的な用途なんじゃないかなと予想しています。(中で似たような操作をしていそうですが)

 

追記:

SubSceneを使いましょう

tsubakit1.hateblo.jp

関連

https://forum.unity.com/threads/serialization-questions.534187/

 

【Unity】「開発計画に役に立つUnityロードマップ 」

Unity 2018.1~Unity 2018.3に渡り追加される機能を網羅的に紹介したスライドです。「こんな機能が追加されるのか!」と中々にテンション上がりそうです。
特にNestedPrefabとTerrain改修あたり。

f:id:tsubaki_t1:20180831181400j:plain

スライド

www.slideshare.net

 

アジェンダ

大ざぱな内容はこんな感じです。

  • Unityのリリースとバージョンについて
  • Unityの新しいネットワークゲーム向け機能について
  • 2018.1 までのおさらい
  • 2018.2 以降の新機能

【Unity】Unityでベジェ曲線(パス)を描く方法

f:id:tsubaki_t1:20180831144240j:plain

今回はUnityで曲線を描く方法について紹介します。

 

 

Unity 2018.1でVector Graphicが導入可能に

Unity 2018.1からVector Graphicsのプレビュー版が導入可能になり、SVGといったフォーマットのファイルも使用可能になりました。

ファイルサイズの割にエッジの綺麗な絵を出しやすいのが良いですね

https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20180523/20180523000844.jpg

tsubakit1.hateblo.jp

今回はそのランタイムAPIを使用して線を描画してみます。
なおVector Graphicsはその設計上、UIには使用できません。Graphicコンポーネントを拡張すれば行けるかもしれませんが、云々。

 

Vector Graphicsでベジェ曲線を描画する

Vector Graphicsの導入手順は省略して、ランタイムAPIを使用してパスを描画していきます。

まずパスの元となるオブジェクト郡を用意していきます。操作しやすくするため、アイコンの色も変えておいた方が良いです。P0は特別なので、大アイコンとかにしとくと良いです。P1~P2はベジェ曲線のためのものです。

  • S0-P0(青
  • S0-P1(青
  • S0-P2(赤
  • S1-P0(赤
  • S1-P1(赤
  • S1-P2(緑
  • S2-P0(緑

f:id:tsubaki_t1:20180831154723j:plain

次にサンプルコードを導入して設定していきます。

f:id:tsubaki_t1:20180831155323j:plain

gist.github.com

これでゲームを再生すると、線が描画されるようになります。
一致する色のGameObjectを動かせば線が変化します。

f:id:tsubaki_t1:20180831143950g:plain

なお、曲線はあくまでメッシュで描画しているので、あまり複雑なメッシュを描画すると負荷が上がるかもしれません(余程の量出ない限り大したことがないかもしれませんが…)

f:id:tsubaki_t1:20180831155847j:plain

 

Vector GraphicsのAPIを見てみる

線を描画するAPIを見ていきます。詳しい内容はマニュアルで確認できます。PackageManagerはUnityのバージョンに依存しないので、Package Managerからマニュアルを確認します。(URL的にバージョンでアドレスが変わりそうなので、直接リンクで紹介しにくい所)

f:id:tsubaki_t1:20180831145038j:plain

 

VectorGraphicの要素を構成するのは、概ね3つに分かれています。

Scene ジオメトリを生成する単位。SceneManagerのシーンとは別物
SceneNode 描画設定の階層化
IDrawable 描画するベクター情報を格納する。PathやShapeなどが指定できる

 

まず一番子の要素にIDrawableを継承したクラス、PathやShapeがあります。この2つは線や形状の情報で、どのような頂点でどのように塗りつぶすのか…といった形状の情報を格納します。

IDrawableを保持するのはSceneNodeです。SceneNodeは描画する対象情報の他に、クリップの情報やトランスフォーム(gameObject.transformとは別)といった、描画内容やオフセット等の情報を持っています。関係はIDrawableがMeshでSceneNodeがGameObjectと考えるとスッキリします。

Sceneはジオメトリを生成する単位です。基本的にSceneNodeをSceneに登録してメッシュやスプライトを作成します。なおSceneManagerのScene(Unity開発者が日常的に使用しているシーン)とは異なるものです。紛らわしい!!

f:id:tsubaki_t1:20180831170829j:plain

f:id:tsubaki_t1:20180831175350j:plain

実際のコードでは、まず最初にパスを作りSceneNodeに登録。描画する線は一本なのでSceneNode一つだけ作成して、ソレをSceneに登録しています。

f:id:tsubaki_t1:20180831171708j:plain

後は作成したSceneをVectorUtilsを通してMeshもしくはSpriteに変換します。
変換する際に、TessellationOptionに指定したパラメータを要求します。

f:id:tsubaki_t1:20180831172132j:plain

f:id:tsubaki_t1:20180831172823j:plain

 

PathのAPIを見てみる

PathのAPIを見てみます。
PathはBezierPathSegmentの配列とBezierPathSegmentのP0, P1, P2で構築していきます。P0が座標、P1とP2がカーブの為の情報です。

少し厄介なのが、P2が次のBezierPathSegmentから伸びるカーブ情報という点です。例えば下の場合、赤い頂点の左側のカーブの情報は、segment0.p2に強く依存しています。

f:id:tsubaki_t1:20180831154002j:plain

実際のコードでsegmentsのIDと上の画像の各ノードの色合いを比較すると、何となく立ち位置が分かるんじゃないかなと思います。

f:id:tsubaki_t1:20180831173100j:plain

なお毎フレームメシュを更新するのは何となくアレなので、コードではTransformが変化したタイミングでのみTransformの座標にアクセスし情報を抽出しています。

 

感想

VectorGraphicsで曲線を描いてみました。LineRendererで曲線を書くのと異なり保持すべき情報が少ないので、色々と良いんじゃないかなという気がしています。
特にプロシージャルに綺麗な曲線が引けるのは中々に良いです。

なお、単純に動的に画像をロードしたいんだ!という場合には普通にImportSVGしてしまうのが良さそうです。テキストのSVG定義データそのまま読めます。

 

関連

サンプルプロジェクト

github.com

【Unity】「一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術 」

先日のCEDEC 2018の資料です。
プロファイルに関する幾つかのアップデートが紹介されています。出来なかった事が出来るようになっている部分もあるので、一度見るオススメです。

 

www.slideshare.net

 

要約(プロファイル関連)

Time.realtimeSinceStartupで正しい時間を取得

タイムスケールに依存する時間ではなくリアルタイムな時間を取得します。
差分で処理時間を計測するならコレがオススメです。

Player Loopでプロファイラーを繋がずとも負荷を計測

PlayerLoopとTime.realtimeSinceStartupを組み合わせてリアルタイムに処理時間を計測しようというアイディアです。
個人的にはこの用途にPlayerloopを使うのは少し危険じゃないかなという気がしていますが、面白いアイディアでもあります。
(導入の順番や上書き等で面白おかしいデータになりそうな)

f:id:tsubaki_t1:20180831014612j:plain

tsubakit1.hateblo.jp

Recorder APIで処理時間を取得

ProfilerのRecorderが実行時にスクリプトから処理時間が取得出来るので、それを使用して負荷を計測する的なアプローチです。
PlayerLoopと異なり、全スレッドの累計時間を取ります。

f:id:tsubaki_t1:20180831015013j:plain

tsubakit1.hateblo.jp

メモリを消費しているアセットの把握

Profiler APIを使用してメモリ使用量を把握するか、Resources APIで利用中のアセットを全取得という感じのアプローチを紹介しています。
FindObjectsOfTypeAllで読み込んでるアセットを全取得は面白いアイディアです
主にプロファイラーに繋げない環境で便利そう。

f:id:tsubaki_t1:20180831015931j:plain

プロファイラーの書き出しについて

プロファイル結果をファイルに書き出すAPIと、300フレーム以上の結果を確認しやすくするエディター拡張の紹介です。
300フレーム制限、本当に何で存在するんでしょう

f:id:tsubaki_t1:20180831020314p:plain

github.com

 

要約(新機能情報)

実機上でのプロファイラー動作が高速化(2017.3+)

Unity 2017.3以降は、送信する情報や集計方法を整理することでかなり高速になります。
ただ以前のバージョンのプロファイリング結果と互換性がなくなるっぽいです。

実機上でのDeep Profiling(2017.3+)

引数に「-deepprofiling」を渡すと実機でもDeep Profilingが可能に(.NET 4系かつMono限定)

androidadb shell am start -n [パッケージ名]/ [アクティビティ名] -e “unity -deepprofiling" で行けるみたいです。

f:id:tsubaki_t1:20180831021327j:plain

ユーザーThread(.NETのスレッド)にプロファイラーが対応(2017.3+)

BeginThreadProfiling / EndThreadProfilingで、.NET側で作成したスレッドをプロファイラーに載せられるように。
Unity 2018.3から自動登録されるので、把握は簡単になりそうです。

tsubakit1.hateblo.jp

ジョブシステムが既にスレッドを目一杯使用しているので、観測できるからとスレッド使いまくるとコンテキストスイッチ等で大変なことになりそう

Native Plugin Callback(Unity 2018.2+)

プロファイラーのタイムライン相当の情報をフックして構築
上手く使うと、Systraceで見れる謎のプロセスの中身が分かるようになります。だから何が出来ると言われると微妙な機能ですが、頑張れば色々できそう

f:id:tsubaki_t1:20180831021711j:plain

tsubakit1.hateblo.jp

IL2CPPでScript Debugging(2018.2)

IL2CPP変換後の結果とC#コードがバインド出来るようになるみたいです。
これでiOSビルド後のバグが追いやすくなります。
.NET 4系のみです。

新しいMemoryProfiler

Memory Profiler Extensionがエディターに統合されます。

github.com

f:id:tsubaki_t1:20180831022243j:plain

感想

プロファイリング関連は常に最新を追っていきたいですね。
色んな最適化Tipsはありますが、結局ゲームに一致してるのかはプロファイルしてみないと分からないので、そこのところを出来るだけ楽したい心です。

f:id:tsubaki_t1:20180831024112j:plain

f:id:tsubaki_t1:20180831024119j:plain

Unite Berlin 2018 - Unity's Evolving Best Practices - YouTube