コレクション型
配列のような型
いくつかの主な配列風の型は、Unity.Collections.NativeArray<T>
と Unity.Collections.NativeSlice<T>
を含む コアモジュール によって提供されます。このパッケージ自体は以下を備えています。
データ構造体 | 説明 |
---|---|
NativeList<T> | サイズ変更可能な List。スレッドおよび破棄の安全性チェックを備えています。 |
UnsafeList<T> | サイズ変更可能な List。 |
UnsafePtrList<T> | ポインターのサイズ変更可能な List。 |
NativeStream | 追加専用、型なしの一連のバッファ。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeStream | 追加専用、型なしの一連のバッファ。 |
UnsafeAppendBuffer | 追加専用、型なしのバッファ。 |
NativeQueue<T> | サイズ変更可能なキュー。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeRingQueue<T> | 固定サイズの循環バッファ。 |
FixedList32Bytes<T> | 32 バイトの List。2 バイトのオーバーヘッドが含まれるので、30 バイトをストレージに使用できます。最大容量は T によって決まります。 |
FixedList32Bytes<T>
には、より大きなサイズのバリエーションとして、FixedList64Bytes<T>
、FixedList128Bytes<T>
、FixedList512Bytes<T>
、FixedList4096Bytes<T>
があります。
多次元配列型はありませんが、単純にすべてのデータを 1 次元配列にパックすることができます。例えば、int[4][5]
配列なら、代わりに int[20]
配列を使用します (4 * 5
は 20
であるため)。
Entities パッケージを使用する場合、配列または List 風のコレクションには、通常は DynamicBuffer コンポーネントが最適です。
@Unity.Collections.NativeArrayExtensions、@Unity.Collections.ListExtensions、@Unity.Collections.NativeSortExtension も参照してください。
Map 型と Set 型
単一スレッドでの使用に適し、メモリオーバーヘッドが低い。
データ構造体 | 説明 |
---|---|
NativeHashMap<TKey, TValue> | キーと値のペアからなる順序なしの連想配列。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeHashMap<TKey, TValue> | キーと値のペアからなる順序なしの連想配列。 |
NativeHashSet<T> | 一連の固有値。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeHashSet<T> | 一連の固有値。 |
複数スレッドでの使用に適し、メモリオーバーヘッドが高い。
データ構造体 | 説明 |
---|---|
NativeParallelHashMap<TKey, TValue> | キーと値のペアからなる順序なしの連想配列。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeParallelHashMap<TKey, TValue> | キーと値のペアからなる順序なしの連想配列。 |
NativeParallelHashSet<T> | 一連の固有値。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeParallelHashSet<T> | 一連の固有値。 |
@Unity.Collections.NativeMultiParallelHashMap`2 | キーと値のペアからなる順序なしの連想配列。キーを固有にする必要はありません。つまり 2 つのペアに同一のキーを使うことができます。スレッドおよび破棄安全性チェックを備えています。 |
@Unity.Collections.LowLevel.Unsafe.UnsafeMultiParallelHashMap`2 | キーと値のペアからなる順序なしの連想配列。キーを固有にする必要はありません。つまり 2 つのペアに同一のキーを使うことができます。 |
@Unity.Collections.ParallelHashSetExtensions、@Unity.Collections.NotBurstCompatible.Extensions、@Unity.Collections.LowLevel.Unsafe.NotBurstCompatible.Extensions も参照してください。
ビット配列とビットフィールド
データ構造体 | 説明 |
---|---|
BitField32 | 32 ビットの固定サイズの配列。 |
BitField64 | 64 ビットの固定サイズの配列。 |
NativeBitArray | 任意のサイズのビット配列。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeBitArray | 任意のサイズのビット配列。 |
String 型
データ構造体 | 説明 |
---|---|
NativeText | UTF-8 でエンコードされた文字列。可変かつサイズ変更可能。スレッドおよび破棄安全性チェックを備えています。 |
FixedString32Bytes | 32 バイト UTF-8 でエンコードされた文字列。3 バイトのオーバーヘッドが含まれるので、29 バイトをストレージに使用できます。 |
FixedString32Bytes
には、より大きなサイズのバリエーションとして、FixedString64Bytes
、FixedString128Bytes
、FixedString512Bytes
、FixedString4096Bytes
があります。
FixedStringMethods も参照してください。
その他の型
データ構造体 | 説明 |
---|---|
NativeReference<T> | 単一値への参照。長さ 1 の配列と機能的に同等です。スレッドおよび破棄安全性チェックを備えています。 |
UnsafeAtomicCounter32 | 32 ビットのアトミックカウンター。 |
UnsafeAtomicCounter64 | 64 ビットのアトミックカウンター。 |
ジョブ安全性チェック
ジョブ安全性チェックの目的は、ジョブの競合を検出することです。以下のような場合に 2 つのジョブが競合します。
- 両方のジョブが同じデータにアクセスする。
- 一方または両方のジョブにデータへの書き込みアクセス権限がある。
つまり、どちらのジョブにもデータへの読み取り専用アクセス権限しかない場合、競合は発生しません。
例えば、一方のジョブで配列の読み取りを行うと同時に、もう一方のジョブで同じ配列に書き込みを行うことは、一般に望ましくありません。そのため安全性チェックでは、その可能性を競合と見なします。そのような競合を解決するには、一方のジョブをもう一方のジョブの依存関係にして、それらの実行が重複しないようにします。2 つのジョブのうち、どちらか先に実行したい方を、もう一方の依存関係にする必要があります。
安全性チェックが有効になっているときは、各 Native-
コレクションがスレッド安全性チェックを実行するための AtomicSafetyHandle
を持ちます。ジョブのスケジューリングを行うと、そのジョブ内のすべての Native-
コレクションの AtomicSafetyHandle
がロックされます。ジョブが完了すると、そのジョブ内のすべての Native-
コレクションの AtomicSafetyHandle
が解放されます。
Native-
コレクションの AtomicSafetyHandle
がロックされている間は、以下のようになります。
- そのコレクションを使用するジョブは、同様にそれを使用するスケジュール済みのすべてのジョブに依存する場合にのみスケジュールできる。
- メインスレッドからそのコレクションにアクセスすると、例外が発生する。
ジョブでの読み取り専用アクセス権限
特殊なケースとして、2 つのジョブが両方とも同じデータの読み取りだけを厳密に行う場合、競合は発生しません。例えば、一方のジョブが配列からの読み取りを行うと同時に、もう一方のジョブも同じ配列からの読み取りを行う場合、競合は発生しません。
ReadOnlyAttribute は、ジョブ構造体内の Native-
コレクションを読み取り専用としてマークします。
public struct MyJob : IJob
{
// This array can only be read in the job.
[ReadOnly] public NativeArray<int> nums;
public void Execute()
{
// If safety checks are enabled, an exception is thrown here
// because the array is read only.
nums[0] = 100;
}
}
コレクションを読み取り専用としてマークすることには、以下の 2 つのメリットがあります。
- コレクションを使用するスケジュール済みのすべてのジョブが読み取り専用アクセス権限のみを持つ場合に、メインスレッドでそのコレクションの読み取りを継続できる。
- 同じコレクションへの読み取り専用アクセス権限を持つ複数のジョブをスケジュールする場合、それらのジョブ間に依存関係がなくても安全性チェックの対象にならない。そのため、それらのジョブを同時に実行できる。
Enumerator (列挙子)
ほとんどのコレクションには、IEnumerator<T>
の実装を返す GetEnumerator
メソッドがあります。Enumerator の MoveNext
メソッドは、その Current
プロパティを次の要素へと進めます。
NativeList<int> nums = new NativeList<int>(10, Allocator.Temp);
// Calculate the sum of all elements in the list.
int sum = 0;
NativeArray<int>.Enumerator enumerator = nums.GetEnumerator();
// The first MoveNext call advances the enumerator to the first element.
// MoveNext returns false when the enumerator has advanced past the last element.
while (enumerator.MoveNext())
{
sum += enumerator.Current;
}
// The enumerator is no longer valid to use after the array is disposed.
nums.Dispose();
並列のリーダーとライター
一部のコレクション型には、並列ジョブからの読み取りと書き込み用にネスト状の型が含まれています。例えば、並列ジョブから NativeList<T>
に安全に書き込みを行うには、NativeList<T>.ParallelWriter
が必要です。
NativeList<int> nums = new NativeList<int>(1000, Allocator.TempJob);
// The parallel writer shares the original list's AtomicSafetyHandle.
var job = new MyParallelJob {NumsWriter = nums.AsParallelWriter()};
public struct MyParallelJob : IJobParallelFor
{
public NativeList<int>.ParallelWriter NumsWriter;
public void Execute(int i)
{
// A NativeList<T>.ParallelWriter can append values
// but not grow the capacity of the list.
NumsWriter.AddNoResize(i);
}
}
これらの並列リーダーおよびライターは、通常はコレクションのすべての機能には対応していないので、注意してください。例えば、NativeList
は並列ジョブで容量を拡大することができません (同期オーバーヘッドを大幅に増やすことなく安全に拡大する方法がないため)。
確定的な読み取りと書き込み
ParallelWriter
は同時書き込みの安全性は保証しますが、同時書き込みの順序はスレッドスケジューリングの偶発性に依存する (オペレーティングシステムとその他のプログラムの制御外の要因によって左右される) ので、本質的には不確定です。
同様に、ParallelReader
は同時読み取りの安全性は保証しますが、同時読み取りの順序は本質的に不確定なので、どのスレッドがどの値を読み取るかは不明です。
解決策の一例としては、読み取りと書き込みをスレッドごとに別のバッファに分離することで不確定性を回避できる、NativeStream または UnsafeStream を使用する方法があります。
または、読み取りを個別の範囲に確定的に分割し、各範囲を独自のスレッドで処理すれば、実質的に並列読み取りを確定的な順序で実行できます。
List に書き込んだ後にデータを確定的にソートすることでも、確定的な順序を実現できます。