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

CompanionGameObjectUpdateTransformSystem is very slow on large scenes

Discussion in 'Entity Component System' started by apkdev, Dec 3, 2020.

  1. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    I am working with a rather large scene and I have a lot of hybrid components (mostly decal projectors). It appears that even though they're static (and are using StaticOptimizeEntity), the CompanionGameObjectUpdateTransformSystem is updating transforms each frame. Looks pretty heavy on the GC, too.

    upload_2021-2-1_11-47-12.png

    I think I might resort to disabling the CompanionGameObjectUpdateTransformSystem entirely because I'm not even using this feature.
     
    Last edited: Feb 1, 2021
    Tony_Max and Ghat-Smith like this.
  2. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    It turned out that disabling the system wasn't a great idea (hybrid component transforms wouldn't have their positions initialized).

    Regretfully, I have ended up manually modifying the Entities package in order to fix this. This is for Entities 0.16 but AFAIK the implementation in 0.17 is currently the same.

    Code (CSharp):
    1. // CompanionGameObjectUpdateTransformSystem.cs
    2.  
    3. protected override unsafe JobHandle OnUpdate(JobHandle inputDeps)
    4. {
    5.     int hasNew = m_NewQuery.CalculateChunkCount();
    6.  
    7.     EntityManager.AddComponent<CompanionGameObjectUpdateTransformSystemState>(m_NewQuery);
    8.     EntityManager.RemoveComponent<CompanionGameObjectUpdateTransformSystemState>(m_DestroyedQuery);
    9.  
    10.     if (hasNew <= 0)
    11.         return inputDeps;
    12.  
    13. // ...
    This avoids running the system update unless there are new objects that need to have their positions updated. Obviously, it breaks companions for moving entities, but I don't currently need those.

    If there are temporary obstacles to improving this system's performance, it would be great if we at least had a built-in way of disabling updates for static companions.
     
    Mockarutan likes this.
  3. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    334
    I have no idea why this system done like that. If you look into CompanionGameObjectUpdateTransformSystem you will see that every frame all your entities with CompanionLink (which is private for some reason) component are used in query which then call ToEntityArray(Allocator.Persistent). So here i have 2 questions: Why not ToEntityArrayAsync AND why Allocator.Persistent, TempJob isn't enough? Then it allocates Transform[] of entity array length and fill it with transforms of CompanionLink gameobjects. Then it pass transform array to TransformAccessArray and do bursted job.
    We have no components to prevent this system to handle hybrid entities. We can't use write groups, because this system doesn't use it. We can't write our own more performant companion systems, because CompanionLink is private. All we can is just refuse to use this workflow and link gameobjects and entities manually like before companion link. OR cache whole package just to be able to modify this strange code.
     
    Last edited: Mar 19, 2021
    Mockarutan likes this.
  4. Mockarutan

    Mockarutan

    Joined:
    May 22, 2011
    Posts:
    158
    This system takes a lot of time for me as well. I'm going to try out making the changes you two has described, but I would love to hear if you come to more conclusions since this was brought up.
     
  5. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    334
    I have rewrited CompanionGameObjectUpdateTransformSystem, but now i think that it is better to just not include this system and just use regular CopyTransformToGameObjectSystem

    Code (CSharp):
    1. #if !UNITY_DISABLE_MANAGED_COMPONENTS
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Transforms;
    7. using UnityEngine.Jobs;
    8.  
    9. struct CompanionGameObjectUpdateTransformSystemState : ISystemStateComponentData
    10. {
    11. }
    12.  
    13. public struct CopyTransformToGameObjectRequest : IComponentData
    14. {
    15. }
    16.  
    17. [UnityEngine.ExecuteAlways]
    18. [UpdateAfter(typeof(TransformSystemGroup))]
    19. public class CompanionGameObjectUpdateTransformSystem : JobComponentSystem
    20. {
    21.     TransformAccessArray m_TransformAccessArray;
    22.  
    23.     EntityQuery m_NewQuery;
    24.     EntityQuery m_ExistingQuery;
    25.     EntityQuery m_DestroyedQuery;
    26.     EntityQuery m_SyncRequestQuery;
    27.  
    28.     protected override void OnCreate()
    29.     {
    30.         m_TransformAccessArray = new TransformAccessArray(0);
    31.  
    32.         m_NewQuery = GetEntityQuery(
    33.             new EntityQueryDesc
    34.             {
    35.                 All = new[] {ComponentType.ReadOnly<CompanionLink>()},
    36.                 None = new[] {ComponentType.ReadOnly<CompanionGameObjectUpdateTransformSystemState>()}
    37.             }
    38.         );
    39.  
    40.         m_ExistingQuery = GetEntityQuery(
    41.             new EntityQueryDesc
    42.             {
    43.                 All = new[]
    44.                 {
    45.                     ComponentType.ReadOnly<CompanionLink>(),
    46.                     ComponentType.ReadOnly<CompanionGameObjectUpdateTransformSystemState>(),
    47.                     ComponentType.ReadOnly<LocalToWorld>(),
    48.                 },
    49.                 Any = new[]
    50.                 {
    51.                     ComponentType.ReadOnly<CopyTransformToGameObject>(),
    52.                     ComponentType.ReadOnly<CopyTransformToGameObjectRequest>()
    53.                 }
    54.             }
    55.         );
    56.  
    57.         m_DestroyedQuery = GetEntityQuery(
    58.             new EntityQueryDesc
    59.             {
    60.                 All = new[] {ComponentType.ReadOnly<CompanionGameObjectUpdateTransformSystemState>()},
    61.                 None = new[] {ComponentType.ReadOnly<CompanionLink>()}
    62.             }
    63.         );
    64.  
    65.         m_SyncRequestQuery = GetEntityQuery
    66.         (
    67.             new EntityQueryDesc
    68.             {
    69.                 All = new[] { ComponentType.ReadOnly<CopyTransformToGameObjectRequest>() }
    70.             }
    71.         );
    72.     }
    73.  
    74.     protected override void OnDestroy()
    75.     {
    76.         m_TransformAccessArray.Dispose();
    77.     }
    78.  
    79.     protected override unsafe JobHandle OnUpdate(JobHandle inputDeps)
    80.     {
    81.         EntityManager.AddComponent<CompanionGameObjectUpdateTransformSystemState>(m_NewQuery);
    82.         EntityManager.RemoveComponent<CompanionGameObjectUpdateTransformSystemState>(m_DestroyedQuery);
    83.  
    84.         var entities = m_ExistingQuery.ToEntityArrayAsync(Allocator.TempJob, out var toEntityHandle);
    85.         toEntityHandle.Complete();
    86.  
    87.         EntityManager.RemoveComponent<CopyTransformToGameObjectRequest>(m_SyncRequestQuery);
    88.  
    89.         var transforms = new UnityEngine.Transform[entities.Length];
    90.         for (int i = 0; i < entities.Length; i++)
    91.         {
    92.             var link = EntityManager.GetComponentData<CompanionLink>(entities[i]);
    93.             transforms[i] = link.Companion.transform;
    94.         }
    95.  
    96.         m_TransformAccessArray.SetTransforms(transforms);
    97.  
    98.         return new CopyTransformJob
    99.         {
    100.             localToWorld = GetComponentDataFromEntity<LocalToWorld>(),
    101.             entities = entities
    102.         }.Schedule(m_TransformAccessArray, inputDeps);
    103.  
    104.     }
    105.  
    106.     [BurstCompile]
    107.     struct CopyTransformJob : IJobParallelForTransform
    108.     {
    109.         [NativeDisableParallelForRestriction] public ComponentDataFromEntity<LocalToWorld> localToWorld;
    110.  
    111.         [DeallocateOnJobCompletion]
    112.         [ReadOnly] public NativeArray<Entity> entities;
    113.  
    114.         public unsafe void Execute(int index, TransformAccess transform)
    115.         {
    116.             var ltw = localToWorld[entities[index]];
    117.             var mat = *(UnityEngine.Matrix4x4*) & ltw;
    118.             transform.localPosition = ltw.Position;
    119.             transform.localRotation = mat.rotation;
    120.             transform.localScale = mat.lossyScale;
    121.         }
    122.     }
    123. }
    124. #endif
     
    thelebaron, apkdev and Mockarutan like this.
  6. StayThirsty

    StayThirsty

    Joined:
    Jul 6, 2013
    Posts:
    33
    @Tony_Max

    I have replaced the CompanionGameObjectUpdateTransformSystem with your rewritten version and my entities sprites are stacked on top of each other and dont move. However I know their positions are accurate because of collision responses but their sprites are just off to the side of the screen and they are all stacked together

    The entities have no children, the sprite renderer component is attached to the root game object

    Could you help me fix this? Also, you believe its better to use CopyTransformToGameObjectSystem, how can I go about that?

    Thanks
     
    Last edited: May 28, 2022
  7. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    334
    Do they have CopyTransformToGameObject or CopyTransformToGameObjectRequest? And can you please share a screen of entity data?

    Also i don't know about what happened with this system in 0.50 update
     
  8. StayThirsty

    StayThirsty

    Joined:
    Jul 6, 2013
    Posts:
    33
    entityScreenshot.png

    I dont see either CopyTransformToGameObject or CopyTransformToGameObjectRequest. I am pretty sure there was an issue with the Entity Query because I was logging the entities length and it was 0

    However I "solved" my issue by editing the original script to create the entities and transforms only once and then reuse them. So no more ~6KB of GC Allocation per frame

    This works if you don't instantiate any new gameobjects which is fine for me since I create all my entities at the start and then pool/reuse them

    I'm still using 0.17 since I can only imagine the things I would have to change if I upgrade to 0.50 :D

    Thank you