Search Unity

Entity hierarchy architecture

Discussion in 'Entity Component System' started by JooleanLogic, Apr 8, 2018.

  1. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    I'm converting a game to this ECS model and I'm struggling with the correct way to represent and process hierarchy in a an ECS performant way.
    My situation is I have a car with a single ai controller which computes the power values for both the back left and right wheels and assigns the value to them. So one controller per two wheels. The structure is like so:

    Code (CSharp):
    1. Car GameObject
    2.     Car : MonoBehaviour
    3.         GameObject leftWheel;
    4.         GameObject rightWheel;
    5.     AIController : MonoBehaviour
    Very simple and straight forward in the OO approach. The AIController.Update() method computes different power values for the left and right wheels and assigns those values to the wheels through the Car MonoBehaviour. The wheels have their own behaviours that operate on that power value and don't know anything about the aiController.

    In ECS, this works great for the AIController as I can just add an AIControllerComponent to each car entity and update them all in a System loop as they don't need to know about the wheels for their computation. All very good.
    Another system needs to apply the values from the aiComponent to the wheelComponents and this is where I'm unsure.

    Ideally I want something like this. if I have say 50 cars, then in the update system, I'd want a linear array of 50 AIController components and a matching linear array of 100 Wheel components so I could go:
    Code (CSharp):
    1. for(i=0; i<AIComponentArray.Length; i++){
    2.     WheelComponentArray[ i*2 ].power = AIComponentArray[ i ].leftPower;
    3.     WheelComponentArray[ i*2 + 1 ].power = AIComponentArray[ i ].rightPower;
    4. }
    Is this possible? I want to avoid having to look up unaligned components.
    Replicating the OO approach where I have a CarComponent that contains references to the left and right wheel entities, it'd look something like this:

    Code (CSharp):
    1. struct Group{
    2.     int Length;
    3.     ComponentDataArray<CarComponent> carComponents;
    4.     ComponentDataArray<AIComponent> aiComponents;
    5. }
    6.  
    7. for(i=0; i<Group.Length; i++){
    8.     CarComponent carComponent = carComponents[ i ];
    9.     AIComponent aiComponent = aiComponents[ i ];
    10.  
    11.     // Lookup wheel components. This is non-linear data access is it not?
    12.     WheelComponent leftWheelComponent = EntityManager.GetComponentData<WheelComponent>(carComponent.leftWheel);
    13.     WheelComponent rightWheelComponent = EntityManager.GetComponentData<WheelComponent>(carComponent.rightWheel);
    14.  
    15.     // Assign values
    16.     leftWheelComponent.power = aiComponent.leftPower;
    17.     rightWheelComponent.power = aiComponent.rightPower;
    18.  
    19.     // Another lookup to apply changes
    20.     EntityManager.SetComponentData(carComponent.leftWheel, leftWheelComponent);
    21.     EntityManager.SetComponentData(carComponent.rightWheel, rightWheelComponent);
    22. }
    Perhaps there's nothing wrong with the above lookups and I'm just overcomplicating it?
    Cheers.
     
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Why saving the entity of leftWheel and rightWheel?
    You can also reference to the Wheelcollider it self...
     
  3. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    I don't understand what you mean by saving. This is for a pure ecs system. Do you mean why have a wheel entity at all? The wheel has traction and various other properties that translate into physics forces being applied to the wheel.

    I have a system that updates all the ai components and a system that updates all the wheel components but I need a system in the middle that transfers values from the ai component to the wheel components. It's not a 1 to 1 relationship though, it's 1 to 2. How do you combine two different entity sets that are of different sizes?
     
  4. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    Not sure if it is faster
    but it can be done using ComponentGroup.SetFilter

    Code (CSharp):
    1. struct CarId : ISharedComponentData{
    2.   public int id;
    3. }
    4. public struct WheelComponent:IComponentData{
    5.   public bool1 isLeftWheel;
    6.   public float power;
    7. }
    8. public struct AIComponent:IComponentData{
    9.   public float leftPower;
    10.   public float rightPower;
    11. }
    12.  
    13. public class TestSys:ComponentSystem{
    14.   struct CarsGroup{
    15.     public int Length;
    16.     public ComponentDataArray<AIComponent> aiComponents;
    17.     public SharedComponentDataArray<CarId> carIds;
    18.   }
    19.  
    20.   [Inject] private CarsGroup carsData;
    21.   private ComponentGroup  wheelsData;
    22.  
    23.   protected override void OnCreateManager(int capacity){
    24.     wheelsData = GetComponentGroup(typeof(WheelComponent), typeof(CarId));
    25.   }
    26.  
    27.   protected override void OnUpdate(){
    28.     for( int i = 0; i != carsData.Length; i++ ){
    29.       int currentCarId = carsData.carIds[i].id;
    30.       wheelsData.SetFilter(new CarId { id = currentCarId });
    31.       var wheels = wheelsData.GetComponentDataArray<WheelComponent>();
    32.       for(int j = 0; j != wheels.Length; j++ ){
    33.         WheelComponent wheel = wheels[j];
    34.         wheel.power = wheel.isLeftWheel
    35.           ? carsData.aiComponents[i].leftPower
    36.           : carsData.aiComponents[i].rightPower;
    37.         wheels[j]=wheel;
    38.       }
    39.     }
    40.   }
    41.  
    42. }
    43.  
     
    Last edited: Apr 10, 2018
  5. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    There is absolutely nothing wrong with using.

    EntityManager.SetComponentData(carComponent.leftWheel, leftWheelComponent);

    or better

    ComponentDataFromEntity<> in a job.

    When you have references to other objects, thats the correct approach. Complex filtering approaches based on SharedComponentData is absolutely not the way to go.
     
  6. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Thanks for that input SubPixelPerfect. I can see you understand the problem, but that's possibly a bit too heavy of a solution.
    Presumably the filter just compares the needle against the haystack. So for every one of the 50 cars, it's going to iterate the 100 wheels array until it finds the two matching wheels. O(n)?
    I thought of using SharedComponent but that also didn't feel right either. Every car has an AiComponent so making it shared is a bit odd and it also requires that a wheel knows whether it is left or right.
     
  7. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Thanks for the response Joachim.
    So you're saying it's ok for components to contain Entity references like so?
    Code (CSharp):
    1. CarComponent : IComponentData{
    2.     Entity leftWheel;
    3.     Entity rightWheel;
    4. }
    If so that's great. I've been going around in circles trying to find alternate approaches to represent hierarchy.
    So in a situation where a wheel Entity could be destroyed, then before using the Entity reference, you would just preface check it like so?
    Code (CSharp):
    1. if(entityManager.exists(shipComponent.leftWheel))
    2.     ;
    Thanks.
     
  8. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Yes, additionally and very importantly. You can safely read & write to component data by entity lookup from a job!
    (A hierarchy / connection to another entity always implies random memory access, thats inherent in the definition)

    And currently the best pattern is to simply use ComponentDataFromEntity to perform the lookups, it is reasonably well optimised and works in Burst.

    Check out this sample. (In the sample project its the ChainExample.unity) Basically doing simple verlet physics via an IComponentData having a reference to another entity and then in a job reading from other entities position values:

    https://github.com/Unity-Technologi...impleMovement/PositionConstraintSystem.cs#L24
     
  9. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Ah, so ComponentDataFromEntity is like an optimised component lookup object. I used an ecs system years ago and it had the same thing which made the code a bit less verbose as well. It's starting to come back to me. :)
    You're right. It can only be done with sorted arrays which would have to exist outside the ecs.

    Thanks for your help Joachim.
     
  10. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    982
    I just want to confirm my understanding. ComponentDataFromEntity<T> is like Dictionary<Entity, T>, am I right?
     
  11. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    yes