Copying ComponentData in IJobChunk and MemCpyStride

Hello,

I took inspiration from someone performantly copying ComponentData per chunk into NativeArrays.
I adjusted it for my use case and it worked as long as the source stride fit the destination stride.
When I wanted to only fill a part of the destination array elements, it did not work anymore.
So I probably did not understood MemCopyStride() correctly.

Here is the code of my new approach:

public struct RuleData
{
    public float3 position;
    public quaternion rotation;
    public float speed;
}
[BurstCompile]
private unsafe struct GatherPositionsJob : IJobChunk
{
    [ReadOnly] public ComponentTypeHandle<LocalTransform> TransformHandle;
    [ReadOnly] public ComponentTypeHandle<CSpeed> SpeedHandle;

    [NativeDisableContainerSafetyRestriction] public NativeArray<RuleData> RuleDataArray;

    [ReadOnly] public NativeArray<int> FirstEntityIndices;

    [BurstCompile]
    public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    {
        var ruleDataPtr = (RuleData*)this.RuleDataArray.GetUnsafePtr();

        var ruleDataDst = ruleDataPtr + this.FirstEntityIndices[unfilteredChunkIndex];

        var positionSize = UnsafeUtility.SizeOf<float3>();
        var rotationSize = UnsafeUtility.SizeOf<quaternion>();
        var speedSize = UnsafeUtility.SizeOf<float>();
        var ruleDataSize = UnsafeUtility.SizeOf<RuleData>();

        var positions = chunk.GetNativeArray(ref this.TransformHandle).Slice().SliceWithStride<float3>(0);
        var rotations = chunk.GetNativeArray(ref this.TransformHandle).Slice().SliceWithStride<quaternion>(12);
        var speeds = chunk.GetNativeArray(ref this.SpeedHandle).Slice().SliceWithStride<float>(0);

        UnsafeUtility.MemCpyStride(ruleDataDst, ruleDataSize, positions.GetUnsafeReadOnlyPtr(), positions.Stride, positionSize, positions.Length);
        UnsafeUtility.MemCpyStride(ruleDataDst, ruleDataSize, rotations.GetUnsafeReadOnlyPtr(), rotations.Stride, rotationSize, rotations.Length);
        UnsafeUtility.MemCpyStride(ruleDataDst, ruleDataSize, speeds.GetUnsafeReadOnlyPtr(), speeds.Stride, speedSize, speeds.Length);
    }
}

So ruleDataSize does not offset the array position of the destination by its size?
I thought the documentation implied it.

Your destination ruleDataDst still needs to be offset, otherwise it’s writing from 0 which is your position. Your next offset for rotation would be ruleDataDst + positionSize.

Also could this be shortened by getting a pointer to the chunk.GetNativeArray(ref this.TransformHandle). And then in your copy:

UnsafeUtility.MemCpyStride(ruleDataDst + positionOffset, ruleDataSize, chunkPtr + positionOffset, chunkEntitySize, positionSize, positions.Length);

And so on.

Your stride is how many bytes to get to the same position in the array of entities, therefore the entity size (however big that is).

1 Like

Hey, thx for your answer.
What do you mean by array of entities? Its an IJobChunk and the data is structured in arrays for each component type that is part of the entity archetype. In what way do I need the byte size of the entities?

I combined position and rotation to custom struct PosRot and added a speed offset.
Its still not working.

private unsafe struct GatherPositionsJob : IJobChunk
{
    [ReadOnly] public ComponentTypeHandle<LocalTransform> TransformHandle;
    [ReadOnly] public ComponentTypeHandle<CSpeed> SpeedHandle;

    [NativeDisableContainerSafetyRestriction] public NativeArray<RuleData> RuleDataArray;

    [ReadOnly] public NativeArray<int> FirstEntityIndices;

    [BurstCompile]
    public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    {
        var ruleDataPtr = (RuleData*)this.RuleDataArray.GetUnsafePtr();

        var posRotSize = UnsafeUtility.SizeOf<PosRot>();
        var speedSize = UnsafeUtility.SizeOf<float>();
        var ruleDataSize = UnsafeUtility.SizeOf<RuleData>();

        var ruleDataDst = ruleDataPtr + this.FirstEntityIndices[unfilteredChunkIndex];
        var speedOffset = posRotSize;

        var posRots = chunk.GetNativeArray(ref this.TransformHandle).Slice().SliceWithStride<PosRot>(0);
        var speeds = chunk.GetNativeArray(ref this.SpeedHandle).Slice().SliceWithStride<float>(0);

        UnsafeUtility.MemCpyStride(ruleDataDst, ruleDataSize, posRots.GetUnsafeReadOnlyPtr(), posRots.Stride, posRotSize, speeds.Length);
        UnsafeUtility.MemCpyStride(ruleDataDst + speedOffset, ruleDataSize, speeds.GetUnsafeReadOnlyPtr(), speeds.Stride, speedSize, speeds.Length);
    }
}

struct PosRot
{
    public float3 position;
    public quaternion rotation;
}

ruleDataDst is the pointer to the RW Array I write to.
ruleDataDst + speedOffset just adds the offset for the speed.
The second parameter in memcpystride is the amount of bytes that are skiped according to the memcpystride examle in the documentation. So it should be the overall size of the elementSize of the array I write to?
I really want to learn this but apparently I need to understand it better. Any help?

Looking into it a bit more, if you know the size of your Entity, that will always be your stride. The stride is number of bytes between ComponentA[0] and ComponentA[1], or ComponentB[0] and [1], of each Entity in the ArchetypeChunk. I’ve broken this down into a ‘before’ although I typed this up, it might have some errors below to fix!

private unsafe struct GatherPositionsJob : IJobChunk
{
    [ReadOnly] public ComponentTypeHandle<LocalTransform> transformHandle;
    [ReadOnly] public ComponentTypeHandle<CSpeed> speedHandle;

    [NativeDisableContainerSafetyRestriction] public NativeArray<RuleData> ruleDataArray;

    [ReadOnly] public NativeArray<int> firstEntityIndices;

    [BurstCompile]
    public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    {
        var ruleDataPtr = (RuleData*)ruleDataArray.GetUnsafePtr() + firstEntityIndices[unfilteredChunkIndex];

        float3* ruleDataPos = &ruleDataPtr->position;
        float4* ruleDataRot = &ruleDataPtr->rotation;
        float* ruleDataSpd = &ruleDataPtr->speed;

        var entityTformPtr = (LocalTransform*)GetComponentDataPtrRO(ref transformHandle);
        float3* entityPos = &entityTformPtr->position;
        float4* entityRot = &entityTformPtr->rotation;
        float* entitySpd = GetComponentDataPtrRO(ref speedHandle);

        for (i = 0; i < chunk.ChunkEntityCount; i++)
        {
            ruleDataPos[i] = entityPos[i];
            ruleDataRot[i] = entityRot[i];
            ruleDataSpd[i] = entitySpd[i];
        }
    }
}

If you happened to find the entitySize, then you could do this:

    {
        var ruleDataPtr = (RuleData*)ruleDataArray.GetUnsafePtr() + firstEntityIndices[unfilteredChunkIndex];

        float3* ruleDataPos = &ruleDataPtr->position;
        float4* ruleDataRot = &ruleDataPtr->rotation;
        float* ruleDataSpd = &ruleDataPtr->speed;

        var entityTformPtr = (LocalTransform*)GetComponentDataPtrRO(ref transformHandle);
        float3* entityPos = &entityTformPtr->position;
        float4* entityRot = &entityTformPtr->rotation;
        float* entitySpd = GetComponentDataPtrRO(ref speedHandle);

        entitySize = ??;
        int totalEntities = chunk.EntityCount;

        UnsafeUtility.MemCpyStride(ruleDataPos, sizeof(RuleData), entityPos, entitySize, sizeof(float3), totalEntities);
        UnsafeUtility.MemCpyStride(ruleDataRot, sizeof(RuleData), entityRot, entitySize, sizeof(float4), totalEntities);
        UnsafeUtility.MemCpyStride(ruleDataSpd, sizeof(RuleData), entitySpd, entitySize, sizeof(float), totalEntities);
    }

According to the documentation the entities in each chunk are not structured like Entity0[A0,B0,C0]; Entity1[A1,B1,C1]; …
Its structured like this: [A0,A1,A2,A3,…]; [B0, B1, B2, B3,…]; [C0, C1, C2, C3,…]
So the sourcestride should be the componenttype size and not the entitysize?

Using the component type size and its working now.

Interesting, I was unsure on this part. Thanks for the info!