"The container does not support parallel writing." While I'm not writing anything.

Just made a class to profile the HashMap:

using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;

public class HashTestSystem : JobComponentSystem
{
    [ReadOnly] public NativeHashMap<int3, Entity> HashMap;

    protected override void OnCreate()
    {
        var e = EntityManager.CreateEntity(typeof(HashtableTest));
        HashMap = new Unity.Collections.NativeHashMap<Unity.Mathematics.int3, Entity>(10000, Allocator.Persistent);
        for (int x = 0; x < 100; x++)
        {
            for (int y = 0; y < 100; y++)
            {
                for (int z = 0; z < 100; z++)
                {
                    HashMap.Add(new int3(x, y, z), e);
                }
            }
        }
    }

    protected override JobHandle OnUpdate(JobHandle handle)
    {
        var hm = HashMap;
        Entities.ForEach((ref HashtableTest t) =>
        {
            for (int x = 0; x < 1; x++)
            {
                for (int y = 0; y < 1000; y++)
                {
                    for (int z = 0; z < 1000; z++)
                    {
                        t.b = hm.TryGetValue(new int3(x, y, z), out var e);
                    }
                }
            }
        }).WithName("HASH_TEST").Schedule(handle);

        return handle;
    }
}

public struct HashtableTest : IComponentData
{
    public bool b;
}

Getting error:
InvalidOperationException: <>c__DisplayClass_HASH_TEST.Data.hm is not declared [ReadOnly] in a IJobParallelFor job. The container does not support parallel writing. Please use a more suitable container type.

As you can see I’m not writing to the container.

The job activates a write mode for all captured containers unless you add .WithReadOnly(hm), or wait for ScheduleSingle support for ForEach and let the write mode on for hash map.

[ReadOnly] you put doesn’t work because it isn’t part of internal job fields. (imagine public NativeHashMap<int3, Entity> hm; declared automatically which connects with whats captured to lambda, without [ReadOnly]) If you have a custom struct with NativeHashMap inside then you put [ReadOnly] inside this struct then capturing [ReadOnly] works.

2 Likes

Right, thank you. Someone already told me that before and I forgot.

What about this though:

var matrices = GetComponentDataFromEntity<LocalToWorld>(true);

It is explicitly read only. Shouldn’t it just work? It throws the same error message.

Might be different error. It complains that the container is not ReadOnly.

protected override JobHandle OnUpdate(JobHandle handle)
    {
        var matrices = GetComponentDataFromEntity<LocalToWorld>(true);

        handle = Entities.ForEach((ref Translation translation, in FollowHorizontalPositopn transform) =>
        {
            var pos = matrices[transform.Entity].Position;
            translation.Value = new float3(pos.x, translation.Value.y, pos.z);
        }).Schedule(handle);

        return handle;
    }

InvalidOperationException: <>c__DisplayClass_OnUpdate_LambdaJob0.Data.matrices is not declared [ReadOnly] in a IJobParallelFor job. The container does not support parallel writing. Please use a more suitable container type.

I remembered the same thing happen in IJob/IJobForEach/IJobChunk since long ago that I must add [ReadOnly] to match what I use at OnUpdate.

I have the same problem too, I had to go back to the old scripting API (without using Entities.ForEach) to make this use case work.

Does it work if you do this?

        var matrices = GetComponentDataFromEntity<LocalToWorld>(true);

        handle = Entities.
          WithReadOnly(matrices).
          WithNativeDisableParallelForRestriction(matrices).
          ForEach((ref Translation translation, in FollowHorizontalPositopn transform) =>
        {
            var pos = matrices[transform.Entity].Position;
            translation.Value = new float3(pos.x, translation.Value.y, pos.z);
        }).Schedule(handle);

The safety system would complain if you tried to use CDFE in IJobForeach too, you would need to either ScheduleSingle or turn off the parallel restrictions.

Edit: Actually you shouldn’t even need the NativeDisableParallelForRestriction call as long as you’re using WithReadOnly, it should support parallel reading.

Internally GetComponentDataFromEntity uses an entity query to initialize entity access. The query needs to know whether it’s accessing as read-only or not. You still need to tell any jobs you pass it to that you’re explicitly accessing it as read-only.

Of course I added WithoutRestrictions, it works, just a bit weird that I have to do it.

        handle = Entities.ForEach((ref Translation translation, in FollowHorizontalPositopn transform) =>
        {
            var pos = matrices[transform.Entity].Position;
            translation.Value = new float3(pos.x, translation.Value.y, pos.z);
        }).WithNativeDisableContainerSafetyRestriction(matrices).Schedule(handle);

WithReadOnly(matrices) wasn’t necessary I didn’t even know about that one. Turns out either one works. I’m using
WithReadOnly() now. Thanks.

The (true) argument is telling that to the query if so…

If you do the same line and then pass it to a manually created job wouldn’t it work?

It seems like those are two separate APIs and one of them will be deprecated. Or not…

Nop. You always had to declare permissions in the job, manually created or not, even if the container already has any permission already. If you don’t, the permission will be the default (write) and your job will not run parallel with another job reading the same component.

[ ]'s

Mmm… Okay, where is container’s (bool readonly) used then?

To mark the container as readonly for safe checks only and to track internal dependencies in the ECS. For example, calling Complete to dependencies of a ComponentSystem or a JobComponentSystem with AlwaysSynchronizeSystem.

[ ]'s

1 Like

Just FYI, the latest documentation is wrong in this exact way:

https://docs.unity3d.com/Packages/com.unity.entities@0.10/manual/ecs_lookup_data.html

public struct BufferData : IBufferElementData
{
    public float Value;
}
public class BufferLookupSystem : SystemBase
{
    protected override void OnUpdate()
    {
        BufferFromEntity<BufferData> buffersOfAllEntities
            = this.GetBufferFromEntity<BufferData>(true);

        Entities
            .ForEach((ref Rotation orientation,
            in LocalToWorld transform,
            in Target target) =>
            {
                // Check to make sure the target Entity still exists
                if (!buffersOfAllEntities.Exists(target.entity))
                    return;

                // Get a reference to the buffer
                DynamicBuffer<BufferData> bufferOfOneEntity =
                    buffersOfAllEntities[target.entity];

                // Use the data in the buffer
                float avg = 0;
                for (var i = 0; i < bufferOfOneEntity.Length; i++)
                {
                    avg += bufferOfOneEntity[i].Value;
                }
                if (bufferOfOneEntity.Length > 0)
                    avg /= bufferOfOneEntity.Length;
            })
            .ScheduleParallel();
    }
}