【Unity】NativeArrayについての解説及び実装コードを読んでみる

この記事は 「[もなふわすい～とる～む Advent Calendar 2020](https://adventar.org/calendars/5183)」の24日目の記事です。

私の**<font color="Red">最推し</font>**のバーチャルタレント(いわゆるVTuber)である「[巻乃もなかさん](https://twitter.com/monaka_0_0_7)」は以前、ユーザーがリクエストしたセリフを読み上げる形式のASMR配信にて**「[UnsafeUtility](https://docs.unity3d.com/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.html)を使う貴方の事が好き...♡//」** **「私の好きなUnityのAPIって知ってる...? 実はね...UnsafeUtility...♡//」**と言う私のロマンが詰まった読み上げのリクエストに応じてくれました。(カワイイ) (リアル女神) (声が好き過ぎて一生聴いていたい)[^1]

[^1]: ※私の心の声

- 参考動画
    - [【あまあま】こしょこしょ配信【 #もならいぶ 】 - 1:15:50より](https://youtu.be/UU5g9Y7jyKI?t=4550)

<details><summary>**※ 巻乃もなかさんについて (クリックで展開)**</summary><div>

※個人的には寧ろこちらが本編ですが、技術に直接関与しない内容となるので一応折り畳ませてもらってます :bow: :sweat_drops: 

「[巻乃もなかさん](https://twitter.com/monaka_0_0_7)」とは私の**<font color="Red">最推し</font>**のバーチャルタレント(いわゆるVTuber)さんです。
人物像については以下のインタビュー記事が非常によく纏まっております。

- [バーチャルタレント・巻乃もなかインタビュー　本当の自分を出す葛藤、その先にあったカメラやVRChatを語る楽しさ](https://panora.tokyo/archives/8573)

とっても素敵な方ですね！頑張り屋さんで応援したくなります！

後は[YouTubeのチャンネル](https://www.youtube.com/channel/UCZY07-x22jI7rNGJLZn7XPg)にて自己紹介動画の方も公開されてます。

- [５分でわかる巻乃もなか](https://www.youtube.com/watch?v=Q7D82s1u6-E&feature=emb_title)

**<font color="Red">この記事で一番言いたいポイントでもありますが、私はもう声が本当に大好きです。一生聴いてられます。(何なら毎フレーム聴いておきたいレベル)</font>**
**無論、可愛らしい所や努力家な面も大好きであり、諸々を踏まえると最早<font color="Red">「全部好き」</font>と結論付けられます。**

後は日頃からSHOWROOMにて「もなふわすい〜とる〜む」と言うルームで配信を行っているので、興味のある方は一度立ち寄ってみて下さい！

- [もなふわすい〜とる〜む](https://www.showroom-live.com/monaka-007)

最後に【参考・関連リンク】の章に**巻乃もなかさん**に関する幾つかのリンクを載せてあります。
ご存知でない方でも、今回の記事を機に興味を持ってフォロー/応援していただけると励みになります！

</div></details>

こちらはリクエストに応じた読み上げであるものの、**`UnsafeUtility`をここまで甘酸っぱく出来るその声と才能に感化され、<font color="Red">「あ、これはUnsafeUtilityに関する記事を書かなきゃな。」</font>**と思い立ち、こちらの記事を書かせていただくことにしました。

記事自体はQiitaへの投稿というのもあるので、オタクトークは一旦ここまでにしつつ、ここからは技術解説に入っていければと思います。
(※以降、ガチで「もなふわすい〜とる〜む」の「も」すら出てこない様な内容となっているので、アドカレから流れてきた技術系じゃないファンの方は注意 :bow: )





# この記事で解説する内容

今回解説する内容としては「内部実装で[UnsafeUtility](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.html)が使われている一例」として、Unityの[NativeContainer](https://docs.unity3d.com/Manual/JobSystemNativeContainer.html)と言う機能群及びそれに属する「**[NativeArray](https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeArray_1.html)**」を中心に解説していこうと思います。

最後の方ではNativeArrayの実装コードを見つつ、幾つかのポイントについても触れていきます。

もし記事中で間違いや説明不足の箇所などあれば、コメントや編集リクエストなどで教えて頂けると幸いです。 :bow: :sweat_drops: 

### 記事のターゲット

この記事は主に以下の疑問を整理する目的として纏めています。

- `NativeContainer`や`NativeArray`って聞いたことあるけど何なのかよく分からない
    - C#との配列の違いは何？ 利点は何？ 相互のやり取りって出来るの？
- どこで使われてるの？ [DOTS](https://unity.com/ja/dots)？
    - DOTS以外でも使われてるの？
- `NativeContainer`って自作できるの？

これらを踏まえると、ターゲットとしては以下の方にリーチする内容になるかな思います。

- `NativeArray`を通してUnityのメモリ領域について知りたい
    - `UnsafeUtility`を用いたネイティブメモリ操作について知りたい
- `NativeArray`の用途について知りたい
- Unityの`NativeArray`を始めとした`NativeContainer`の内部実装について知りたい
- Unityの`DOTS(Data-Oriented Tech Stack)`技術に興味があり、その一環として理解しておきたい


### 必要となる前知識

解説を進めていく上では幾つかの前知識が必要となってきます。
→ e.g. Unityに於けるメモリ領域, 値型/参照型の理解, Blittable型, etc...

必要な箇所については記事中でも随時補足していきますが、詳しくない方は先ずは以下の講演から目を通してみるのが良いかもしれません。

- [たのしいDOTS 〜初級から上級まで〜](https://learning.unity3d.jp/3252/)
    - ※前半のメモリ周りの話
- [Understanding C# Struct All Things](https://learning.unity3d.jp/3305/)
    - メモリ管理や構造体の理解など

この記事中でも上記の講演などを引用しつつも、以下のポイントについて順に解説していきます。

- Unityに於けるメモリ領域
- NativeContainerについて
    - UnsafeUtilityについて
- NativeArrayについて
- NativeArrayの実装コードを読んでみる


### UnityCsReferenceについて

因みにNativeArrayの実装コードは「[Unity-Technologies/UnityCsReference](https://github.com/Unity-Technologies/UnityCsReference)」にて公開されており、実際にコードを見て中でどうやって「ネイティブメモリの確保と解放を行っているのか？」「マネージヒープの配列との値のやり取りはどうしているのか？」と言った要点を知ることが出来ます。

この記事中では2019.4ブランチの内容をベースに要点を掻い摘んで解説していきます。

- [NativeArray.cs - 2019.4](https://github.com/Unity-Technologies/UnityCsReference/blob/2019.4/Runtime/Export/NativeArray/NativeArray.cs)










# Unityに於けるメモリ領域

以降の解説でも深く関わってくる内容となるので、先にUnityに於けるメモリ領域について軽くおさらいしておきます。
Unityにはスタック領域を除くと大きく分けて以下2種類のメモリ領域があります。

## マネージドメモリ (マネージドヒープ)

- C#側で使用されているメモリであり、**GC(Garbage Collection)の対象となるメモリ領域**
- メモリが確保出来なくなると自動的にヒープを拡張する
    - 拡張されたヒープは基本返ってこない (※後に大きな割当が行われた際にヒープを再拡張しなくても済むようにする為)
- GCのアルゴリズム(`Boehm GC`)の性質上、ヒープが拡張するのに応じてパフォーマンスが劣化するので圧迫には注意する必要がある
    - [※ちなみにUnity2019系統からはIncremental GCが導入されている](https://blogs.unity3d.com/jp/2018/11/26/feature-preview-incremental-garbage-collection/)

詳細については以下のドキュメントを御覧ください。

- [マネージヒープ] (https://docs.unity3d.com/ja/2019.4/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html)
- [自動メモリ管理](https://docs.unity3d.com/ja/2019.4/Manual/UnderstandingAutomaticMemoryManagement.html)

## ネイティブメモリ (アンマネージドメモリ)

- Unityのエンジン側が確保するネイティブメモリ領域 (Textureと言ったAsset等が含まれる)
- **GCの対象外**
    - 確保したら自分で解放する必要がある
- **今回話すNativeContainerで確保したメモリもこちらに属する**
    - 内部的には`UnsafeUtility.Malloc`が呼び出される ([詳細は後述](https://qiita.com/mao_/items/220ccf3b2ef929388036#unsafeutility%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6))

## Profiler上での表記

UnityのMemoryProfilerからそれぞれのメモリ領域の使用量を確認することが可能です。

以下で言う<font color="Red">赤線</font>を引いた箇所は「ネイティブメモリ」を指し、<font color="Blue">青線</font>を引いた箇所は「マネージドヒープ」を指します。

- [Memory Profiler module](https://docs.unity3d.com/ja/2019.4/Manual/ProfilerMemory.html)

![スクリーンショット 2020-12-20 18.28.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/ff53cdbf-934e-4f23-b276-611686c4d4e2.png)










# NativeContainerについて

簡単に言えばUnityのネイティブメモリ領域を利用したコンテナの総称を指します。
この章では一部を掻い摘んで説明するだけとなるので、詳細についてはドキュメントの方も合わせて御覧ください。

- [NativeContainer](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemNativeContainer.html)



## どこで使われるのか？

### JobSystemに於けるメインスレッドとの共有メモリとして利用

`NativeContainer`はUnityの[DOTS(Data-Oriented Technology Stack)](https://unity.com/ja/dots)に属する機能の一つであり、主に`C# JobSystem`(以降、`JobSystem`と記載)と言ったマネージドヒープを利用することが出来ない機能に於いて、メインスレッドとの共有メモリとして利用されると言った用途があります。

#### ※JobSystemについて

同じくDOTSに属する機能の一つである`JobSytem`は、Unityが提供する並列化(マルチスレッドプログラミング)のインターフェースの一つであり、NativeContainerと合わせて利用することでマルチスレッドプログラミングに於いての課題の一つである**「データの安全性(メモリの範囲外アクセス、データの競合チェック、メモリリークリークチェックなど)を担保して実装できる」**と言った利点が出てきます。

`JobSystem`の概念や安全性、パフォーマンスへの利点などについては以下の公式ドキュメントを御覧ください。

- [C# Job System](https://docs.unity3d.com/ja/2019.4/Manual/JobSystem.html)
    - [C# Job System の概要](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemOverview.html)
    - [マルチスレッドとは](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemMultithreading.html)
    - [ジョブシステムとは何か](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemJobSystems.html)
    - [C# Job System の安全システム](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemSafetySystem.html)
    - **[NativeContainer](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemNativeContainer.html)**


### 他にもUnityのネイティブメモリを安全に扱うためのAPIとしても利用される

こちらは主に`NativeArray`で扱われることが多いですが、最近だとマネージドヒープで扱うにはデータ容量が大きい物(例えばテクスチャの全ピクセル情報など)をNativeArrayで扱えるようにしたAPIが増えつつあります。

これにより、マネージドヒープの拡張を抑えることが可能となります。

詳細については[NativeArrayの章にて後述します。](https://qiita.com/mao_/items/220ccf3b2ef929388036#nativearray%E3%82%92%E4%BD%BF%E3%81%86%E5%88%A9%E7%82%B9)



## ネイティブメモリ領域へアクセスする際の安全性を担保

Unityのネイティブメモリ領域を自前で扱っていくには[UnsafeUtility](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.html)と言うAPIを利用する必要があり、**通常はunsafeコードを伴うポインタを用いた処理を実装する必要が出てきます。**

`NativeContainer`の意図の一つとして、**その危険なunsafeコードをNativeContainerの中で閉じてしまうことで、利用側にネイティブメモリ領域の操作(unsafeコード)を意識させずに安全に利用できるようにすると言ったものがあります。**

また、安全性を担保する上で必要となる機能も幾つか提供されており、これらはUnityEditor上で利用することが可能です。
(以降、これらの機能の総称を`セーフティーシステム`として記載)

- メモリリーク追跡
- 競合状態チェック

これらの安全性に於ける仕組みはunsafeコードを書く上では重要な機能となってきます。


### 確保したメモリは自分で解放する必要がある

説明の順番が前後しましたが、**NativeContainerはネイティブメモリ領域を扱う性質上、確保したメモリはGCでは破棄されないので自前で解放してやる必要があります。**

**また、当然ながら解放を忘れるとメモリリークします。**

これらを防ぐためにNativeContainer内部では上述のセーフティーシステムを用いて、万が一解放忘れがあったとしてもエラーとして通知できるような仕組みを用意する必要があります。


### 補足: unsafeコードは何故危険か？

少し余談にはなりますが、unsafeコードがなぜ危険なのか？について自分が思っていることを幾つか記載します。
(普通にC#を書いていく上では基本遭遇することの無い問題かと思われるが...)

因みに、似たような話題は以前↓の講演にて話しているので、興味のある方は御覧ください。

- [たのしいUnsafeUtility](https://learning.unity3d.jp/3753/)

#### 例外が飛んでこない

unsafeコード(と言うよりかはポインタの操作)の危険性を何点か挙げると...**普通にC#を書いていく上で例外が飛んでくるような処理を実装しても「例外が飛ばずに」処理が動き続けたり、それが影響して場合によってはクラッシュすると言った厄介な不具合が発生する可能性があります。**[^2]

例えばC#の配列は範囲外にアクセスすると`IndexOutOfRangeException`がスローされますが、**生ポインタから直に範囲外にアクセスした際には基本的に例外やエラーが飛んでこなかったりします。**[^2]

<font color="Red">これらの挙動は発生してしまうと、不具合の原因特定を困難にさせてくれます。</font>

[^2]: Unityだと利用するAllocatorによって挙動が変わるかもしれないので、一概には言えないところもある...

#### 無言でメモリリークし続ける危険性

他にもUnityは[UnsafeUtility.Malloc](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.Malloc.html)を叩くことで任意でネイティブメモリを確保することが出来ますが、**こちら当然ながら[UnsafeUtility.Free](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.Free.html)を叩き忘れるとメモリリークします。**

更に言うとUnity標準で用意されている`NativeArray`には予めリーク検知などのセーフティーシステムが組み込まれているので、仮に実装ミスでリークしてもエラーとして通知されるようになってますが...**`Malloc`を直に叩いて確保する形だとリークしても一切エラーが出ないので、メモリ不足の問題が発生するなどして気付くまでは無言でメモリリークし続けることになります。**

#### 危険性を下げるために

もしもunsafeなコードを利用側にも伝播するような実装にしてしまうと、あらゆる所で上記の問題を気にする必要が出てきたり、処理のミスによって特定困難な厄介な不具合が発生すると言った危険度が増してきます。

これを避けるために「危険地帯(unsafe）のスコープはなるべく狭める → **言い換えるとunsafeなコードはNaticeContainerの実装部だけに留める**」と言った対策が必要となってきます。


#### Assembly Definition Fileを活用していく

Unityはunsafeコードを書く際には以下の設定を適用する必要があります。

- Project Settings -> Player Settingsより`Allow 'unsafe' Code`を有効にする
- Assembly Definition File(以降、`ADF`と記載)の設定項目から`Allow 'unsafe' Code`を有効にする

前者は`Assembly-CSharp`といった従来のアセンブリ全域に対する影響力を持ち、後者はADFで分割したアセンブリ内のみの影響力を持ちます。

前者はその性質から広範囲に対してunsafeコードを許可してしまうことになるので、可能であればunsafeコードはADFで分割したアセンブリレベルでスコープを狭めてやるのが安全面でも見通しの面でも良いかもしれません。


## UnsafeUtilityについて

既に何度か登場してきた`UnsafeUtility`ですが、こちらを利用することでUnityのネイティブメモリ領域を確保したり、関連するメモリ操作を行なうことができます。

- [UnsafeUtility](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.html)

UnsafeUtilityに関する幾つかの詳細やTipsについては以前記載した以下の記事にて纏めているので、詳しいことについてはこちらを御覧ください。

- [【Unity】UnsafeUtilityについて纏めてみる](https://qiita.com/mao_/items/fc9b4340b05e7e83c3ff)

### 安全に書くには

安全に書くには色々とやり方があるかと思われますが、例えば以下の点について見直してみると良いかもしれません。

- **Allocatorに応じて処理を使い分けてメモリの寿命をはっきりさせる (確定的なメモリ管理を心がける)**
    - ※以下の例に挙げている[Allocatorの種類については後述](https://qiita.com/mao_/items/220ccf3b2ef929388036#allocator%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
    - e.g. 
        - `Allocator.Temp`ならそのフレーム内でスコープを明示的にする
        - `Allocator.TempJob`ならJob構造体に`DeallocateOnJobCompletion`属性を付けて自動的に破棄されるようにする
        - `Allocator. Persistent`なら`IDisposable`を実装したり、クラスのデストラクタを通して確実に破棄するようにする
- **セーフティーシステムをフル活用する**

思想的な所については以下の講演が参考になります。

- [たのしいDOTS 〜初級から上級まで〜](https://learning.unity3d.jp/3252/)
    - ※前半の「メモリの戦い 〜その歴史〜」と「ネイティブコンテナのトリック 〜Unmanagedに挑戦〜」辺りが参考になります



## NativeContainerを自作するには

`NativeContainer`はある程度自作できるような思想となっており、以下のドキュメントにサンプルコード含めて記述があります。

- [NativeContainerAttribute](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeContainerAttribute.html)
    - ※簡易的なカスタムコンテナの実装例が載っている
- [Unity Jobs Package](https://docs.unity3d.com/Packages/com.unity.jobs@latest//index.html)　　→`Custom job types`の項目を参照
    - ※[`Jobs 0.7.0-preview.17`時点でのドキュメントはこちら](https://docs.unity3d.com/Packages/com.unity.jobs@0.7/manual/custom_job_types.html#custom-nativecontainers)

少し前の講演にはなりますが、CEDEC2018で行われた以下の講演でも自作NativeContainerについての解説があります。

- [【CEDEC2018】CPUを使い切れ！ Entity Component System（通称ECS） が切り開く新しいプログラミング](https://www.slideshare.net/UnityTechnologiesJapan/cedec2018cpu-entity-component-systemecs)
    - → `17.自作せよ！　Nativeコンテナ`を参照
    - [講演動画はこちら](https://www.youtube.com/watch?v=T8lGD__aSm8)










# NativeArrayについて

[Unityに於けるメモリ領域](https://qiita.com/mao_/items/220ccf3b2ef929388036#unity%E3%81%AB%E6%96%BC%E3%81%91%E3%82%8B%E3%83%A1%E3%83%A2%E3%83%AA%E9%A0%98%E5%9F%9F)と[NativeContainerの概要](https://qiita.com/mao_/items/220ccf3b2ef929388036#nativecontainer%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)について触れてきましたが、ここからは今回の題材である「`NativeArray`とは何か？」についてもう少し掘り下げていきます。


## C#の配列との違い

「そもそもC#の配列とは何が違うのか？」について、先ずは大雑把に比較する為のリストを用意しました。
※ここで言う配列は通常の一次元配列を指す

||C#の配列|NativeArray|
|:--:|:--:|:--:|
|**メモリ空間**|マネージドヒープ|ネイティブメモリ|
|**保持できる型**|値型・参照型|値型(Blittable型のみ)|
|**多次元配列**|可|不可(自作すれば可)|

雑に言ってしまうと**「C#の配列は参照型としてメモリの確保にマネージドヒープを利用するが、NativeArrayはメモリの確保にネイティブメモリを利用する」**と言った違いがあり、それに応じて利用できる型などにも違いが生じています。


### C#の配列

- **メモリ空間**
    - マネージドヒープから確保
    - GCの対象であり、不必要にインスタンスを生成しまくるとGCの負荷に繋がり得る
- **保持できる型**
    - 値型の他にも参照型も配列に保持可能
- **多次元配列**
    - 言語レベルで多次元配列がサポートされている

### NativeArray

- **メモリ空間**
    - ネイティブメモリから確保
        - **使い終わったら自身でDisposeを呼んで破棄(解放)する必要がある** (やらないとメモリリーク)
    - インスタンスの寿命に応じて[Allocator](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.Allocator.html)を指定 ([後述](https://qiita.com/mao_/items/220ccf3b2ef929388036#allocator%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6))
- **保持できる型**
    - 保持できる型は**制約のある値型(いわゆる`Blittable型`)**である必要がある ([後述](https://qiita.com/mao_/items/220ccf3b2ef929388036#%E5%88%B6%E7%B4%84%E3%81%AE%E3%81%82%E3%82%8B%E5%80%A4%E5%9E%8B%E3%81%84%E3%82%8F%E3%82%86%E3%82%8Bblittable%E5%9E%8B%E3%81%A7%E3%81%82%E3%82%8B%E5%BF%85%E8%A6%81%E6%80%A7))
        - 構造体の場合にはフィールドの全てが値型でなきゃいけない
            - → 例えば「フィールドに参照型を持つ構造体」は`非Blittable型`となるので不可
- **多次元配列**
    - NativeArrayを普通に使う上では不可
        - ※ 「NativeArrayにNativeArrayを持たせれば行けるのでは？」と思うかもしれないが、NativeArrayはフィールドに参照型を持っており、型としては`非Blittable型`となるので持つことが出来ない。[これについては後ほど実装コードを踏まえて解説していく。](https://qiita.com/mao_/items/220ccf3b2ef929388036#%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
        - ※ C# 8から利用可能な[アンマネージなジェネリック構造体](https://ufcpp.net/study/csharp/sp_unsafe.html#unmanaged-generic-struct)を活用してBlittableなNativeArray互換のNativeContainerを実装すればそれっぽいことは出来るかもしれない。(要調査)
    - 若しくは自分でポインタのポインタ(所謂`void**`)として持てるNativeContainerを自作


## Allocatorについて

ネイティブメモリを割り当てる際にはインスタンスの寿命に応じて割り当てるAllocatorを指定する必要があります。

- [Allocator](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.Allocator.html)

NativeArrayを普通に利用する場合だと、主に使うのは以下の3つになるかと思います。

- **Temp**
    - 寿命は１フレーム
        - 言い換えるとそのフレーム内でのみ有効
    - 割り当てと解放に要する速度は最も高速
- **TempJob**
    - 寿命は4フレーム
        - 4フレ以内に解放しないとエラー
    - 割り当てと解放に要する速度は`Temp`よりは遅い
    - 主にJobSystemのJobに渡す際などに利用
        - 例えば[DeallocateOnJobCompletion](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.DeallocateOnJobCompletionAttribute.html)と言う属性をJobSystemで利用する構造体にセットしておくことで、Job終了時に自動的で解放してくれると言った機能が備わっている
            - 参考: [【Unity】Allocator.TempJobで作ったNativeArrayを自動的に開放する](http://tsubakit1.hateblo.jp/entry/2018/05/21/101354)
- **Persistent**
    - 寿命は無制限であり、永続的に使用可能
        - 自身で解放するまでずっと保持
    - 割り当てと解放に要する速度は一番遅い

もう少し加味した情報を以下の記事で記載しているので、興味ある方は合わせて御覧下さい。

- [【Unity】UnsafeUtilityについて纏めてみる
 -> Allocatorについて](https://qiita.com/mao_/items/fc9b4340b05e7e83c3ff#allocator%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)


## 制約のある値型(いわゆる`Blittable型`)である必要性

一言で言うと**「データがメモリ上に連続して並ぶ(ひと塊になる)」**必要があります。

C#は言語の特性上、参照型はマネージドヒープを利用することとなり、こちらを配列として扱うと以下のように「参照が配列に並んでいるだけ」の状態となり、実態が散らばってしまいます。
→ Managed heap側にあるオレンジの矩形がデータの実体

![スクリーンショット 2020-12-20 19.07.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/0e7700b6-df5e-616e-3d21-a20d418b8d0c.png)

他にもマネージドヒープはそもそもとしてGCの管理対象であったり、参照の保持だとJobSystemと言ったlockを前提としない並列化の仕組みの上では競合の問題が発生すると言った懸念が出てくるかと思われます。

**一方で値型の場合には、以下のようにデータの実態をメモリ上に連続(ひと塊)にして配置することが出来るようになります。**
→ Arrayに並んでいる水色の矩形がデータの実体

![スクリーンショット 2020-12-20 19.08.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/7dc398f2-cda9-db0c-8b2c-6e23e40d1b9e.png)

JobSystemは実行スレッドにデータがコピーされる性質があるので、データの競合が発生する懸念はなくなります。

- 参考: [C# Job System の安全システム](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemSafetySystem.html)



### 構造体は値型のみで構成される必要性がある

任意の構造体(値型)を定義する際には**「全てのフィールドが`値型`である」**事に準拠する必要があります。
**逆に言うと「フィールドに参照型を持つような構造体」は利用することが出来ません。**

これ自体は広義の言葉に置き換えると**「`Blittable型`であるかどうか？」**とも言えるかもしれません。
(因みに`UnsafeUtility`には[IsBlittable](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.IsBlittable.html)と言う判定用のメソッドが実装されている)

- [Blittable 型と非 Blittable 型](https://docs.microsoft.com/ja-jp/dotnet/framework/interop/blittable-and-non-blittable-types)
    - ※以降、便宜的に上記の定義を`広義のBlittable型`として記載します


### Unity2019系統からは少し扱いが異なってきている

Unity2019系統からは`広義のBlittable型`とは若干異なる形式になりそうな仕様が追加されてます。

広義の定義では「`char`と`bool`は**非Blittable型**」として定義されてますが、Unity2019系統からは以下の変更が入っており、「`char`と`bool`も**Blittable型**」として扱われるようになってます。

> [Unity 2019.1 Release note](https://unity3d.com/jp/unity/whats-new/2019.1.0)
> Scripting: Added ability to create NativeArrays of bool and char and types containing bool and char. (1127499, 1129523)

これにより`NativeArray`から`char`と`bool`も扱えるようになりました。
(余談だが...それまではこれら2点は定義できず、例えば`bool`は`byte`を代わりに使っていた)

**ただ、この変更によって「`広義のBlittable型`の定義」そのものが変わるわけでは無いかと思われるので、Unityのネイティブメモリ内部に限定される仕様と捉えておくのが安全かもしれません。**

※因みに「渡された型がBlittable型かどうか？」を判定するメソッドとして[UnsafeUtility.IsBlittable](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.IsBlittable.html)と言うAPIが生えてますが、2019系統?からは新規で[UnsafeUtility.IsValidNativeContainerElementType](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.IsValidNativeContainerElementType.html)と言うAPIが生えてきており、NativeArrayの内部実装などではこちらで型チェックを行う形になってます。


## NativeArrayを使う利点

`NativeArray`は上手く活用していくことで、マネージドヒープで管理するには重いデータ(例えばテクスチャの全ピクセル情報など)をネイティブメモリ側で管理できるようになります。

#### 対応APIは増えてきている

分かりやすい例を一つ挙げると「[AsyncGPUReadbackでRenderTextureをTexture2Dに変換する](https://qiita.com/UnagiHuman/items/583219cb0366b758a7fe)」が有り、読み込まれた結果は[AsyncGPUReadbackRequest.GetData](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Rendering.AsyncGPUReadbackRequest.GetData.html)と言うAPIから`NativeArray`形式で返されます。

これを用いることで`RenderTexture`を`Texture2D`形式に変換する際に、以下の手順を踏むことで**マネージドヒープ側に全ピクセル情報をコピーすること無くTexture2Dに読み込ませることが出来るようになります。**
(これによりヒープ全体が膨らむのを抑えることが出来る)

- 1. [AsyncGPUReadback.Request](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Rendering.AsyncGPUReadback.Request.html)で`RenderTexuture`を読み込むリクエストを生成
- 2. [AsyncGPUReadbackRequest.GetData](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Rendering.AsyncGPUReadbackRequest.GetData.html)から`NativeArray<Color32>`形式でピクセル情報を受取る
- 3. 受けっ取った`NativeArray`を[Texture2D.LoadRawTextureData](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Texture2D.LoadRawTextureData.html)に渡して`Texture2D`を生成

他の対応APIも徐々に増えてきている印象はあり、例えば[Unity 2021.1.0 Alpha 2のリリースノート](https://unity3d.com/unity/alpha/2021.1.0a2)のAPI Changesには以下の記載があります。

> Networking: Added: UnityWebRequest now supports sending/receiving data using NativeArray, avoiding managed allocations (UploadHandlerRaw and DownloadHandlerBuffer). (1138156)

そのまま読み取ると「UnityWebRequestでNativeArrayを用いたデータの送受信が可能になる」とのことであり、大きいデータを取り扱った際のマネージドヒープへの影響を抑えられそうな印象はあります。


### その他応用例

最近参考にさせていただいた応用例の一つとしては、以下の記事にあるように「事前にGPU向けにエンコードしたテクスチャをマネージドヒープを介さずに読み込む」と言った手法があります。

- [UnityのNativeArrayを使ってASTCをロードする](https://qiita.com/hzn/items/5762cd8d3b0ed1f205f9)

こちらで行われている手順を簡単に纏めると以下の手順となります。(色々端折っているので、詳細はソースの記事の方を御覧ください。)

- 1. テクスチャを事前にASTC形式に圧縮
- 2. 圧縮したASTC画像を[AsyncReadManager](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.IO.LowLevel.Unsafe.AsyncReadManager.html)からネイティブメモリ領域に読み込んで、テクスチャのポインタを取得
- 3. 取得したテクスチャのポインタを[Texture2D.LoadRawTextureData](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Texture2D.LoadRawTextureData.html)でロード

**※`AsyncReadManager`とは？**

簡単に言えば「ファイルを非同期かつアンマネージドメモリ領域に読み込むことが出来るAPI」になるかと思います。
少し古いですが、以前記事を書いたので参考までにリンクを載せておきます。

- [【Unity】ファイルを非同期で読み込んでアンマネージドメモリに展開できるAsyncReadManagerを試してみた](https://qiita.com/mao_/items/8f95bc9dbeb3179ba4b9)



## NativeArray以外のNativeContainerについて

Unityには`NativeArray`の他にも「NativeArrayから指定した範囲を切り出すための`NativeSlice`」と言う実装も含まれてます。

- [NativeSlice<T0>](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.NativeSlice_1.html)

他にもUPMを経由して[Unity Collections Package](https://docs.unity3d.com/Packages/com.unity.collections@latest/index.html)と言うパッケージを入れることで、`NativeList`, `NativeQueue`, `NativeHashMap`と言ったコレクションを利用することが可能になります。

以下、[ドキュメントにも記載のある](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemNativeContainer.html)一部のコレクションを引用します。

- [NativeList](https://docs.unity3d.com/Packages/com.unity.collections@0.14/api/Unity.Collections.INativeList-1.html) - サイズ変更可能な NativeArray
- [NativeHashMap](https://docs.unity3d.com/Packages/com.unity.collections@0.14/api/Unity.Collections.NativeHashMap-2.html) - キーと値のペア
- [NativeMultiHashMap](https://docs.unity3d.com/Packages/com.unity.collections@0.14/api/Unity.Collections.NativeMultiHashMap-2.html) - 各キーに複数の値
- [NativeQueue](https://docs.unity3d.com/Packages/com.unity.collections@0.14/api/Unity.Collections.NativeQueue-1.html) - 先入れ先出し (FIFO) キュー


## `Span<T>`との違い

以下の講演にある`Span vs NativeArray`が参考になります。

- [Understanding C# Struct All Things](https://learning.unity3d.jp/3305/)

どちらも性質的には似たものではありますが、対応APIなどに応じて変換が必要となってくるので、機能に応じて使い分けていくのが良いかと思います。










# NativeArrayの実装コードを読んでみる

最後にUnityCsReferenceにあるNativeArrayの実装コードを読みながら、幾つかのポイントについて補足していきます。
コード全てについて頭から解説すると長くなるので...今回は必要そうな要点のみ掻い摘んで解説していきます。

- [NativeArray.cs - 2019.4](https://github.com/Unity-Technologies/UnityCsReference/blob/2019.4/Runtime/Export/NativeArray/NativeArray.cs)


## 定義に付いている各種属性について

色々とくっついてますね。

特に`Native~`から始まるものは`Scripting API`を直接見ても情報が揃っていないことが多いですが、調べると情報自体は出てくるので以下に纏めます。

```csharp:NativeArray.cs
[StructLayout(LayoutKind.Sequential)]
[NativeContainer()]
[NativeContainerSupportsMinMaxWriteRestriction()]
[NativeContainerSupportsDeallocateOnJobCompletion()]
[NativeContainerSupportsDeferredConvertListToArray()]
[DebuggerDisplay("Length = {Length}")]
[DebuggerTypeProxy(typeof(NativeArrayDebugView<>))]
public unsafe struct NativeArray<T> : IDisposable, IEnumerable<T>, IEquatable<NativeArray<T>> where T : struct
{
```


### `[StructLayout(LayoutKind.Sequential)]`
    
こちらは構造体のメモリレイアウトの指定であり、`LayoutKind.Sequential`は**フィールドを定義した順番通りに並べていく性質があります。** 
(因みに構造体のデフォルトは`Sequential`が適用される)

他の`LayoutKind`含めて以下の記事が参考になります

- [複合型のレイアウト](https://ufcpp.net/study/csharp/interop/memorylayout/)


### `[NativeContainer]`

こちらについては`Scripting API`に詳細の記述があり、自作NativeContainerの簡易的な実装例も合わせて記載されてます。

- [NativeContainerAttribute](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeContainerAttribute.html)

ドキュメントの内容を要約すると、JobSystemで利用する上でのセーフティシステムを機能させるために必要となり、属性を付けることでJobDebugger[^3]がコンテナに対する全てのアクセスの安全性の保証し、データの競合や非決定的な振る舞いを確認した際に例外がスローされる様になります。

[^3]: 恐らくはJobSystemの内部で実装されているであろうデバッグ機能。詳細は不明...

他にも幾つかの要点があるので、箇条書きにて纏めます。
詳細につきましてはドキュメントの方を御覧ください。

- 含まれるデータは全てBlittable型である必要がある
- `セーフティシステムについて`
    - 競合状態を検出できるようにするためには`AtomicSafetyHandle`を埋め込む必要がある
    - リークの検出には`DisposeSentinel`が使用される
- `テストはしっかり書こう`
    - 自作コンテナを作る上では、競合防止のために全てのシナリオのテストカバレッジを追加して、競合状態が確実に防止されるようにする事を強く推奨する
        - [※ 正しく実装されていない場合には厄介なエラーに遭遇する可能性があるため](https://qiita.com/mao_/items/220ccf3b2ef929388036#%E8%A3%9C%E8%B6%B3-unsafe%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AF%E4%BD%95%E6%95%85%E5%8D%B1%E9%99%BA%E3%81%8B)

#### セーフティシステムの変数名について

[Scripting APIにあるサンプルコード](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeContainerAttribute.html)には以下のコメントの記述があります。

```csharp
// Marks our struct as a NativeContainer.
// If ENABLE_UNITY_COLLECTIONS_CHECKS is enabled,
// it is required that m_Safety & m_DisposeSentinel are declared, with exactly these names.
[NativeContainer()]
```

要約すると、**セーフティ機能である`AtomicSafetyHandle`と`DisposeSentinel`は以下の名称で定義されている必要があるみたいです。**

詳細については不明ですが...名称を指定する辺りからしてReflectionの香りはしてきます。

```csharp
#if ENABLE_UNITY_COLLECTIONS_CHECKS
    internal AtomicSafetyHandle       m_Safety;
    internal DisposeSentinel          m_DisposeSentinel;
#endif
```

因みに、`ENABLE_UNITY_COLLECTIONS_CHECKS`と言うシンボルはEditor実行時に自動で定義される「セーフティーシステムの有効/無効」を示す物らしいです。

セーフティーシステムは基本的にはEditorOnlyを想定しているので、NativeContainerを自作する際には例に習って分けるようにしたほうが良さそうです。


### その他`Native~`から始まる属性について

これらについては`Scriptin API`側に記述は無いものの、`Unity Jobs Package`側のドキュメントに情報が纏められています。

- [Available attributes](https://docs.unity3d.com/Packages/com.unity.jobs@0.7/manual/custom_job_types.html#available-attributes)
    - ※リンクは`Jobs 0.7.0-preview.17`にある記述を参照

#### `[NativeContainerSupportsMinMaxWriteRestriction]`

こちらを付けることで**自身がアクセスできる範囲**を取得出来るようになります。

範囲を取得するにあたっては、属性を付けたNativeContainer側で以下の順でフィールドが定義されている必要があります。
(こちらも恐らくはReflectionを使ってる...?)

```csharp
internal int    m_Length;
internal int    m_MinIndex;
internal int    m_MaxIndex;
```

もう少し踏み込んで説明すると、NativeContainerは[IJobParallelFor](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Jobs.IJobParallelFor.html)に渡した際に**分割される**可能性があります。

※`IJobParallelFor`についてはこちらを参照。
→ [IJobParallelForで並列処理させるジョブを作ってみる](https://www.shibuya24.info/entry/jobsystem2)

分割された状態で好き勝手にランダムアクセスを行ってしまうと、不正な範囲外アクセスに繋がり得るので`[NativeContainerSupportsMinMaxWriteRestriction]`から自身がアクセスできる範囲を取得して範囲外チェックを行う必要があります。
(分割された際の範囲については恐らくはJob実行時にNativeArrayがコピーされる際に代入されていると予想)


例として`NativeArray`のインデクサーに関する実装を見ると、以下のように`CheckElementReadAccess`及び`CheckElementWriteAccess`でチェック処理が入っていることを確認できます。

```csharp:NativeArray.cs
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckElementReadAccess(int index)
{
    if (index < m_MinIndex || index > m_MaxIndex)
        FailOutOfRangeError(index);

    var versionPtr = (int*)m_Safety.versionNode;
    if (m_Safety.version != ((*versionPtr) & AtomicSafetyHandle.ReadCheck))
        AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut(m_Safety);
}

[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckElementWriteAccess(int index)
{
    if (index < m_MinIndex || index > m_MaxIndex)
        FailOutOfRangeError(index);

    var versionPtr = (int*)m_Safety.versionNode;
    if (m_Safety.version != ((*versionPtr) & AtomicSafetyHandle.WriteCheck))
        AtomicSafetyHandle.CheckWriteAndThrowNoEarlyOut(m_Safety);
}

public T this[int index]
{
    get
    {
        CheckElementReadAccess(index);
        return UnsafeUtility.ReadArrayElement<T>(m_Buffer, index);
    }

    [WriteAccessRequired]
    set
    {
        CheckElementWriteAccess(index);
        UnsafeUtility.WriteArrayElement(m_Buffer, index, value);
    }
}
```



#### `[NativeContainerSupportsDeallocateOnJobCompletion]`

こちらを付けることで、NativeContainerは[Allocatorの項目](https://qiita.com/mao_/items/220ccf3b2ef929388036#allocator%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)でも記載した`DeallocateOnJobCompletion`を利用することが可能になります。

但し、使うためには以下のフィールドを持っている必要があるみたいです。
(前に倣って名前を指定している辺りからして、こちらも恐らくはReflectionを使ってる...?)

```csharp
internal IntPtr           m_Buffer;
internal DisposeSentinel  m_DisposeSentinel;
internal Allocator        m_AllocatorLabel;
```

#### `[NativeContainerSupportsDeferredConvertListToArray]`

こちらのみ情報が見つからなかったので不明...
(名前的に遅延変換を意味する..?)


### `[DebuggerDisplay]`と`[DebuggerTypeProxy]`

最後にNativeArrayに付いている表題の属性についてですが、こちらは主にVisualStudioなどのツール上での可視化の際に利用されるみたいです。

参考: [DebuggerDisplay 属性もいいけど DebuggerTypeProxy 属性もね](http://kendik.hatenablog.com/entry/2015/04/17/012858)

```csharp:NativeArray.cs
[DebuggerDisplay("Length = {Length}")]
[DebuggerTypeProxy(typeof(NativeCustomArrayDebugView<>))]
```

`DebuggerTypeProxy`に渡されている`NativeCustomArrayDebugView`は、`NativeArray.cs`内にて以下のように定義されてます。

```csharp:NativeArray.cs
/// <summary>
/// DebuggerTypeProxy for <see cref="NativeArray{T}"/>
/// </summary>
internal sealed class NativeArrayDebugView<T> where T : struct
{
    NativeArray<T> m_Array;

    public NativeArrayDebugView(NativeArray<T> array)
    {
        m_Array = array;
    }

    public T[] Items => m_Array.ToArray();
}
```



## フィールドについて

先ず「NativeArrayが内部的にどのようにしてデータを保持するのか？」を見るために、フィールドの定義部から見ていきます。

データ自体は`UnsafeUtility.Malloc`から確保したネイティブメモリを汎用ポインタ(`void*`)形式で持っており、それ以外は配列の長さやセーフティーシステムで使われる変数などになります。

[上述の属性の項目](https://qiita.com/mao_/items/220ccf3b2ef929388036#%E5%AE%9A%E7%BE%A9%E3%81%AB%E4%BB%98%E3%81%84%E3%81%A6%E3%81%84%E3%82%8B%E5%90%84%E7%A8%AE%E5%B1%9E%E6%80%A7%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を踏まえつつ補足をコメントに補足を追記していきます。

```csharp:NativeArray.cs
public unsafe struct NativeArray<T> : IDisposable, IEnumerable<T>, IEquatable<NativeArray<T>> where T : struct
{
    // UnsafeUtility.Mallocで確保したネイティブメモリのポインタ
    // ※[NativeDisableUnsafePtrRestriction]属性については後述
    [NativeDisableUnsafePtrRestriction]
    internal void*                    m_Buffer;

    // 配列の長さや、分割された際にWorkerThread上からアクセス可能な配列の範囲など
    // 詳しくは[NativeContainerSupportsMinMaxWriteRestriction]の項目を参照
    internal int                      m_Length;
    internal int                      m_MinIndex;
    internal int                      m_MaxIndex;

    // 各種セーフティーシステムで用いられる変数
    // 詳しくは[NativeContainer]の項目を参照
    // ※[NativeSetClassTypeToNullOnSchedule]については後述
    internal AtomicSafetyHandle       m_Safety;

    // NOTE: DisposeSentinelは参照型なので注意。
    //       その影響でNativeArrayは非Bllitableとなっている。
    [NativeSetClassTypeToNullOnSchedule]
    internal DisposeSentinel          m_DisposeSentinel;

    // メモリを確保する際のAllocator
　  // [NativeContainerSupportsDeallocateOnJobCompletion]の都合上、`m_AllocatorLabel`の名称で定義する必要がありそう
    internal Allocator                m_AllocatorLabel;

```

### NativeArrayは構造体 → 値渡し時にコピーされる点に注目

NativeArrayは構造体です。
**そのために値渡し時にはデータがコピーされます。**

後はJobが実行される際にも実行スレッドにコピーされた値が渡されます。
(Job実行時にコピーされる理由については安全上の理由であり、詳細については以下を参照)

- [C# Job System の安全システム](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemSafetySystem.html)

NativeArrayはデータ本体がポインタとなるので、**コピーされても参照先が変わりません。**
この様に**全体的にコピーされても問題のない作り**になってますが、NativeContainerを自作する際にはこの性質を念頭に置いといたほうが良いかもしれません。
(※NativeArrayのフィールドの大体は初期化時に不変値が決定される + 一部の値については[上述の属性](https://qiita.com/mao_/items/220ccf3b2ef929388036#%E5%AE%9A%E7%BE%A9%E3%81%AB%E4%BB%98%E3%81%84%E3%81%A6%E3%81%84%E3%82%8B%E5%90%84%E7%A8%AE%E5%B1%9E%E6%80%A7%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)が自動で値を設定してくれるものかと思われる)

```csharp
// NativeArrayの生成
var monafuwaArray = new NativeArray<int>(7, Allocator.Persistent);

// この時、`sweetRoomArray`には`monafuwaArray`のコピーが入る
// 但しNativeArrayの本体はポインタであり、参照先は変わらないので気にせずに使える
var sweetRoomArray = monafuwaArray;
```


### `[NativeDisableUnsafePtrRestriction]`について

JobSystemを使う上では基本的にポインタを直接持つことが禁止されています。
(理由としては、ポインタを直接持ってしまうことで安全性を保証出来なくなるから)

但し、こちらを明示的に定義することでその制限を無効化することが出来るようになります。

- [NativeDisableUnsafePtrRestrictionAttribute](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestrictionAttribute.html)

一件挙動だけ見ると怖そうな設定ではありますが、NativeContainerについては`AtomicSafetyHandle`や`DisposeSentinel`と言ったセーフティーシステムで安全面を担保している面があるので、こちらで制限を外しても問題は無いのかと思います。


### `[NativeSetClassTypeToNullOnSchedule]`について

JobSystemは基本的には参照型を持つことは許可されていません。
もし参照型を持たせる必要がある場合には、こちらの属性を設定する必要があります。

こちらを付けることでJobSystem側で構造体がコピーされる際に、参照型にnullが設定されるようになります。

- [NativeSetClassTypeToNullOnScheduleAttribute](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeSetClassTypeToNullOnScheduleAttribute.html)

セーフティーシステムの一つである`DisposeSentinel`は参照型となるので、属性が付与されています。


#### 補足: `NativeArray<T>`が非Blittableな構造体である理由

[既にこちらでチラッと記述してますが](https://qiita.com/mao_/items/220ccf3b2ef929388036#%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)、**NativeArrayのフィールドに含まれる`DisposeSentinel`は参照型となります。**
これがNativeArrayが非Blittable型である所以です。



## コンストラクタ時にメモリを確保

メモリの確保は基本的にはコンストラクタ時に行われます。
その上で[NativeArrayのコンストラクタの定義](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.NativeArray_1-ctor.html)を見ると、以下3点のオーバーロードが含まれます。

- 配列の長さを指定して初期化
- C#の配列からコピーして作成
- NativeArrayからコピーして作成

どの処理に於いても先ずは「配列の長さからメモリの確保(`Allocate`)」が呼び出されており、コピー関連は確保された後のメモリに対して行われる形となってます。

先ずはメモリの確保から解説していきます。

```csharp:NativeArray.cs
// 配列の長さを指定して初期化
public NativeArray(int length, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
    Allocate(length, allocator, out this);
    if ((options & NativeArrayOptions.ClearMemory) == NativeArrayOptions.ClearMemory)
        UnsafeUtility.MemClear(m_Buffer, (long)Length * UnsafeUtility.SizeOf<T>());
}

// C#の配列からコピーする形で初期化
public NativeArray(T[] array, Allocator allocator)
{
    if (array == null)
        throw new ArgumentNullException(nameof(array));

    Allocate(array.Length, allocator, out this);
    Copy(array, this);
}

// 既存のNativeArrayからコピーする形で初期化
public NativeArray(NativeArray<T> array, Allocator allocator)
{
    Allocate(array.Length, allocator, out this);
    Copy(array, this);
}
```

`Allocate`の内部実装については以下の様になっており、以前書いたUnsafeUtilityの記事とほぼ同様の内容になります。
※但し事前に幾つかの例外処理が入っていたり、`DisposeSentinel`の初期化などが行われている。

- [ネイティブメモリの確保と解放](https://qiita.com/mao_/items/fc9b4340b05e7e83c3ff#%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%AE%E7%A2%BA%E4%BF%9D%E3%81%A8%E8%A7%A3%E6%94%BE)

```csharp:NativeArray.cs
// メモリ確保
static void Allocate(int length, Allocator allocator, out NativeArray<T> array)
{
    var totalSize = UnsafeUtility.SizeOf<T>() * (long)length;

    // Native allocation is only valid for Temp, Job and Persistent.
    if (allocator <= Allocator.None)
        throw new ArgumentException("Allocator must be Temp, TempJob or Persistent", nameof(allocator));
    if (length < 0)
        throw new ArgumentOutOfRangeException(nameof(length), "Length must be >= 0");

    IsUnmanagedAndThrow();

    // Make sure we cannot allocate more than int.MaxValue (2,147,483,647 bytes)
    // because the underlying UnsafeUtility.Malloc is expecting a int.
    // TODO: change UnsafeUtility.Malloc to accept a UIntPtr length instead to match C++ API
    if (totalSize > int.MaxValue)
        throw new ArgumentOutOfRangeException(nameof(length), $"Length * sizeof(T) cannot exceed {int.MaxValue} bytes");

    array = default(NativeArray<T>);
    array.m_Buffer = UnsafeUtility.Malloc(totalSize, UnsafeUtility.AlignOf<T>(), allocator);
    array.m_Length = length;
    array.m_AllocatorLabel = allocator;

    array.m_MinIndex = 0;
    array.m_MaxIndex = length - 1;
    DisposeSentinel.Create(out array.m_Safety, out array.m_DisposeSentinel, 1, allocator);
}

// NOTE: NativeArrayで確保される方が許可されたものかどうかをチェック
[BurstDiscard]
internal static void IsUnmanagedAndThrow()
{
    // NOTE: 因みに2019系統より前は`IsBlittable`にて判定を行っており、char/boolは扱えなかった
    // ref: https://github.com/Unity-Technologies/UnityCsReference/blob/2018.4/Runtime/Export/NativeArray/NativeArray.cs#L97
    if (!UnsafeUtility.IsValidNativeContainerElementType<T>())
    {
        throw new InvalidOperationException(
            $"{typeof(T)} used in NativeArray<{typeof(T)}> must be unmanaged (contain no managed types) and cannot itself be a native container type.");
    }
}
```

## 配列のコピーについて

NativeArrayには配列のコピー関数として以下のメソッドが実装されています。

これらは同じ型であるNativeArray同士のコピーは勿論のこと、**メモリ空間が異なるC#配列とも相互に取り扱えるための仕組みが用意されてます。**

- [Copy](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.NativeArray_1.Copy.html)
    - NativeArrayのstatic methodとして実装されている
    - 性質的には[System.Array.Copy](https://docs.microsoft.com/ja-jp/dotnet/api/system.array.copy?view=netstandard-2.0)に近い
- [CopyTo](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.NativeArray_1.CopyTo.html)
    - 自身の内容を渡された同じ長さの配列にコピーする
- [CopyFrom](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.NativeArray_1.CopyFrom.html)
    - 渡された同じ長さの配列の内容を自身にコピーする

コピー関連メソッドの定義の大半はオーバーロードとなるので、実装の肝となる内部実装のみ抜粋して解説していきます。


### NativeArray間のコピー

やっていることは非常に単純で「引数の不正チェックを行った後に`MemCpy`を呼び出している」だけです。
NativeArray同士の場合には、元から同じネイティブメモリ空有上でのやり取りとなるのでシンプルな実装で済みます。

因みに[AtomicSafetyHandle.CheckReadAndThrow](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrow.html)と[AtomicSafetyHandle.CheckWriteAndThrow](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrow.html)は、「ハンドルの読み書きが出来るかどうかを確認し、メモリが破棄済み又はJobSystem側で書き込まれている場合」には例外がスローされます。

```csharp:NativeArray.cs
public static void Copy(NativeArray<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
{
    AtomicSafetyHandle.CheckReadAndThrow(src.m_Safety);
    AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);

    if (length < 0)
        throw new ArgumentOutOfRangeException(nameof(length), "length must be equal or greater than zero.");

    if (srcIndex < 0 || srcIndex > src.Length || (srcIndex == src.Length && src.Length > 0))
        throw new ArgumentOutOfRangeException(nameof(srcIndex), "srcIndex is outside the range of valid indexes for the source NativeArray.");

    if (dstIndex < 0 || dstIndex > dst.Length || (dstIndex == dst.Length && dst.Length > 0))
        throw new ArgumentOutOfRangeException(nameof(dstIndex), "dstIndex is outside the range of valid indexes for the destination NativeArray.");

    if (srcIndex + length > src.Length)
        throw new ArgumentException("length is greater than the number of elements from srcIndex to the end of the source NativeArray.", nameof(length));

    if (dstIndex + length > dst.Length)
        throw new ArgumentException("length is greater than the number of elements from dstIndex to the end of the destination NativeArray.", nameof(length));

    UnsafeUtility.MemCpy(
        (byte*)dst.m_Buffer + dstIndex * UnsafeUtility.SizeOf<T>(),
        (byte*)src.m_Buffer + srcIndex * UnsafeUtility.SizeOf<T>(),
        length * UnsafeUtility.SizeOf<T>());
}
```


### `NativeArray`と`C#配列`間のコピー

`src`をC#配列とし、dstを`NativeArray`とした実装例を用いて解説します。
(因みにsrcとdstが逆の場合も大したコードは変わらない)

最初に引数の不正チェックを行なうところまではNativeArray同士と変わりませんが、C#配列はそのままの形ではコピー出来ないので、一度`GCHandle.Alloc(src, GCHandleType.Pinned)`で固定されたマネージドオブジェクトのハンドルを取得し、`AddrOfPinnedObject`でハンドル内のオブジェクトのアドレスを取得した上で`MemCpy`が行われてます。


- [GCHandle.Alloc ](https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.gchandle.alloc?view=netstandard-2.0#System_Runtime_InteropServices_GCHandle_Alloc_System_Object_System_Runtime_InteropServices_GCHandleType_)
    - [GCHandleType](https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.gchandletype?view=netstandard-2.0)
- [GCHandle.AddrOfPinnedObject](https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.gchandle.addrofpinnedobject?view=netstandard-2.0)


```csharp:NativeArray.cs
public static void Copy(T[] src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
{
    AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);

    if (src == null)
        throw new ArgumentNullException(nameof(src));

    if (length < 0)
        throw new ArgumentOutOfRangeException(nameof(length), "length must be equal or greater than zero.");

    if (srcIndex < 0 || srcIndex > src.Length || (srcIndex == src.Length && src.Length > 0))
        throw new ArgumentOutOfRangeException(nameof(srcIndex), "srcIndex is outside the range of valid indexes for the source array.");

    if (dstIndex < 0 || dstIndex > dst.Length || (dstIndex == dst.Length && dst.Length > 0))
        throw new ArgumentOutOfRangeException(nameof(dstIndex), "dstIndex is outside the range of valid indexes for the destination NativeArray.");

    if (srcIndex + length > src.Length)
        throw new ArgumentException("length is greater than the number of elements from srcIndex to the end of the source array.", nameof(length));

    if (dstIndex + length > dst.Length)
        throw new ArgumentException("length is greater than the number of elements from dstIndex to the end of the destination NativeArray.", nameof(length));

    // Pinnedで固定されたマネージドオブジェクトのハンドルを取得
    var handle = GCHandle.Alloc(src, GCHandleType.Pinned);
    // ハンドル内のオブジェクトのアドレスを取得
    var addr = handle.AddrOfPinnedObject();

    UnsafeUtility.MemCpy(
        (byte*)dst.m_Buffer + dstIndex * UnsafeUtility.SizeOf<T>(),
        (byte*)addr + srcIndex * UnsafeUtility.SizeOf<T>(),
        length * UnsafeUtility.SizeOf<T>());

    handle.Free();
}
```

## `IDisposable`でメモリを破棄

NativeArrayには`IDisposable`が実装されており、`Dispose`内にてメモリの破棄は行われます。
Allocatorの不正チェックなどを行った後に、メモリ解放処理(`Free`)が走ります。

```csharp:NativeArray.cs
void Deallocate()
{
    UnsafeUtility.Free(m_Buffer, m_AllocatorLabel);
    m_Buffer = null;
    m_Length = 0;
}

[WriteAccessRequired]
public void Dispose()
{

    if (!UnsafeUtility.IsValidAllocator(m_AllocatorLabel))
        throw new InvalidOperationException("The NativeArray can not be Disposed because it was not allocated with a valid allocator.");

    DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
    Deallocate();
}
```

### JobSystemで破棄することも可能

因みに`Dispose`にはジョブをスケジュールしつつ、`JobHandle`を返す形式のものも存在するみたいです。
引数から前後関係の`JobHandle`を渡すことが出来るので、「一連のシーケンスを追えた後に破棄」と言った処理に使えるかもしれません。

```csharp:NativeArray.cs
/// <summary>
/// Safely disposes of this container and deallocates its memory when the jobs that use it have completed.
/// </summary>
/// <remarks>You can call this function dispose of the container immediately after scheduling the job. Pass
/// the [JobHandle](https://docs.unity3d.com/ScriptReference/Unity.Jobs.JobHandle.html) returned by
/// the [Job.Schedule](https://docs.unity3d.com/ScriptReference/Unity.Jobs.IJobExtensions.Schedule.html)
/// method using the `jobHandle` parameter so the job scheduler can dispose the container after all jobs
/// using it have run.</remarks>
/// <param name="jobHandle">The job handle or handles for any scheduled jobs that use this container.</param>
/// <returns>A new job handle containing the prior handles as well as the handle for the job that deletes
/// the container.</returns>
public JobHandle Dispose(JobHandle inputDeps)
{
    // [DeallocateOnJobCompletion] is not supported, but we want the deallocation
    // to happen in a thread. DisposeSentinel needs to be cleared on main thread.
    // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
    // will check that no jobs are writing to the container).
    DisposeSentinel.Clear(ref m_DisposeSentinel);
    var jobHandle = new DisposeJob { Container = this }.Schedule(inputDeps);

    AtomicSafetyHandle.Release(m_Safety);
    m_Buffer = null;
    m_Length = 0;

    return jobHandle;
}

// [BurstCompile] - can't use attribute since it's inside com.untity.collections.
struct DisposeJob : IJob
{
    public NativeArray<T> Container;

    public void Execute()
    {
        Container.Deallocate();
    }
}
```


## `NativeArrayUnsafeUtlity`について

`NativeArray.cs`には、NativeArrayに関するunsafeな操作のモジュール群である`NativeArrayUnsafeUtlity`と言う拡張メソッド群も実装されてます。

- [NativeArrayUnsafeUtility](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.html)

あまり使うことは無いかと思われますが、こちらを用いることで「既存のメモリ確保済みのポインタからNativeArrayを構築」したり、「NativeArrayからポインタを抜き出す」と言った操作を行なうことが出来ます。
※それ故に使う上ではunsafeコンテキストが必要となってくる

```csharp:NativeArray.cs
namespace Unity.Collections.LowLevel.Unsafe
{
    public static class NativeArrayUnsafeUtility
    {
        public static AtomicSafetyHandle GetAtomicSafetyHandle<T>(NativeArray<T> array) where T : struct
        {
            return array.m_Safety;
        }

        public static void SetAtomicSafetyHandle<T>(ref NativeArray<T> array, AtomicSafetyHandle safety) where T : struct
        {
            array.m_Safety = safety;
        }


        /// Internal method used typically by other systems to provide a view on them.
        /// The caller is still the owner of the data.
        public static unsafe NativeArray<T> ConvertExistingDataToNativeArray<T>(void* dataPointer, int length, Allocator allocator) where T : struct
        {
            if (length < 0)
                throw new ArgumentOutOfRangeException(nameof(length), "Length must be >= 0");

            NativeArray<T>.IsUnmanagedAndThrow();

            var totalSize = UnsafeUtility.SizeOf<T>() * (long)length;
            // Make sure we cannot allocate more than int.MaxValue (2,147,483,647 bytes)
            // because the underlying UnsafeUtility.Malloc is expecting a int.
            // TODO: change UnsafeUtility.Malloc to accept a UIntPtr length instead to match C++ API
            if (totalSize > int.MaxValue)
                throw new ArgumentOutOfRangeException(nameof(length), $"Length * sizeof(T) cannot exceed {int.MaxValue} bytes");

            var newArray = new NativeArray<T>
            {
                m_Buffer = dataPointer,
                m_Length = length,
                m_AllocatorLabel = allocator,

                m_MinIndex = 0,
                m_MaxIndex = length - 1,
            };

            return newArray;
        }

        public static unsafe void* GetUnsafePtr<T>(this NativeArray<T> nativeArray) where T : struct
        {
            AtomicSafetyHandle.CheckWriteAndThrow(nativeArray.m_Safety);
            return nativeArray.m_Buffer;
        }

        public static unsafe void* GetUnsafeReadOnlyPtr<T>(this NativeArray<T> nativeArray) where T : struct
        {
            AtomicSafetyHandle.CheckReadAndThrow(nativeArray.m_Safety);
            return nativeArray.m_Buffer;
        }

        public static unsafe void* GetUnsafeBufferPointerWithoutChecks<T>(NativeArray<T> nativeArray) where T : struct
        {
            return nativeArray.m_Buffer;
        }
    }
}
```








# 参考・関連リンク

## 巻乃もなかさん 及び もなふわすい〜とる〜む関連

### 紹介/インタビュー記事

- [バーチャルタレント・巻乃もなかインタビュー　本当の自分を出す葛藤、その先にあったカメラやVRChatを語る楽しさ](https://panora.tokyo/archives/8573)
- [５分でわかる巻乃もなか (紹介動画)](https://www.youtube.com/watch?v=Q7D82s1u6-E)

### Twitter

- [巻乃もなか - Twitter](https://twitter.com/monaka_0_0_7)

### 配信

- [もなふわすい〜とる〜む](https://www.showroom-live.com/monaka-007)
- [もなふわちゃんねる](https://www.youtube.com/channel/UCZY07-x22jI7rNGJLZn7XPg)
    - [【あまあま】こしょこしょ配信【 #もならいぶ 】](https://youtu.be/UU5g9Y7jyKI)
        - <font color="Red">※今回この記事を書くきっかけとなった講演動画</font>

### Other

- [巻乃もなかにマシュマロを投げる | マシュマロ](https://marshmallow-qa.com/monaka_0_0_7)
- [巻乃もなか｜pixivFANBOX](https://makinomonaka.fanbox.cc/)


## Unity関連

齟齬が出ないよう、ドキュメント各種のバージョンは2019.4で固定してあります。

### 公式マニュアル

#### Unityに於けるメモリ領域

- [マネージヒープ] (https://docs.unity3d.com/ja/2019.4/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html)
- [自動メモリ管理](https://docs.unity3d.com/ja/2019.4/Manual/UnderstandingAutomaticMemoryManagement.html)

#### NativeContainer

- [NativeContainer](https://docs.unity3d.com/ja/current/Manual/JobSystemNativeContainer.html)
- [Unity Jobs Package](https://docs.unity3d.com/Packages/com.unity.jobs@latest//index.html)　　→`Custom job types`の項目を参照
    - ※[`Jobs 0.7.0-preview.17`時点でのドキュメントはこちら](https://docs.unity3d.com/Packages/com.unity.jobs@0.7/manual/custom_job_types.html#custom-nativecontainers)

#### JobSystem

- [C# Job System](https://docs.unity3d.com/ja/2019.4/Manual/JobSystem.html)
    - [C# Job System の概要](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemOverview.html)
    - [マルチスレッドとは](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemMultithreading.html)
    - [ジョブシステムとは何か](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemJobSystems.html)
    - [C# Job System の安全システム](https://docs.unity3d.com/ja/2019.4/Manual/JobSystemSafetySystem.html)

### UnityCsReferen

- [Unity-Technologies/UnityCsReferen](https://github.com/Unity-Technologies/UnityCsReference)
    - [NativeArray.cs - 2019.4](https://github.com/Unity-Technologies/UnityCsReference/blob/2019.4/Runtime/Export/NativeArray/NativeArray.cs)

## 参考資料

### 講演資料

- **DOTS**
    - [たのしいDOTS 〜初級から上級まで〜](https://learning.unity3d.jp/3252/)
    - [大量のオブジェクトを含む広いステージでも大丈夫、そうDOTSならね](https://learning.unity3d.jp/3266/)
    - [【CEDEC2018】CPUを使い切れ！ Entity Component System（通称ECS） が切り開く新しいプログラミング](https://www.slideshare.net/UnityTechnologiesJapan/cedec2018cpu-entity-component-systemecs)
        - [講演動画](https://www.youtube.com/watch?v=T8lGD__aSm8)
- **C#**
    - [Understanding C# Struct All Things](https://learning.unity3d.jp/3305/)

### 参考サイト

- **DOTS**
    - [【Unity】Allocator.TempJobで作ったNativeArrayを自動的に開放する](http://tsubakit1.hateblo.jp/entry/2018/05/21/101354)
- **C#**
    - [Blittable 型と非 Blittable 型](https://docs.microsoft.com/ja-jp/dotnet/framework/interop/blittable-and-non-blittable-types)
    - [unsafe](https://ufcpp.net/study/csharp/sp_unsafe.html)
    - [値型と参照型](https://programming.pc-note.net/csharp/type_value_reference.html)
- **Unity**
    - [AsyncGPUReadbackでRenderTextureをTexture2Dに変換する](https://qiita.com/UnagiHuman/items/583219cb0366b758a7fe)
        - [AsyncGPUReadbackRequest](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Rendering.AsyncGPUReadbackRequest.html)
        - [Texture2D.LoadRawTextureData](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Texture2D.LoadRawTextureData.html)
    - [UnityのNativeArrayを使ってASTCをロードする](https://qiita.com/hzn/items/5762cd8d3b0ed1f205f9)
        - [AsyncReadManager](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Unity.IO.LowLevel.Unsafe.AsyncReadManager.html)
        - [【Unity】ファイルを非同期で読み込んでアンマネージドメモリに展開できるAsyncReadManagerを試してみた](https://qiita.com/mao_/items/8f95bc9dbeb3179ba4b9)
    - [【Unity】UnsafeUtilityについて纏めてみる](https://qiita.com/mao_/items/fc9b4340b05e7e83c3ff)