Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Bug It's Impossible to get, and enable, a chunk component from the same job.

Discussion in 'Entity Component System' started by PublicEnumE, Oct 28, 2022.

  1. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    In Entites 1.0 experimental, it's not possible to both get a chunk component, and also change its enable state, in the same job.

    Consider the following job:

    Code (CSharp):
    1. public struct FooChunkComponentJob : IJobChunk
    2. {
    3.     public ComponentTypeHandle<FooChunkComponent> fooChunkComponentTypeHandle;
    4.  
    5.     public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    6.     {
    7.         FooChunkComponent fooChunkComponent = chunk.GetChunkComponentData(fooChunkComponentTypeHandle);
    8.  
    9.         chunk.SetComponentEnabled(fooChunkComponentTypeHandle, 0, false);
    10.     }
    11. }
    Scheduling this will cause the following error:


    AssertionException: out-of-range indexInArchetype passed to GetChangeVersionArrayForType
    Assertion failure. Value was False
    Expected: True


    This seems to be because the ComponentTypeHandle<FooChunkComponent> passed into the job doesn't know it's a chunk component. It's TypeIndex.IsChunkComponent == false.

    That's because it was created the normal way that ComponentTypeHandles are created, using:

    SystemState.GetComponentTypeHandle<T>()


    That function offers no way to specify that the TypeIndex should represent a chunk component. And that TypeIndex is immutable after it's been created.

    So when that ComponentTypeHandle is passed into
    chunk.SetComponentEnabled()
    , the code has no way of knowing that we want to enable a chunk component, rather than a regular component. The code looks to see if that chunk has that component type (the non-chunk version), and finds that it doesn't. Then it throws that error.

    We need some way to tell
    chunk.SetComponentEnabled()
    that we're trying to enable a chunk component. Maybe the best solution is a new function, like:

    chunk.SetChunkComponentEnabled()
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Chunk components don't live in normal chunks. They live in meta chunks. And accessing a chunk's chunk component is actually a random access into another chunk. Because of this, setting the enabled state of a chunk component in parallel is not thread safe, and the only way to make it thread safe is to have write-access to the entire meta chunk.

    I suggest switching to meta chunk iteration, which you can do by querying specifically for only the chunk components (as normal components) and adding a readonly ChunkHeader requirement to your query. The ChunkHeader then lets you iterate the normal chunks.
     
    Egad_McDad and PublicEnumE like this.
  3. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Thank you - I had not heard about this option before:

    I'm now looking for documentation/source code for this.
     
  4. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
  6. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I've got this working, and thank you for the advice.

    However, it seems like this has the potential to be much less efficient, and potentially not thread safe:

    1. By iterating over the meta chunks, I'm referencing all normal, entity component data via random access. That's not so bad, but:
    2. Doing this way also prevents me from including the normal, entity component types as part of the Entity Query, which seems like it would omit safety checks and automatic dependency management.
    3. Iterating over all chunk components in the meta chunk will mean iterating over a bunch of normal chunks in one IJobChunk.Execute() call. This could cut back significantly on how much parallelism is possible.
     
    Last edited: Oct 28, 2022
  7. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I wonder if a cleaner approach might involve something like a ComponentLookup<MyChunkComponent>.

    You could pass that into a normal IJobChunk, which iterates over normal entity components. Then you could use the ComponentLookup to set the enable state of a particular chunk component, using the chunk's meta entity.

    I'm not sure if that's currently possible.
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Not really. You are randomly accessing normal chunks, which is what you are doing with normal chunk iteration anyways.
    As long as you have the normal ComponentTypeHandles, you don't have to worry about that. The only thing you need to make sure of is that the normal chunks have the component types you want.
    This is the downside of enableable chunk components. You may want to reserve a full bool chunk component instead.

    Setting the enabled state in parallel via ComponentLookup is not safe, even if you can guarantee you are writing to different bits. Unity's implementation does not update the bits atomically, meaning that adjacent bits can have race conditions.
     
  9. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Thanks for the response.

    What did you mean by this?

    Are you suggesting to create a chunk component so large that it's guaranteed to take up an entire meta chunk's capacity?
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    No. I am saying instead of making the chunk component an enabled component, give it a bool instead. A bool can be written to without potential race conditions with adjacent elements.
     
  11. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    But it can't be used in a query to represent "enabled" data.
     
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Tradeoffs...
     
  13. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I guess I should actually test this approach before assuming it's not performant enough. :)

    Theoretically, if I have enough different jobs that can occupy other worker threads, there may not be a large perf difference.

    This is getting into crazy territory, but I wonder if padding the chunk component to lessen the number of them in one meta chunk could, oddly, increase performance in this case.

    Anyhow, onwards.
     
    PolarTron likes this.