Search Unity

Transform sync can end up out of sync, get wrong transform data

Discussion in 'Entity Component System' started by ZoinkGames, Jul 3, 2019.

  1. ZoinkGames

    ZoinkGames

    Joined:
    Nov 11, 2015
    Posts:
    20
    Hello

    We've run into an issue which we believe to be a bug.

    We have a bunch of object which still rely to some part on standard systems, hence we're still very much working in a hybrid way and utilizing CopyTransformFromGameObject on most such objects. These are instantiated with IConvertGameObjectToEntity, along with ConvertToEntity set to ConvertAndInjectOriginal

    In certain situations it appears as if the LocalToWorld component data can get incorrect values from the system handling the syncing. We noticed it when we had enemes targeting the player, and for one frame targeting something completely different position.

    It became very apparent when we had a component we added to an entity, removed it and replaced it with another component, and then removed that one in the same frame. (Code examples below)
    The problem went away when we used the same components and methods, but delayed the replacement and removal one frame. I.e., instead of Add -> Remove/Add another component, it works if we do Add -> Remove -> Next frame -> Add

    Are we doing something horriby wrong, or is this a bug?
    Wild guess, but I'm assuming that there occurs some mismatch in the copy transform system due to the entity changing archetypes in an unorthodox manner.

    Our authoring scripts

    Code (CSharp):
    1. public class TestUnitProxy : MonoBehaviour,  IConvertGameObjectToEntity
    2. {
    3.     public Entity entity { get; private set; }
    4.     public EntityManager entityManager { get; private set; }
    5.  
    6.  
    7.     void Update()
    8.     {
    9.         if(Input.GetKeyDown(KeyCode.Return))
    10.         {
    11.             if(!entityManager.HasComponent<TestUnitComponentData>(entity))
    12.             {
    13.                 entityManager.AddComponentData<TestUnitComponentData>(entity, new TestUnitComponentData());
    14.             }
    15.         }
    16.     }
    17.  
    18.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    19.     {
    20.         this.entity = entity;
    21.         entityManager = dstManager;
    22.  
    23.         entityManager.AddComponentData<CopyTransformFromGameObject>(this.entity, new CopyTransformFromGameObject());
    24.  
    25.         dstManager.SetName(entity, name);
    26.     }
    27. }
    Code (CSharp):
    1. public class StationaryProxy : MonoBehaviour, IConvertGameObjectToEntity
    2. {
    3.     public Entity entity { get; private set; }
    4.     public EntityManager entityManager { get; private set; }
    5.  
    6.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    7.     {
    8.         this.entity = entity;
    9.         entityManager = dstManager;
    10.         dstManager.SetComponentData<LocalToWorld>(entity, new LocalToWorld
    11.         {
    12.             Value = new float4x4(transform.rotation, transform.position)
    13.         });
    14.  
    15.         dstManager.AddComponentData<CopyTransformFromGameObject>(entity, new CopyTransformFromGameObject());
    16.  
    17. #if UNITY_EDITOR
    18.         dstManager.SetName(entity, name);
    19. #endif
    20.     }
    21. }
    TestUnitProxy adds an arbitrary component on hitting enter. The other type is just as a counterpoint, I tihnk there needs to be at least two archetypes for it to happen

    The components. Does not seem related to whether they have data or are just tags, I've reproduced it with both cases

    Code (CSharp):
    1. public struct TestUnitComponentData : IComponentData
    2. {
    3. }
    Code (CSharp):
    1. public struct TestUnitSecondComponentData : IComponentData
    2. {
    3. }
    4.  
    The systems

    Code (CSharp):
    1. public sealed class TestUnitSystem : ComponentSystem
    2. {
    3.     #region Fields
    4.     EntityQuery group;
    5.     #endregion // Fields
    6.  
    7.     #region Methods
    8.     #region ComponentSystem
    9.     protected override void OnCreate()
    10.     {
    11.         base.OnCreate();
    12.  
    13.         group = GetEntityQuery(ComponentType.ReadWrite<TestUnitComponentData>());
    14.     }
    15.  
    16.     protected override void OnUpdate()
    17.     {
    18.         NativeArray<Entity> entities = group.ToEntityArray(Allocator.TempJob);
    19.  
    20.         for(int i = 0; i < entities.Length; ++i)
    21.         {
    22.             Entity entity = entities[i];
    23.  
    24.             PostUpdateCommands.AddComponent<TestUnitSecondComponentData>(entity, new TestUnitSecondComponentData());
    25.             PostUpdateCommands.RemoveComponent<TestUnitComponentData>(entity);
    26.         }
    27.  
    28.         entities.Dispose();
    29.     }
    30.    
    31.     #endregion // ComponentSystem
    32.     #endregion // Methods
    33. }
    Code (CSharp):
    1. public sealed class SecondTestUnitSystem : ComponentSystem
    2. {
    3.     #region Fields
    4.     EntityQuery group;
    5.     #endregion // Fields
    6.  
    7.     #region Methods
    8.     #region ComponentSystem
    9.     protected override void OnCreate()
    10.     {
    11.         base.OnCreate();
    12.  
    13.         group = GetEntityQuery(ComponentType.ReadOnly<TestUnitSecondComponentData>());
    14.     }
    15.    
    16.     protected override void OnUpdate()
    17.     {
    18.         EntityManager.RemoveComponent(group, typeof(TestUnitSecondComponentData));
    19.     }
    20.    
    21.     #endregion // ComponentSystem
    22.     #endregion // Methods
    23. }
    I've also uploaded a package with the project I used to reproduce the issue.
     

    Attached Files:

  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Show systems order from entity debugger
     
  3. ZoinkGames

    ZoinkGames

    Joined:
    Nov 11, 2015
    Posts:
    20
    Here ya go
     

    Attached Files:

  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    So your test unit system updates before CopyTransformFromGameObject therefore any game object changes won't be reflected in your system till the next frame.

    (this is actually an issue I have with the transform setup atm. it seems to meant to be end of frame but this isn't possible a lot of the time.)
     
  5. ZoinkGames

    ZoinkGames

    Joined:
    Nov 11, 2015
    Posts:
    20
    Yes, but I believe that is not the case here?

    I'm not doing any actual transform modifications in my test case, but it appears as if an Entity's LocalToWorld component gets transform data form the wrong GameObject when it copies the transform data. I'm assuming the arrays gets mismatched/unsynced.

    Also, a tangent based off of what you mentioned: that is my issue as well with how transform sync work, that both CopyTo and CopyFrom are run at end of frame. At least I think they are, I recall they were setup that way in the beginning but haven't used it since. According to me, it would make more sense if CopyFrom happened at beginning of the frame, and CopyTo and the end. Or vice versa.
    That way you could get all transform data from the beginning of the frame, operate on the LocalToWorld data in your systems, and at the end of the frame sync it back to the transforms. But that is unrelated to the issue at hand, just venting ^^