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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Get reference to component in ISystem

Discussion in 'Entity Component System' started by friesenmedien, Feb 19, 2023.

  1. friesenmedien

    friesenmedien

    Joined:
    Mar 15, 2021
    Posts:
    7
    Dumb beginner DOTS 1.0 question:
    How do I get a RO reference to a component (WorldTransform) from an entity (in ISystem)?
    I tried something like this:

    Code (CSharp):
    1. RefRO<WorldTransform> targetWorldTransform = SystemAPI.GetComponent<WorldTransform>(someEntity);
    This obviously throws an error. But how to get the component reference (Readonly)?
     
    frankfringe likes this.
  2. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    761
    In many cases it shouldn't make a difference, except in larger components. A reference stores a pointer, (8 bytes in 64 bit system) so a ref would be even larger than a component that contains less than 8 bytes of data. As long as you don't use this method to call larger components in a loop thousands of times in a frame, it shouldn't have much of an impact. Then maybe a nested loop would have to be considered.
     
    friesenmedien likes this.
  3. friesenmedien

    friesenmedien

    Joined:
    Mar 15, 2021
    Posts:
    7
    Say I have planets and units that can move between them. So every time I want to command some units to move to an other planet, I create a entity to store this information in a component:

    Code (CSharp):
    1.  
    2. public struct SendUnitsToTargetCommand : IComponentData
    3. {
    4.     public int PlayerNumber;            // The player ID
    5.     public int UnitCount;                   // Number of units to send to target
    6.     public int OriginPlanetNumber;  // The planet ID of origin planet
    7.     public Entity OriginPlanet;
    8.     public Entity TargetPlanet;
    9. }
    10.  
    Then I have a system that gets the entity with a SendUnitsToTargetCommand component attached to it.
    The system then queries and iterates through all units that:
    a) Have a SharedComponent "PlayerNumber" with the same value as the command
    b) Have a SharedComponent "PlanetNumber" with the same value as the command
    c) Have a Component "UnitOrbitalMovementData" <- wich means that these units are "idling" on a planet and don't move towards a target already

    So this is what I came up with:

    Code (CSharp):
    1.  
    2. NativeArray<SendUnitsToTargetCommand> SendUnitsCommands = q_sendCommand.ToComponentDataArray<SendUnitsToTargetCommand>(Allocator.Temp);
    3.  
    4. EntityQuery q = state.EntityManager.CreateEntityQuery(ComponentType.ReadOnly<UnitOrbitalMovementData>(), ComponentType.ReadOnly<PlayerNumber>(), ComponentType.ReadOnly<PlanetNumber>());
    5.             q.AddSharedComponentFilter(new PlayerNumber { Value = SendUnitsCommands[0].PlayerNumber });
    6.             q.AddSharedComponentFilter(new PlanetNumber { Value = SendUnitsCommands[0].OriginPlanetNumber });
    So far so good. Now I have a query with all the units that now need to have some components changed.
    Units can be in two states:
    a) Idling on a planet
    b) Moving towards a target planet

    All units with state a) are parented to the planet, have a PlanetNumber and a UnitOrbitalMovementData component.
    All units with state b) are not parented to anything, have a TargetMovementData component.

    So I write an IJobEntity to change the components like this:

    Code (CSharp):
    1.  
    2. public partial struct ProcessSendUnitJob : IJobEntity
    3. {
    4.     public EntityCommandBuffer.ParallelWriter Ecb;
    5.     public int UnitCount;
    6.     public Entity TargetPlanet;
    7.     public RefRO<WorldTransform> TargetWorldTrans; // <- I want to pass the WorldTrans as reference :( don't know how
    8.  
    9.     private void Execute(Entity e, [EntityIndexInQuery] int entityIndex , in UnitOrbitalMovementData unitOrbitalMovementData, in LocalTransform transform, in WorldTransform unitWorldTransform, in PlanetNumber planetID)
    10.     {
    11.         if (entityIndex < UnitCount)
    12.         {
    13.             // remove components that make a unit idling on a planet
    14.             Ecb.RemoveComponent<UnitOrbitalMovementData>(entityIndex, e);
    15.             Ecb.RemoveComponent<PlanetNumber>(entityIndex, e);
    16.  
    17.             // get current position in worldspace
    18.             float3 worldpos = unitWorldTransform.Position;
    19.    
    20.             // unparent unit from planet
    21.             Ecb.RemoveComponent<Parent>(entityIndex, e);
    22.  
    23.             // add components that make a unit move to a target
    24.             Ecb.AddComponent(entityIndex, e, new TargetMovementData { targetEntity = TargetPlanet, worldTransform = TargetWorldTrans });
    25.             Ecb.SetComponent(entityIndex, e, new LocalTransform { Position = worldpos, Scale = 1 });
    26.         }
    27.     }
    28. }
    29.  
    30. // Units have this component attached, if they are moving towards a target
    31. public struct TargetMovementData : IComponentData
    32. {
    33.     public Entity targetEntity;
    34.     public RefRO<WorldTransform> worldTransform;
    35. }
    36.  

    So my question is... how do I pass the WorldTransform of the targetPlanet to the jobs?
    This is what my final OnUpdate looks like:

    Code (CSharp):
    1.  
    2.  
    3. public void OnUpdate(ref SystemState state)
    4.     {
    5.         EntityQuery q_sendCommand = new EntityQueryBuilder(Allocator.Temp)
    6.         .WithAll<SendUnitsToTargetCommand>()
    7.         .Build(ref state);
    8.  
    9.         NativeArray<SendUnitsToTargetCommand> SendUnitsCommands = q_sendCommand.ToComponentDataArray<SendUnitsToTargetCommand>(Allocator.Temp);
    10.         if (SendUnitsCommands.Length > 0)
    11.         {
    12.             EntityCommandBuffer.ParallelWriter ecb = GetEntityCommandBuffer(ref state);
    13.      
    14.             EntityQuery q = state.EntityManager.CreateEntityQuery(ComponentType.ReadOnly<UnitOrbitalMovementData>(), ComponentType.ReadOnly<PlayerNumber>(), ComponentType.ReadOnly<PlanetNumber>());
    15.             q.AddSharedComponentFilter(new PlayerNumber { Value = SendUnitsCommands[0].PlayerNumber });
    16.             q.AddSharedComponentFilter(new PlanetNumber { Value = SendUnitsCommands[0].OriginPlanetNumber });
    17.  
    18.             // HOW TO??? pass this WorldTransform by reference...
    19.             RefRO<WorldTransform> targetWorldTransform = SystemAPI.GetComponent<WorldTransform>(SendUnitsCommands[0].TargetPlanet);
    20.             // this throws an error...
    21.  
    22.             new ProcessSendUnitJob
    23.             {
    24.                 Ecb = ecb,
    25.                 UnitCount = SendUnitsCommands[0].UnitCount,
    26.                 TargetPlanet = SendUnitsCommands[0].TargetPlanet,
    27.                 TargetWorldTrans = targetWorldTransform
    28.            }.ScheduleParallel(q);
    29.  
    30.             // Destroy command entity afterwards
    31.             state.EntityManager.DestroyEntity(SendUnitsCommands[0].self);
    32.         }
    33.  
    34.         SendUnitsCommands.Dispose();
    35.     }
    36.  
    37.  
    38.  
     
    Last edited: Feb 20, 2023
  4. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    761
    Is there a reason why it has to be a reference? Since it is only a single call, there are no performance problems here, and the value would never change while the job is running, so a copy would not be a problem.
     
    friesenmedien likes this.
  5. friesenmedien

    friesenmedien

    Joined:
    Mar 15, 2021
    Posts:
    7

    yeah but planets do move. So I need a reference stored, not just the value of the planet position at the time of creating the command, right?
    Cause the SendUnitsToTarget system gets called only if the player creates a command entity.
    Then an other system moves the unit towards the target on update.
    So the units need to have a reference to the target stored somewhere in a component, so I want my units to have the following component attached...

    Code (CSharp):
    1. public struct TargetMovementData : IComponentData
    2. {
    3.     // Store BOTH the target entity and WorldTransform of it in this component
    4.     public Entity targetEntity;
    5.     public RefRO<WorldTransform> worldTransform;
    6. }
    7.  
    That's because I read that I should not use

    SystemAPI.GetComponent<WorldTransform>(targetEntity);

    inside a Job, but pass the component before executing the job... but how? :D
     
    Last edited: Feb 20, 2023
  6. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    761
    That would be a bad idea as the component might move and the ref would still point to the old memory. Rather use only the entity and fetch the components when you need them.
    I also wonder if it wouldn't be better if Unity made ref structs out of the RefRW/RefRO/Aspects.

    Edit:
    You could also make a system that updates the transform copy.
     
    friesenmedien likes this.
  7. friesenmedien

    friesenmedien

    Joined:
    Mar 15, 2021
    Posts:
    7
    Crap, I thought so already...
    So I have to call GetComponent inside every MoveUnitToTarget Job, to get the actual value of the target planet WorldTransform :(

    Or as you said query for all units having the "TargetMovementData" component and update the WorldTransform position of all components, before moving all units in a second system.

    So my TargetMovementData looks like this

    Code (CSharp):
    1. public struct TargetMovementData : IComponentData
    2. {
    3.     // Store BOTH the target entity and WorldTransform of it in this component
    4.     public Entity targetEntity;
    5.     public float3 currentTargetWorldPosition; // <- this has to be updated before moving the units
    6. }