Unity throws here as “Entitymanager.RemoveComponent” invalidates the ComponentTypeHandles and BufferTypeHandles for a job scheduled job.
// Generate vertices and colliders (High LoD)
var updateVerticesQuery = new EntityQueryDesc
{
All = new ComponentType[] { typeof(Chunk_Data), typeof(Chunk_CellBuffer), typeof(Chunk_UpdateVerticesTag) },
None = new ComponentType[] { typeof(Chunk_IsLowLODFlag) }
};
var hasVertexUpdates = GetEntityQuery(updateVerticesQuery);
if (!hasVertexUpdates.IsEmpty)
{
var vertexHandle = new HighLodVertexJobChunk
{
cellCoordinatesLookup = GetComponentLookup<Cell_CoordinatesData>(true),
cellNeighborsLookup = GetBufferLookup<Cell_NeighborsBuffer>(true),
chunkHandle = GetComponentTypeHandle<Chunk_Data>(),
chunkCellsHandle = GetBufferTypeHandle<Chunk_CellBuffer>(),
vStream = vertexAndUVStream.AsWriter(),
cStream = colliderStream.AsWriter()
}.Schedule(hasVertexUpdates, Dependency);
}
EntityManager.RemoveComponent<Chunk_UpdateVerticesTag>(hasVertexUpdates);
How do I make sure none of these entities match this query on my next update frame without forcefully calling JobHandle.Complete() ?
To reiterate the initial problem, Unity throws the following error when trying to remove a component from entities involved in a scheduled job using EntityManager or an EntityCommandBuffer played back immediately:
InvalidOperationException: The previously scheduled job HighLodVertexJobChunk reads from the Unity.Entities.EntityTypeHandle HighLodVertexJobChunk.safety. You must call JobHandle.Complete() on the job HighLodVertexJobChunk, before you can deallocate the Unity.Entities.EntityTypeHandle safely.
The goal is to find a solution that:
- Does not block the main thread (using JobHandle.Complete() for example)
- Guarantees the matching entities will not match again during the next update frame
The puzzling part is this seems like desired behavior that should be common in a data-oriented technology stack, but even ChatGPT seems unable to provide a functioning solution, so I am stumped!
And I posted the error thrown by Unity with your solution. This was with either of the following solutions:
Entities.WithAll<Chunk_UpdateVerticesTag>().ForEach((Entity en) =>
{
EntityManager.RemoveComponent<Chunk_UpdateVerticesTag>(hasVertexUpdates);
}).WithStructuralChanges().Run();
Or
var removeUpdateECB = new EntityCommandBuffer(Allocator.Temp);
var queryAsEntities = hasVertexUpdates.ToEntityArray(Allocator.Temp);
removeUpdateECB.RemoveComponent<Chunk_UpdateVerticesTag>(queryAsEntities);
removeUpdateECB.Playback(EntityManager);
Unless a deferred EntityCommandBuffer (ie using EndSimulationEntityCommandBufferSystem.AddJobHandleForProducer(JobHandle)) is guaranteed to playback before the next update frame, that would not be the solution either.
Is your goal to schedule a job, then remove a component from an entity query immediately on the main thread in the same system’s OnUpdate() that just scheduled the job?
If so, then this is not possible currently.
Yes, that is exactly what I am trying to achieve
But the ultimate goal is to simply make sure the entities do not match this query again on the next update frame.
When do you want the structural change sync point to happen?
At any point before this system checks if the entities match this query again in a future update frame.
Then use an existing EntityCommandBufferSystem, such as BeginInitializationEntityCommandBufferSystem.
Thank you for your suggestion. Would something like this work, then?
// Generate vertices and colliders (High LoD)
var updateVerticesQuery = new EntityQueryDesc
{
All = new ComponentType[] { typeof(Chunk_Data), typeof(Chunk_CellBuffer), typeof(Chunk_UpdateVerticesTag) },
None = new ComponentType[] { typeof(Chunk_IsLowLODFlag) }
};
var hasVertexUpdates = GetEntityQuery(updateVerticesQuery);
if (!hasVertexUpdates.IsEmpty)
{
var vertexHandle = new HighLodVertexJobChunk
{
cellCoordinatesLookup = GetComponentLookup<Cell_CoordinatesData>(true),
cellNeighborsLookup = GetBufferLookup<Cell_NeighborsBuffer>(true),
chunkHandle = GetComponentTypeHandle<Chunk_Data>(),
chunkCellsHandle = GetBufferTypeHandle<Chunk_CellBuffer>(),
//cellColliderVertices = meshData.chunkColliderVertices.AsParallelWriter(),
//chunkVertices = meshData.chunkVertices.AsParallelWriter(),
//chunkUVS = meshData.chunkUVs.AsParallelWriter(),
vStream = vertexAndUVStream.AsWriter(),
cStream = colliderStream.AsWriter()
}.Schedule(hasVertexUpdates, Dependency);
Dependency = vertexHandle;
var ecbSystem = World.GetOrCreateSystemManaged<BeginInitializationEntityCommandBufferSystem>();
var ecb = ecbSystem.CreateCommandBuffer();
ecb.RemoveComponent<Chunk_UpdateVerticesTag>(hasVertexUpdates, EntityQueryCaptureMode.AtPlayback);
ecbSystem.AddJobHandleForProducer(Dependency);
}
Unity did not throw an exception with this implementation. I am not 100% sure on how using deferred command buffers work, but if you tell me it will always run before the next Update() frame, then this appears to be the solution.
The ECB systems execute at points indicated by their name in the groups they belong to. For systems in groups that run once per application frame, any of the init/update/presentation ECB systems will be suitable, and it’s just a matter of using the least number of them possible so you have the least sync points. Be mindful however to use the fixed update ECB systems if running code in that group, to match the update frequency of the group running zero or more times in an update. Any other group with such behaviour (using IRateManager) needs to have its own ECB systems defined in it for reliable updates every time the group runs all its contained systems.
1 Like