Workaround for no arrays in IComponentData?

Hello,
Just starting out learning ECS and trying to wrap my head around not being able to use arrays in IComponentData structs. I wrote this to try to mimic array functionality using a bunch of ints and functions that link the variable to a fake index since I only have a handful of elements. Is this a stupid way of doing this? What’s the proper method?

public struct VoxelElements : IComponentData {
    public int water;
    public int smoke;
    public int fog;
    public int arcane;

    public int Read(int index) {
        switch (index) {
            case 0: return water;
            case 1: return smoke;
            case 2: return fog;
            case 3: return arcane;
            default: return -1;
        }
    }

   
   public void Write(int index, int value) {
        switch (index) {
            case 0: water = value; break;
            case 1: smoke = value; break;
            case 2: fog = value; break;
            case 3: arcane = value; break;           
        }
    }
    }
}

The proper way is either:

  • fixed array
  • BlobAssetReference with the desired array
  • IBufferElementData instead of IComponentData
  • Unsafe colletions (but I think that the other alternatives are way most suitable in 90% of the cases)

Can you combine these values into a single int/long using bitwise operations? Ie restrict value range of each to short and you can use a single int.

Alternative: one component per value.

Also note that using switch will prevent Burst from vectorizing code.

I have an article for that.

3 Likes

Thanks for the article. I am now trying to convert to FixedList. Is there a difference between what can be used in IComponentData and what can be used in OnUpdate? I’m running into this:
The type ‘AtmosphereSystem.AtmoNeed’ must be non-nullable value type, along with all fields at any level of nesting, in order to use it as a parameter ‘T’ in the generic type or method ‘NativeList’

public partial class AtmosphereSystem : SystemBase
{
    protected override void OnUpdate() {
        NativeList<AtmoNeed> needs = new NativeList<AtmoNeed>(Allocator.TempJob);
    }
}

public struct AtmoNeed {
    public FixedList64Bytes<int> elemDiff;
}

Still trying to understand what can be used where, thanks for your help.

Can’t have native collections inside native collections as a general idea.
Can’t have native collections inside structs as part of components.

Nobody stops you to have pointers or indexes for collections that are stored elsewhere though.
Stored unsafe collections are pointers basically, but its a whole diffent can of worms.

Also, in the initial post, you can have fixed array.
Note not native collection, but rather have a this[ ] operator (indexer)
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/this

This way, you’d still have separate int values, but will be able to do

int value = voxel[i];
// or
voxel[i] = *somevalue*;

instead of respective Read / Write methods

Alternatively, if you only need one bit to determine wheter element is set, 4 bytes is a bit a of a waste.
Perhaps bitmask would be a better option? Then you can use single int / enum value to determine if element is set.

Thanks for the clarification. So it looks like I cant use FixedList either if I intend to store multiple in another list.

The “elements” are going to value 1-100, they represent how much of an “element” is in a voxel of space at any time in order to do a spreading simulation. So 86 units of smoke would spread some to its neighbors. So int is correct for this usage?

Can you elaborate further on how to use “this” with fixed array? I read the doc you linked but failed to see how it applied to the example. Also, would this still be illegal if its struct were to be stored in an array?
Sorry for the handhold request!

I see. In that case byte is better. Less memory wasted.

As for the indexer:

→ SmallBuffer example, pretty much covers it. Its similar how FixedStringXBytes work.

Idea is to make sequentially layed out memory block that acts as an array. Then provide access to parts of it by the pointer offset based on index. If your total amount of elements does not change by design often, then it might be a valid solution.