Fixed size arrays for components

I need to make components that have a fixed size array of integers, Unity complains that a primitive array inside of a struct (e.g. int[ ]) is a non-blittable type.

I’ve tried searching the forum but the answers I see seem to be focused on arrays that can vary in size. I get that just defining an int[ ] doesn’t force it to have a fixed size, but is there any way to declare a component that does have a fixed size array? I was thinking as a workaround for small array sizes I could declare my components to have a whole series of int4 variables…

Thanks for any help!

Unity.Collections has various fixed lists of different sizes. For example you could use FixedListInt64, which takes up 64 bytes and has space for 15 ints (4 bytes per int * 15 = 60 bytes, the remaining 4 bytes are used to store the length). Or you can use FixedListX if you need custom types.

2 Likes

I don’t like using Unity.Collections … collections to represent fixed sized arrays, since it wastes 4 bytes to keep track of a length. This can cause memory issues and pretty much always disables auto vectorization.
Luckily we have unsafe code!

unsafe public struct MyComponent : IComponentData
{
    public fixed int MyArray[16];
}

Not that fixed arrays do not perform bounds checks, resulting in undefined behavior if the index is out of range. You can and should instead implement such components in the following way:

unsafe public struct MyComponent : IComponentData
{
    private const int SIZE = 16;
    private fixed int MyArray[SIZE];

    [Conditional("DEBUG")]
    private static void BoundsCheck(int index)
    {
        if (index < 0 || index > (SIZE - 1))
        {
            throw new IndexOutOfRangeException();
        }
    }

    public int this[int index]
    {
        get
        {
            BoundsCheck(index);
            return MyArray[index];
        }

        set
        {
            BoundsCheck(index);
            MyArray[index] = value;
        }
    }
}

The Conditional attribute can be replaced by wrapping the method body in a #if DEBUG […] #endif block, of course - it’s all a matter of preference.

The only disadvantage of fixed arrays is that they require the elements to be a C# primitive. If you’re “doing ECS right” that only means that the array accessor needs to convert the underlying values to an IComponentData type (get) or write the underlying value of an IComponentData to the array (set).
If you’re using stuff like int4, though, the implementation looks like this (and should be optimized to a single 16 byte SIMD read/write instruction in Burst code):

public struct MyComponent : IComponentData
{
    public int4 Value;
}

unsafe public struct MyComponentArray : IComponentData
{
    private const int SIZE = 16 * 4;
    private fixed int MyArray[SIZE];

    [Conditional("DEBUG")]
    private static void BoundsCheck(int index)
    {
        if (index < 0 || (index + 3) > (SIZE - 1))
        {
            throw new IndexOutOfRangeException();
        }
    }

    public MyComponent this[int index]
    {
        get
        {
            index *= 4;

            BoundsCheck(index);
          
            return new MyComponent{ Value = new int4(MyArray[index + 0],
                                                                                    MyArray[index + 1],
                                                                                    MyArray[index + 2],
                                                                                    MyArray[index + 3]) };
        }

        set
        {
            index *= 4;

            BoundsCheck(index);

            MyArray[index + 0] = value.Value.x;
            MyArray[index + 1] = value.Value.y;
            MyArray[index + 2] = value.Value.z;
            MyArray[index + 3] = value.Value.w;
        }
    }
}
5 Likes

Thank you for both of these responses – the FixedList solution is working for me, and I will try out the unsafe/fixed version as well. Thanks!

I favour the cleaner approach of being able to allocate as I like with fixed / unsafe, using what C# has always fundamentally provided for this purpose. It’s more C-like. Interesting about the failure to auto-vectorise.

Does this imply that your fixed buffers example above would not be optimised to such an instruction?

You mean when a bunch of components using ints and not int4s are processed linearly? Like you accessing all of the ints of a simple fixed array in sequence just like in the int4 example?
My best guess is that it would be auto-vectorized. I’m at least 95% certain of that. Unless the debug bounds checks are enabled, of course. Even simple branches often make auto-vectorization impossible, currently, even though manual vectorization would be trivial if you knew the native instructions/compiler intrinsics.

2 Likes

Check this post. Might help.