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

Resolved [Fixed in 1.0.0-pre.65] (IN-32522, 1.0.0-pre.44) Idiomatic foreach repeats RefRW/RefRO values

Discussion in 'Entity Component System' started by Anthiese, Feb 17, 2023.

  1. Anthiese

    Anthiese

    Joined:
    Oct 13, 2013
    Posts:
    72
    It looks like idiomatic foreach with (SystemAPI.Query) can end up using the same component in memory for multiple iterations. I sure hope I'm just doing something totally wrong here.

    The repro below makes 4 entities with different stored component values. Printing output from the foreach, you get back 4 of the same value (and memory address) when they should be different. Assuming the entities from CreateEntity are in order and in the same chunk, it seems like all 4 iterations get a reference to the first entity's component data.

    Code (CSharp):
    1.  
    2. using Unity.Collections;
    3. using Unity.Entities;
    4.  
    5. namespace ReproAssembly
    6. {
    7.     internal partial struct ReproIFESystem : ISystem
    8.     {
    9.         // Burst/not does not affect it
    10.         /*[Unity.Burst.BurstCompile]*/
    11.         public unsafe void OnUpdate(ref SystemState state)
    12.         {
    13.             const int n = 4;
    14.             NativeArray<ComponentType> array = new(1 /*2*/, Allocator.Temp);
    15.             array[0] = ComponentType.ReadOnly<DummyIntComponent>();
    16.             //array[1] = ComponentType.ReadOnly<DummyFloatComponent>();
    17.             var archetype = state.EntityManager.CreateArchetype(array);
    18.             array.Dispose(); // (Allocator.Temp disposal is noop)
    19.             var entities = state.EntityManager.CreateEntity(archetype, n, Allocator.Temp);
    20.             for (int i = 0; i < n; i++)
    21.             {
    22.                 state.EntityManager.SetComponentData(entities[i], new DummyIntComponent { Value = i + 1 });
    23.             }
    24.             entities.Dispose();
    25.             UnityEngine.Debug.Log("IFE start");
    26.             foreach ((RefRO<DummyIntComponent> intComponent, Entity entity)
    27.                      in SystemAPI.Query<
    28.                          RefRO<DummyIntComponent>
    29.                      >().WithEntityAccess())
    30.             {
    31.                 fixed (void* intPtr = &intComponent.ValueRO)
    32.                 {
    33.                     UnityEngine.Debug.Log($"{entity.Index}:{entity.Version}: addr {(ulong)intPtr}, value {intComponent.ValueRO.Value}");
    34.                 }
    35.             }
    36.             state.Enabled = false;
    37.         }
    38.     }
    39.  
    40.     internal struct DummyIntComponent : IComponentData
    41.     {
    42.         public int Value;
    43.     }
    44.  
    45.     internal struct DummyFloatComponent : IComponentData
    46.     {
    47.         public float Value;
    48.     }
    49. }
    50.  
    upload_2023-2-17_4-23-7.png

    This happens for 1..n RefRO/RefRW component parameters.

    I tried bumping this up to 4000 entities. I get back batches of 128.

    upload_2023-2-17_4-30-17.png
     
    JooleanLogic and Kmsxkuse like this.
  2. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    297
    I can reproduce this issue myself as well.

    It seems like the signature of the foreach statement breaks in extremely weird ways:
    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using UnityEngine;
    5.  
    6. [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
    7. public partial struct TestSystem : ISystem
    8. {
    9.     [BurstCompile]
    10.     public void OnCreate(ref SystemState state)
    11.     {
    12.         EntityManager em = state.EntityManager;
    13.  
    14.         EntityArchetype arch = em.CreateArchetype(new NativeArray<ComponentType>(2, Allocator.Temp)
    15.         {
    16.              [0] = ComponentType.ReadOnly<TestStruct>(),
    17.              [1] = ComponentType.ReadOnly<AltStruct>()
    18.         });
    19.  
    20.         for (var i = 0; i < 4; i++)
    21.         {
    22.              Entity entity = em.CreateEntity(arch);
    23.              em.SetComponentData(entity, new TestStruct { Value = i + 1 });
    24.              em.SetComponentData(entity, new AltStruct { Value = 3 - i });
    25.         }
    26.  
    27.         // ----------- This works. -----------
    28.         // foreach (RefRO<TestStruct> test in SystemAPI.Query<RefRO<TestStruct>>())
    29.         // foreach (var (test, ent) in SystemAPI.Query<RefRO<TestStruct>>().WithEntityAccess())
    30.         // foreach ((TestStruct test, Entity ent) in SystemAPI.Query<TestStruct>().WithEntityAccess())
    31.         // foreach (var (test, alt) in SystemAPI.Query<RefRO<TestStruct>, RefRO<AltStruct>>())
    32.         // foreach ((var test, var alt) in SystemAPI.Query<RefRO<TestStruct>, RefRO<AltStruct>>())
    33.  
    34.         // ----------- This does not work! -----------
    35.         // foreach ((RefRW<TestStruct> test, RefRO<AltStruct> alt) in SystemAPI.Query<RefRW<TestStruct>, RefRO<AltStruct>>())
    36.  
    37.         // ----------- Partially works! -----------
    38.         // Note that only the implicit or value copied element works, the explicitly defined RefRO does not.
    39.         // foreach ((RefRO<TestStruct> test, Entity ent) in SystemAPI.Query<RefRO<TestStruct>>().WithEntityAccess())
    40.         foreach ((var test, RefRO<AltStruct> alt) in SystemAPI.Query<RefRW<TestStruct>, RefRO<AltStruct>>())
    41.         {
    42.             Debug.Log($"T {test.ValueRO.Value} A {alt.ValueRO.Value}");
    43.         }
    44.     }
    45. }
    46.  
    47. public struct TestStruct : IComponentData
    48. {
    49.     public int Value;
    50. }
    51. public struct AltStruct : IComponentData
    52. {
    53.     public int Value;
    54. }
    55.  
    Using explicit typing using a RefRO or a RefRW wrapper around a component that requires deconstruction (i.e., more than 1 component being queried) results in the component breaking in iteration.
     
    Last edited: Feb 18, 2023
    Anthiese likes this.
  3. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    908
    This is the code to reproduce the error:
    The bug only occurs in entities 1.0.0-pre.44 not 1.0.0-pre.15
    Code (CSharp):
    1. public struct TestStruct : IComponentData
    2.     {
    3.         public int Value;
    4.     }
    5.    
    6.     public partial struct QueryTestSystem : ISystem
    7.     {
    8.         public unsafe void OnCreate(ref SystemState state)
    9.         {
    10.             EntityManager em = state.EntityManager;
    11.  
    12.             EntityArchetype arch = em.CreateArchetype(new NativeArray<ComponentType>(1, Allocator.Temp)
    13.             {
    14.                 [0] = ComponentType.ReadOnly<TestStruct>()
    15.             });
    16.  
    17.             for (var i = 0; i < 4; i++)
    18.             {
    19.                 Entity entity = em.CreateEntity(arch);
    20.                 em.SetComponentData(entity, new TestStruct() { Value = i + 1 });
    21.             }
    22.            
    23.             // this works
    24.             foreach (var (test, ent) in SystemAPI.Query<RefRO<TestStruct>>().WithEntityAccess())
    25.             {
    26.                 fixed (void* intPtr = &test.ValueRO)
    27.                 {
    28.                     Debug.Log($"{ent.Index}:{ent.Version}: add {(ulong)intPtr}, value {test.ValueRO.Value}");
    29.                 }
    30.             }
    31.            
    32.             // this doesn't
    33.             foreach ((RefRO<TestStruct> test, Entity ent) in SystemAPI.Query<RefRO<TestStruct>>().WithEntityAccess())
    34.             {
    35.                 fixed (void* intPtr = &test.ValueRO)
    36.                 {
    37.                     Debug.Log($"{ent.Index}:{ent.Version}: add {(ulong)intPtr}, value {test.ValueRO.Value}");
    38.                 }
    39.             }
    40.         }
    41.  
    42.         public void OnDestroy(ref SystemState state)
    43.         {
    44.            
    45.         }
    46.  
    47.         public void OnUpdate(ref SystemState state)
    48.         {
    49.         }
    50.     }
     
  4. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Great catch Anthiese! The generated source offers no clues so would love to know what's going on here.
    Luckily I've used var everywhere cos this is the kind of bug that would cause me hair loss.