I’ve been using ECS frameworks for a while yet but am struggling a bit getting started with the Unity system - It’s great, but there are many solutions to the problem
I have an entity and want to give it a component that represents some kind of Hash (ie, fixed number of bytes and characters A-Z), and another component that references that Hash. I’ve ended up with something like this:
public struct Hash : IBufferElementData
{
public byte Value;
}
public struct SomeParentHash : IBufferElementData
{
public byte Value;
}
Assigning SomeParentHash can potentially be a costly operation as I may have to cycle through a large number of entities to find a good match. What’s the “correct” way to think about this? At first I thought a reactive system, then IJobProcessComponentData (but these are IBufferElementDatas, not IComponentDatas)… etc etc… I’m lost in a sea of options. Can I somehow ditch the IBufferElementDatas and use IComponentDatas with fixed arrays without having to sprinkle unsafeeverywhere? Or continue with one of these strategies? Or is there some other path I’m not recognizing? I feel like I’m barely scratching the surface here.
If you need some kind of collection in your ecs I definitely would go with dynamic buffers. Note you can convert them to nativearrays for fast iterations.
You can use IJobProcessComponentData\IJobProcessComponentDataWithEntity with BufferFromEntity for get buffer from entity and you can use BufferFromEntity.Exists(Entity) for check, exist buffer on entity or not, also, if you want iterate only entities with YourBufferType, you can use [RequireComponentTag(typeof(YourBufferType))]
this is basically what i ended up on, it just feels a bit clunky to me, like creating an empty IComponentData just to tag these with, and not even consuming it in Execute(). it would be nice to have at least IJobProcessBufferElementData and IJobProcessBufferElementDataWithEntity, or ideally some way to mix the two (or way to stuff some kind of fixed array structure into an IComponentData).
//[BurstCompile]
[RequireComponentTag(typeof(BufferA))]
[RequireComponentTag(typeof(BufferB))]
[RequireComponentTag(typeof(BufferC))]
struct Job : IJobProcessComponentDataWithEntity<SomeTag>
{
[ReadOnly] public BufferFromEntity<BufferA> bufferA;
[ReadOnly] public BufferFromEntity<BufferB> bufferB;
[ReadOnly] public BufferFromEntity<BufferC> bufferC;
public void Execute(Entity entity, int index, ref SomeTag data)
{
var someBytes = Encoding.UTF8.GetBytes("HELLO");
var someArray = new BufferA[someBytes.Length];
for (var i = 0; i < someBytes.Length; i++)
{
someArray[i].Value = someBytes[i];
}
bufferA[entity].CopyFrom(someArray);
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var job = new Job()
{
bufferA = GetBufferFromEntity<BufferA>(false)
bufferB = GetBufferFromEntity<BufferB>(false)
bufferC = GetBufferFromEntity<BufferC>(false)
};
return job.Schedule(this, inputDeps);
}
Does this look “about right”? Also, I’m curious why this doesn’t seem to work with Burst. Cheers!
To add to what @eizenhorn said, you could store the bytes in a NativeArray and pass that into the job. Not only will this not access a static property reference, if you mark the passed array as [ReadOnly] then this is a net performance benefit as all jobs can safely read from it.
You also cannot make a managed array new BufferA[someBytes.Length] in a job. But with BufferFromEntity you can use it like a NativeList:
[ReadOnly] public BufferFromEntity<BufferA> bufferAs;
[ReadOnly] public NativeArray<byte> SomeBytes;
public void Execute(Entity entity, int index, [ReadOnly] ref SomeTag data)
{
// if you know the type has the desired dynamic buffer, you don't need to use the Exists(entity) check on the BufferFromEntity<>
var buffer = bufferAs[entity];
buffer.Clear();
buffer.ReInterpret<byte>().AddRange(someBytes); // sizes must match but is otherwise a plain old reinterpret_cast. Use wisely.
}
It can also be “shadow-copied” and masquarade as a normal array if you are not simultaneously changing it’s size via ToArray();
I’ll add that ECB RemoveComponent works with burst, but only the overload that takes a ComponentType struct and not the generics version (since that calls into a static type lookup manager).