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 Best way to pass Camera position inside an ISystem?

Discussion in 'Entity Component System' started by MatanYamin, May 16, 2023.

  1. MatanYamin

    MatanYamin

    Joined:
    Feb 2, 2022
    Posts:
    109
    Hey everyone,

    I am trying to grab the camera position inside my ISystem burst compile with no luck.

    As for now, the thing I do is to update a single entity's component with a float3 on it with the camera position, and then from inside ISystem, I can read that entity's value of position.

    Is there a better way of doing this?
     
  2. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    Without code it's hard to tell what went wrong, but I'm guessing you're trying to access a managed UnityEngine.Camera in a Burst-compiled system. One workaround is using a SystemBase system to pass data from managed objects into an unmanaged singleton, and then using a Burst-compiled system for everything else.

    This isn't perfect, but as long as you need to touch managed data, I don't think there's any way to avoid having a SystemBase somewhere.

    (I've put this sample together in 5 seconds, let me know if the code is actually any good)

    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Entities;
    3. using Unity.Mathematics;
    4. using UnityEngine;
    5.  
    6. [BurstCompile]
    7. [UpdateInGroup(typeof(SimulationSystemGroup))]
    8. public partial struct ExampleSystem : ISystem
    9. {
    10.     [UpdateBefore(typeof(ExampleSystem))]
    11.     [UpdateInGroup(typeof(SimulationSystemGroup))]
    12.     public sealed partial class UpdateSingletonSystem : SystemBase
    13.     {
    14.         protected override void OnUpdate()
    15.         {
    16.             if (!SystemAPI.HasSingleton<SystemData>())
    17.                 EntityManager.CreateSingleton<SystemData>(nameof(ExampleSystem));
    18.  
    19.             ref var singleton = ref SystemAPI.GetSingletonRW<SystemData>().ValueRW;
    20.  
    21.             singleton.CameraPosition
    22.                 = Camera.main is { } camera && camera
    23.                     ? camera.transform.position
    24.                     : null;
    25.         }
    26.     }
    27.  
    28.     public struct SystemData : IComponentData
    29.     {
    30.         public float3? CameraPosition;
    31.     }
    32.  
    33.     public void OnCreate(ref SystemState state)
    34.         => state.RequireForUpdate<SystemData>();
    35.  
    36.     public void OnDestroy(ref SystemState state) { }
    37.  
    38.     [BurstCompile]
    39.     public void OnUpdate(ref SystemState state)
    40.     {
    41.         var cameraPosition
    42.             = SystemAPI.GetSingleton<SystemData>().CameraPosition
    43.               ?? float3.zero;
    44.     }
    45. }
    46.  
     
  3. MatanYamin

    MatanYamin

    Joined:
    Feb 2, 2022
    Posts:
    109
    Well, that actually went pretty well and works great lol (with minor syntax changes). Thanks for the efficient answer!
     
    apkdev likes this.
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    For this purpose I'm using an "EntityTransform" added to the Camera gameObject.
    Meaning its position will get sync'ed to respective Entity in BeforeSimulationGroup.

    So basically Transform -> TransformAccessArray -> Job -> Entity's Position;
    This covers transform cases for the hybrid setup.
    And as a result you don't have to write sync systems for each potential case.

    Otherwise you can do it as suggested.
    Use managed system to read the value into the component data.
    Then you can read it elsewhere in unmanaged systems.
     
  5. MatanYamin

    MatanYamin

    Joined:
    Feb 2, 2022
    Posts:
    109
    Can you share this kind of solution? I'm intrigued
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Its part of EntitiesExt;
    Basically, EntityTransform puts set transform to the TransformAcessArray and stores the id (index) inside the TAA:
    https://github.com/VergilUa/Entitie.../_Code/Plugins/EntitiesExt/EntityTransform.cs

    TransformContainerSystem contains the TAA and re-uses it [when transforms added / removed during runtime] for better performance. Prior it was just a static class, but that messes up with multiworld setups and requires static manual reset during [disabled] domain "reload". So its a system now.

    Plus TransformContainerSystem maintains aligned array of entities (Entity <-> Transform) to be used inside the job.

    It may look like an overhead to fetch all transforms / entities for the job as first.
    But in reality using single TAA [+ tag checks inside the job] and single array of entities is faster than multiple TAAs that change / allocate often (like TransformSystem pre1.0 did). Burst makes wonders.

    In theory that's the most performant approach under 10k transforms. [Tested for the mobile]
    Most of the overhead comes from actual TAA marshalling & scheduling costs.

    Same system is accessed to fetch TAA for the sync system:
    https://github.com/VergilUa/Entitie...titiesExt/Systems/TransformContainerSystem.cs

    Actual system that does the sync:
    https://github.com/VergilUa/Entitie...mations/Systems/SyncPositionToEntitySystem.cs

    Same TAA is used for all sync types. And sync type varies based on the set tags to the entity.
    For example, there are SyncPositionToEntity which syncs before SimulationGroup runs.
    SyncPositionToTransform that syncs after SimulationGroup is done but before PresentationGroup.
    Same with local TRS.

    As you might've guessed, I'm not using new TransformSystem but rather utilize existing one for the hybrid setup. Transform system [native engine] backend of the Unity is actually quite fast. Its the data processing / user driven / C# wrapper overhead that slows it down. Like abusing root objects as "folders" that prevent parallel processing, extreme hierarchy levels etc.

    In any case, system / data can be modified to be used to sync existing transforms to any data type.
     
    Last edited: May 17, 2023
    MatanYamin likes this.
  7. n3b

    n3b

    Joined:
    Nov 16, 2014
    Posts:
    56
    It's possible to do that without SystemBase

    Code (CSharp):
    1.  
    2.     public partial struct TestSystem : ISystem
    3.     {
    4.         TransformAccessArray arr;
    5.  
    6.         public void OnCreate(ref SystemState state)
    7.         {
    8.             arr = new TransformAccessArray(new[] { Camera.main.transform });
    9.         }
    10.  
    11.         [BurstCompile]
    12.         public void OnUpdate(ref SystemState state)
    13.         {
    14.             state.Dependency = new FunkyCamera().Schedule(arr, state.Dependency);
    15.         }
    16.  
    17.         [BurstCompile]
    18.         public void OnDestroy(ref SystemState state)
    19.         {
    20.             arr.Dispose();
    21.         }
    22.  
    23.         struct FunkyCamera : IJobParallelForTransform
    24.         {
    25.             public void Execute(int index, TransformAccess transform)
    26.             {
    27.                 // it's RW here
    28.                 transform.position = new float3(1);
    29.             }
    30.         }
    31.     }
    Frankly, I'd love to have a random access to TransformAccess via TransformAccessArray, and it's even possible via internal
    Code (csharp):
    1. TransformAccessArray.GetSortedTransformAccess(IntPtr transformArrayIntPtr)
    though that one is only allowed in jobs.
     
    MatanYamin and xVergilx like this.
  8. MatanYamin

    MatanYamin

    Joined:
    Feb 2, 2022
    Posts:
    109
    Thanks for that, I learned more about this whole subject.

    Well, I've actually managed to use it, but I needed to go the other way around and get the camera position and then use it inside IJobChunk, so this is how I did that as for now:

    Code (CSharp):
    1.     [BurstCompile]
    2.     struct CameraPosition : IJobParallelForTransform
    3.     {
    4.         public NativeArray<float3> grabCameraPos;
    5.  
    6.         public void Execute(int index, TransformAccess transform)
    7.         {
    8.             grabCameraPos[0] = transform.position;
    9.         }
    10.     }
    After this job is complete, I take "grabCameraPos" with the updated position of the camera and use it inside iJobChunk for my purposes. Do you have a better suggestion?

    "grabCameraPos" is the pipe for transferring the camera position.

    BTW, thanks again for the answers! Appreciate it a lot!
     
    Last edited: May 18, 2023