Hey guys,
I have a system that schedules two jobs with the second one depending on the first one. The first one, writes to a NativeMultiHashMap using the ParallelWriter while the second one reads from that map and does some other stuff based on it. I was assuming that when I pass the JobHandle get from scheduling the first job to the schedule function of the second one, that there shouldn’t be any conflicts regarding access to the map. Still, I get the following error:
InvalidOperationException: The previously scheduled job EventHandlerSystem:OnUpdate_LambdaJob0 writes to the Unity.Collections.NativeMultiHashMap`2[Unity.Entities.Entity,System.Int32] OnUpdate_LambdaJob0.JobData.constantIrrigationChangesPV. You must call JobHandle.Complete() on the job EventHandlerSystem:OnUpdate_LambdaJob0, before you can read from the Unity.Collections.NativeMultiHashMap`2[Unity.Entities.Entity,System.Int32] safely.
So, either I have a fundamental misunderstanding of how the dependencies work or I’m missing something here. I’d appreciate it if you could point out to me what might be going wrong here. When I force the first job to Complete(), everything works as expected but as mentioned I would assume that this is the case usig the dependencies. Anyway, here is the complete code for my system. I’m aware that I don’t really need to do all the JobHandle combines since some of that is done implicitly as I understand it (for the Entities.ForEach at least) but I just wanted to make it super explicit and see if that changes anything (well, it doesn’t):
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class EventHandlerSystem : SystemBase
{
EndSimulationEntityCommandBufferSystem commandBufferSystem;
private EntityQuery eventEntitiesQuery;
protected override void OnCreate()
{
// Acquire/create command buffer
commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
EntityCommandBuffer.ParallelWriter commandBuffer = commandBufferSystem.CreateCommandBuffer().AsParallelWriter();
int eventCount = eventEntitiesQuery.CalculateEntityCount();
NativeMultiHashMap<Entity, int> constantIrrigationChanges = new NativeMultiHashMap<Entity, int>(eventCount, Allocator.TempJob);
NativeMultiHashMap<Entity, int>.ParallelWriter constantIrrigationChangesPV = constantIrrigationChanges.AsParallelWriter();
JobHandle accumulateJobDependency = Entities
.WithStoreEntityQueryInField(ref eventEntitiesQuery)
.ForEach((in ConstantIrrigationChangeEvent constantIrrigationChange, in int entityInQueryIndex, in Entity entity) =>
{
constantIrrigationChangesPV.Add(constantIrrigationChange.targetEntity, constantIrrigationChange.irrigationChange);
commandBuffer.DestroyEntity(entityInQueryIndex, entity);
}).WithBurst()
.ScheduleParallel(this.Dependency);
this.Dependency = JobHandle.CombineDependencies(this.Dependency, accumulateJobDependency);
UpdateConstantIrrigationJob updateJob = new UpdateConstantIrrigationJob()
{
mapKeys = constantIrrigationChanges.GetKeyArray(Allocator.TempJob),
map = constantIrrigationChanges,
constantIrrigationData = GetComponentDataFromEntity<ConstantIrrigation>(false),
ecb = commandBuffer
};
JobHandle updateJobDependency = updateJob.Schedule(constantIrrigationChanges.Count(), 10, this.Dependency);
this.Dependency = JobHandle.CombineDependencies(this.Dependency, updateJobDependency);
constantIrrigationChanges.Dispose(this.Dependency);
commandBufferSystem.AddJobHandleForProducer(this.Dependency);
}
protected override void OnDestroy()
{
}
[BurstCompatible]
public struct UpdateConstantIrrigationJob : IJobParallelFor
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<Entity> mapKeys;
[ReadOnly]
public NativeMultiHashMap<Entity, int> map;
[NativeDisableParallelForRestriction]
public ComponentDataFromEntity<ConstantIrrigation> constantIrrigationData;
public EntityCommandBuffer.ParallelWriter ecb;
public void Execute(int index)
{
for (int i = 0; i < map.Count(); ++i)
{
Entity entity = mapKeys[i];
int sum = 0;
if (map.TryGetFirstValue(entity, out int value, out NativeMultiHashMapIterator<Entity> iterator))
{
sum = value;
while (map.TryGetNextValue(out value, ref iterator))
{
sum += value;
}
if (constantIrrigationData.HasComponent(entity))
{
ConstantIrrigation constIrrigation = constantIrrigationData[entity];
constIrrigation.value = math.max(constIrrigation.value + sum, 0);
constantIrrigationData[entity] = constIrrigation;
if(constIrrigation.value == 0)
{
ecb.RemoveComponent<ConstantIrrigation>(index, entity);
}
} else
{
ecb.AddComponent(index, entity, new ConstantIrrigation()
{
value = sum
});
}
}
}
}
}
}
Thanks a lot for your effort, hope the Dots veterans here can spot the issue!