I have a question is it possible to write to dynamic buffers on different entities from separate jobs?
I have several entities with dynamic buffers. I iterate through them using foreach. Inside this foreach I schedule a job that writes data to it. I can’t make them run in parallel because I get an error that the previously scheduled job did not finish. Those dynamic buffers are independent of each other. Why I can’t write to them at the same time?
The real question is, why are you scheduling an independent job per dynamic buffer? I’m not saying what you are trying to accomplish is objectively bad, but understanding your problem better may help me suggest a better solution.
OK, I will explain what I am doing. I am implementing a flow field. Each entity is one field of set size. It has three buffers for representing targets, cost field and flow field. So first I have a job that clears fields and inits targets. Then a job for calculating a cost field and finally one for the flow field. I would like to run them in parallel because the cost field can’t be run on multiple workers. I could make one IJobForEntity that inside calculates all of these things together on a single maybe that is the way. When ECS was in preview I had a simple prototype for flowfield where I used unsafe native containers on an entity this time I wanted to avoid it, but maybe I will have to revert to this approach. I would like to store data on an entity and not in the system.
Two options for this. Get the NativeList from the query and schedule IJobParallelForDefer jobs and use BufferLookup with [NativeDisableParallelForRestriction].
Other option would be to use my framework and use collection components, which have the behavior you desire (dependencies are tracked by instance rather than type).
I get entities Native list from a query like this:
var entities = _flowFieldQuery.ToEntityListAsync(Allocator.TempJob,state.Dependency, out var handle);
handle = JobHandle.CombineDependencies(state.Dependency, handle);
InvalidOperationException: The previously scheduled job GatherEntitiesJob writes to the Unity.Collections.NativeList`1[Unity.Entities.Entity] GatherEntitiesJob.JobData.OutputList. You must call JobHandle.Complete() on the job GatherEntitiesJob, before you can cast the Unity.Collections.NativeList`1[Unity.Entities.Entity] to a NativeArray safely or use Unity.Collections.NativeList`1[Unity.Entities.Entity].AsDeferredJobArray() to cast the array when the job executes.
I have overcome this temporarily by calling Complete on the handle that comes out of ToEntityListAsync. But I am sure that I am doing something wrong and it is not necessary.
The second question I have is about buffer BufferLookups.
I have this three buffer lookup:
[ReadOnly]
public BufferLookup<FlowFieldTargetElement> FlowFieldTargetLookup;
[NativeDisableParallelForRestriction]
public BufferLookup<FlowFieldElement> FlowFieldLookup;
[NativeDisableParallelForRestriction]
public BufferLookup<CostFieldElement> CostFieldLookup;
I read them like this:
if(!FlowFieldLookup.TryGetBuffer(entity, out var flowFieldBuffer))
return;
if(!CostFieldLookup.TryGetBuffer(entity, out var costFieldBuffer))
return;
if (!FlowFieldTargetLookup.TryGetBuffer(entity, out var targetsBuffer))
return;
var flowField = flowFieldBuffer.Reinterpret<float2>();
var costField = costFieldBuffer.Reinterpret<float>();
var targets = targetsBuffer.Reinterpret<int2>();
But when I use them I get an error
InvalidOperationException: Attempted to access the BufferLookup<FlowFields.Components.FlowFieldTargetElement> InitializeFieldJobDefer.FlowFieldTargetLookup which has been invalidated by a structural change.
I do not add anything to a buffer I am just changing values inside buffers. Do I have to access those buffers differently?
The error message is telling you exactly what you are doing wrong. You are casting a NativeList to NativeArray (either via the deprecated implicit cast or by using AsArray() somewhere in code you didn’t share. You need to use AsDeferredJobArray() instead.
That means you had a structural change on the main thread before scheduling the job. You need to call SystemAPI.GetBufferLookup<>() after all structural changes (or call Update() on the lookups) before scheduling the job.
Thanks for your help. My mistake was that I used NativeArray inside the job instead NativeList.
When I finish this project I plan to make a version with your framework and then compare solutions.