In a IJobParallelFor, can not use EntityManager

FEEDBACK:
Instead of needing to explicitly have fields for each ComponentDataFromEntity you want to to check, allow using entityManager.HasComponent() and .GetComponentData(). This would reduce the amount of boilerplate needed and be more intuitive to DOTS beginners.

Original post is as follows:

given a field declared in IJobParallelFor

[NativeDisableParallelForRestriction, ReadOnly]
        public EntityManager em;

trying to call either em.HasComponent() or em.GetComponentData() results in a runtime error:

InvalidOperationException: The Unity.Entities.EntityManager has been declared as [ReadOnly] in the job, but you are writing to it.
Callstack

Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at :0)
Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at :0)
Unity.Entities.EntityManager.GetCheckedEntityDataAccess () (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/EntityManager.cs:83)
Unity.Entities.EntityManager.HasComponent[T] (Unity.Entities.Entity entity) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/EntityManagerValidate.cs:44)
MessageSystem+EventMsgParseJob._processMsg (EventMsg& msg) (at Assets/Scripts/Systems/AudioSystem.cs:100)
MessageSystem+EventMsgParseJob.Execute (System.Int32 index) (at Assets/Scripts/Systems/AudioSystem.cs:84)
Unity.Jobs.IJobParallelForExtensions+ParallelForJobStruct`1[T].Execute (T& jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at :0)

I am assuming this error is shown as a mistake, as I’m querying, not writing. to EntityManager. In addition, if I use ComponentDataFromEntity that works, where EntityManager does not.

Below is the full example job that shows the problem, errors at runtime on line 45:

    private struct EventMsgParseJob : IJobParallelFor
    {

        [Unity.Collections.LowLevel.Unsafe.NativeSetThreadIndex]
        public int nativeThreadIndex;
        /// <summary>
        /// don't use this reference, but it's included so we tell burst that we do read-write with it so other jobs touching it don't run at the same time.
        /// other jobs using it should use the [ReadOnly] attribute.
        /// </summary>
        public NativeArray<UnsafeList<EventMsg>> threadQueue;
        /// <summary>
        /// Thread local storge.  ptr to NativeArray<UnsafeList<EventMsg>>
        /// </summary>
        [NativeDisableUnsafePtrRestriction]
        public UnsafeList<EventMsg>* p_threadQueue;
        [NativeDisableParallelForRestriction, ReadOnly]
        public ComponentDataFromEntity<OnKill> onKillData;
        [NativeDisableParallelForRestriction]
        public NativeQueue<AudioSystem.AudioMessage>.ParallelWriter audioIn;
        [NativeDisableParallelForRestriction, ReadOnly]
        public EntityManager em;

        public unsafe void Execute(int index)
        {
            //Debug.Log($"EventTest Thread=${nativeThreadIndex}, index={index}");
            var p_list = p_threadQueue[index].Ptr;
            var count = p_threadQueue[index].length;
            for (var i = 0; i < count; i++)
            {
                _processMsg(ref p_list[i]);
            }
            if (count != p_threadQueue[index].length)
            {
                throw new Exception("race, shouldn't happen!");
            }
            p_threadQueue[index].Clear();
        }

        private void _processMsg(ref EventMsg msg)
        {
            switch (msg.type)
            {
                case EventMsgType.Kill:
                    if (em.HasComponent<OnKill>(msg.target))  //BUG: this causes runtime "you are writing to it" error
                    //if(onKillData.HasComponent(msg.target))  //BUG: this works fine.
                    {
                        //var onKill = onKillData[msg.target];  //BUG: this works fine.
                        var onKill = em.GetComponentData<OnKill>(msg.target); //BUG: this causes runtime "you are writing to it" error
                        audioIn.Enqueue(new AudioSystem.AudioMessage() { type = SystemMessageType.Audio_Sfx, audioFile = onKill.sfxName });                                                                                                                                     //onKill.
                    }
                    break;
            }
        }
    }

this isnt a bug, you cant use the entitymanager in jobs. look at ComponentDataFromEntity instead

Thanks, yeah in my “repro” I show how ComponentDataFromEntity works where EntityManager doesn’t.

Ok maybe not a bug. I’ll mark it as “Feedback” instead.

If you need to do component changes or destroy/ creation inside jobs, there are Entity Command Buffers. And inside of ForEach jobs, you can simply use HasComponent and SetComponent as an boilerplate reduction(which afaik is CDFE under the hood).

The reason this came about is because I’m experimenting with a message system. I’d like to send a message like so:

public struct Msg{
  public MsgType type; //enum
  public Entity sender;
  public Entity target;
  public FixedListInt4096 data;
}

So I need a MsgSystem to process enqueued messages. You can see an example of that on line 24 in the above code. That loops through a list of messages, finds the Entity target, then tries to read a component on that entity (OnKill)

It’s questionable if this is the best message system architecture (my first attempt, and new to Unity AND DOTS), but it “works”. My next step is to study other event/msg systems people have written and find out how to better design it.

because the messages are in an UnsafeList, I can’t use the .ForEach. I can’t find any way of stuffing my msg.target entities into a query, but if you have any ideas I’d love to hear.

Feedback for Unity: The error is misleading. You could really do well with supporting custom error messages for AtomicSafetyHandle methods.

You actually can. It is called ExclusiveEntityTransaction.

It has some massive downsides though so I would need to know a lot more about your goals (what performance problem are you trying to solve?) before I can recommend it.

You can see my prior message for what the goals are. Basically not performance related, but workflow related (message/event system)

If that truly is the case, then you should just do everything in the main thread and not worry about jobs or parallelism.

I’m trying to figure out a valid way of setting up a message/event system using DOTS. That is the primary goal. See post #5 for further elaboration

Can you go into some detail about why you need an event/messaging system? I can’t help but feel like there must be a better way, but I guess it depends on the problem you’re trying to solve

Yeah, Thanks for the sanity check. I should probably just get back to learning lol. I’m kind of getting ahead of myself. I still think the feedback at the top (not able to read from EntityManager in an IJobParallelFor) is valid though.