テラシュールブログ

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

【Unity】構造体のデータを異なる型に「再解釈」する

 DOTS…特にECSで色々とやっていると、内部のデータは同じなのに型が違うせいで変換しないといけないというケースがあります。

 例えば float のバッファを流し込んでバッチ処理を行うAPIがあった時、ローカルのデータで NativeArray<FloatData> のような構造体の配列は直接流し込めません。しかしメモリ的にはこの二つはほぼ同じものです。そこで Reinterpret<T, U>() でデータを再解釈して NativeArray<FloatData>NativeArray<float>として扱ってみます。

動かない例

 例えば下のようなコードがあったとします。 MyData という構造体が定義されていて、これを ShowFloatLog(NativeArray<float> inputs) で一気に表示したい。当然MyDataは中身がfloatであっても扱いはfloatではないので、下のコードはエラーになります。

using Unity.Collections;
using UnityEngine;

struct MyData { public float Value; }

public class Sample: MonoBehaviour
{
    void Start()
    {
        var input1 = new NativeArray<MyData>(new[] {
            new MyData { Value = 11 },
            new MyData { Value = 22 },
            new MyData { Value = 33 },
        }, Allocator.Temp);

        ShowLog(input1 );

        input1.Dispose();
    }

    static void ShowFloatLog(NativeArray<float> inputs)
    {
        foreach (var data in inputs)
            Debug.Log(data);
    }
}

f:id:tsubaki_t1:20191017232117j:plain
型が違うのでエラー

Reinterpret<T, U>()で異なる型へ「再解釈」する

 Reinterpret<T, U>()を使用して、NativeArray<MyData>()をNativeArray()に解釈します。このAPICollectionパッケージに含まれているので、パッケージのインポートが必要です。

f:id:tsubaki_t1:20191017232535j:plain
Entitiesを使うと勝手に入るCollectionパッケージ

 下のように使用します。MyDataの中身がfloatしかないので、floatと解釈することが出来ています。なお、あくまで解釈してるだけなのでDisposeはしてはいけません。参照先のポインタは同じものです。

    void Start()
    {
        var input1 = new NativeArray<MyData>(new[] {
            new MyData { Value = 11 },
            new MyData { Value = 22 },
            new MyData { Value = 33 },
        }, Allocator.Temp);

        // MyDataをFloatに再解釈
        var floatInput = input1.Reinterpret<MyData, float>();

        // NativeArray<float>なので動作
        ShowLog(floatInput);

        input1.Dispose();
    }

 この再解釈はポインタ的に、内部データが同じなら色々なデータに解釈できるみたいです。例えばfloat3の配列をfloatに解釈するといった事も可能です。当然、データの長さが異なるので注意が必要ですが、少し面白いと思わなくもないです。

 なお float3 -> float の場合は特に問題は無いですが、 float -> float3 の場合は3で割り切れる数の要素数でないとエラーになります。多い方がベクタライズに有利になりやすいみたいですが、そこのところは注意が必要です。

    void Start()
    {
        var input1 = new NativeArray<float3>(new[] {
            new float3(111, 222, 33),
            new float3(444, 555, 666),
            new float3(777, 888, 999),
        }, Allocator.Temp);

        // float3をfloatに再解釈
        var floatInput = input1.Reinterpret<float3, float>();

        // output : input length 3, reinterpret length 9
        // float3をfloatにするにあたり、配列の長さが変わっている
        Debug.Log($"input length {input1.Length}, reinterpret length {floatInput.Length}");

        // 111 ~ 999 までの要素を個別に出力
        ShowLog(floatInput);

        input1.Dispose();
    }

感想

 NativeArray<Vector3>を返してくる古いAPIと、NativeArray<float3>を要求する新しいAPIの間で苦しんだ時に思い出すと幸せになるかも。