Skip to content

Shared Array

Andrzej Kebab edited this page Feb 4, 2024 · 6 revisions

UtilityLibary.Unity.Runtime

The SharedArray<T, TNative> and SharedArray<T> classes provide a flexible array implementation that can be used as both a NativeArray and a managed array. This allows seamless integration of managed arrays and NativeArrays, enabling easy interoperability in Unity.

A SharedArray is a segment of memory that is represented both as a normal C# array T[], and a Unity NativeArray<T>.

It's designed to reduce the overhead of communicating between C# job data in NativeArray and APIs that use a normal array of structs, such as Graphics.DrawMeshInstanced(), by eliminating the need to copy data.

SharedArray<T, TNative>

Properties

  • Length: Gets the length of the shared array.

Constructors

  • SharedArray(T[] managed): Initializes the SharedArray with a managed array.
  • SharedArray(int size): Initializes the SharedArray with a specified size.

Methods

  • Initialize(T[] managed): Initializes the SharedArray with a managed array.
  • Initialize(): Initializes the SharedArray.
  • CreateNativeAlias(): Creates a NativeArray alias for the managed array.
  • GetPinnableReference(): Allows taking a pointer of the SharedArray in 'fixed' statements.
  • Resize(int newSize): Resizes the array to a new size.
  • Clear(): Clears all elements in the array.
  • GetEnumerator(): Gets an enumerator for the array.

Operators

  • Implicit conversion to NativeArray<TNative>: Allows implicit conversion to a NativeArray of the specified Native type.
  • Implicit conversion to T[]: Allows implicit conversion to a managed array.

IDisposable Implementation

  • Dispose(): Disposes of the array.

IEnumerable<T> Implementation

  • GetEnumerator(): Returns an enumerator for the managed array.

Finalizer

  • ~SharedArray(): Disposes of the array in the finalizer.

SharedArray<T>

Constructors

  • SharedArray(T[] managed): Initializes the SharedArray with a managed array.
  • SharedArray(int size): Initializes the SharedArray with a specified size.

Implicit Conversion

  • Implicit conversion to NativeArray<T>: Allows implicit conversion to a NativeArray.

Methods

  • Initialize(T[] managed): Initializes the SharedArray with a managed array.

Inheritance

  • Inherits from SharedArray<T, T>: Inherits the shared functionality with a native type matching the source type.

Usage Example

// SharedArray implicitly converts to both managed and native array
SharedArray<Vector4> shared = new SharedArray<Vector4>(8);
NativeArray<Vector4> asNative = shared;
Vector4[] asManaged = shared;

Safety System

Unity's job system has a safety system for reading & writing data (in the Editor only). This catches cases where a data race would occur and warns you about it.

SharedArray works with this safety system, so when you access the data on the main thread, the system knows whether it is safe to read or write, just like using a NativeArray allocated the normal way.

Here's all of the operations that include a check of the safety system.

SharedArray<T> sharedArray;  // created elsewhere 

// These 4 operations will check that no jobs are using the data, in any way
T[] asNormalArray = sharedArray; 
sharedArray.Clear();
sharedArray.Resize(32);
sharedArray.Dispose();

// Enumerating in either of these ways will check if any jobs are writing to the data, but allow other readers
foreach(var element in sharedArray) { }

var enumerator = sharedArray.GetEnumerator();

The safest way to use SharedArray is:

  1. Manipulate data in a C# job, using the NativeArray<T> representation
  2. Convert to a managed array T[] only right before you use it on the main thread.

This is important if you want the safety system to work - if you pass around a reference to the managed representation, you won't get the safety system checks.

Aliasing

It's possible to have the NativeArray representation of the data be of a different type than the source managed array.

To do so, create the SharedArray with 2 types instead of 1 :

Vector4[] source = new Vector4[64];
SharedArray<Vector4, float4> shared = new SharedArray<Vector4, float4>(source);
NativeArray<float4> native = shared;
Vector4[] asManaged = shared;

The only safety check that aliasing makes is that the types are both unmanaged and the same size.

Why Alias Types ?

Aliasing was made to eliminate the overhead of converting between analogous types in Unity.Mathematics and UnityEngine (such as float4 <-> Vector4 or float4x4 <-> Matrix4x4).

These Unity.Mathematics types have optimizations specific to the Burst compiler, and replace the existing Unity math structs and methods. We want to get the compiler-specific performance advantage of using those new types, without the overhead of converting back from Unity.Mathematics types.

For types that are laid out the same in memory, we can just treat one like the other. Since we do this for the whole array, there is never any conversion between types happening, and thus no overhead - it's just a different "view" on the same memory.

Clone this wiki locally