Search Unity

InvalidOperationException: The writable NativeArray CollisionJob.ShotsA is the same NativeArray as C

Discussion in 'Entity Component System' started by Arowx, Jun 15, 2018.

  1. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    InvalidOperationException: The writable NativeArray CollisionJob.ShotsA is the same NativeArray as CollisionJob.ShotsB, two NativeArrays may not be the same (aliasing).

    This seems wrong to me as in this case there are enemy and player shots and I want to scan for one hitting the other...

    Especially as I am inputting the data to the two separate arrays using this code:

    Code (CSharp):
    1. protected override JobHandle OnUpdate(JobHandle inputDeps)
    2.         {
    3.             var settings = SpaceInvadersBootstrap.Settings;
    4.  
    5.             if (settings == null)
    6.                 return inputDeps;
    7.  
    8.             var playerShotsVsEnemyShots = new CollisionJob
    9.             {
    10.                 ShotPositions = m_PlayerShots.Position,
    11.                 ShotsA = m_PlayerShots.Shot, // players shots
    12.                 CollisionRadiusSquared = settings.playerCollisionRadius * settings.playerCollisionRadius,
    13.                 ShotsB = m_EnemyShots.Shot, // enemies shots
    14.                 Positions = m_EnemyShots.Position,
    15.             }.Schedule(m_EnemyShots.Length, 1, inputDeps);
    16.  
    17.             return playerShotsVsEnemyShots;
    18.         }
    In this case the players shots would not hit other players shots and ditto for the enemy shots not hitting each other, also this massively reduces the number of calculations needed.

    How would you get around this problem?
     
  2. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I do not know why you get this error. Could you post the whole system? Do you use readonly / disable parallelforwriting attributes properly?
     
  3. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    It's adapted from the TwinStickShooter example ShotDamageSystem.cs

    Code (CSharp):
    1. class ShotVsShotSystem : JobComponentSystem
    2.     {
    3.  
    4.         /// <summary>
    5.         /// All player shots.
    6.         /// </summary>
    7.         struct PlayerShotData
    8.         {
    9.             public int Length;
    10.             public ComponentDataArray<Shot> Shot;
    11.             [ReadOnly] public ComponentDataArray<Position> Position;
    12.             [ReadOnly] public ComponentDataArray<PlayerShot> PlayerShotMarker;
    13.         }
    14.         [Inject] PlayerShotData m_PlayerShots;
    15.  
    16.         /// <summary>
    17.         /// All enemy shots.
    18.         /// </summary>
    19.         struct EnemyShotData
    20.         {
    21.             public int Length;
    22.             public ComponentDataArray<Shot> Shot;
    23.             [ReadOnly] public ComponentDataArray<Position> Position;
    24.             [ReadOnly] public ComponentDataArray<EnemyShot> EnemyShotMarker;
    25.         }
    26.         [Inject] EnemyShotData m_EnemyShots;
    27.  
    28.         [ComputeJobOptimization]
    29.         struct CollisionJob : IJobParallelFor
    30.         {
    31.             public float CollisionRadiusSquared;
    32.  
    33.             [NativeDisableParallelForRestriction]
    34.             public ComponentDataArray<Shot> ShotsA;
    35.             [ReadOnly] public ComponentDataArray<Position> Positions;
    36.  
    37.             [NativeDisableParallelForRestriction]
    38.             public ComponentDataArray<Shot> ShotsB;
    39.  
    40.             [NativeDisableParallelForRestriction]
    41.             [ReadOnly] public ComponentDataArray<Position> ShotPositions;
    42.  
    43.             public void Execute(int index)
    44.             {
    45.                 float damage = 0.0f;
    46.  
    47.                 float3 receiverPos = Positions[index].Value;
    48.                 float3 shotPos;
    49.                 float3 delta;
    50.                 float distSquared;
    51.                 Shot shot;
    52.  
    53.                
    54.                 for (int si = 0; si < ShotsB.Length; ++si)
    55.                 {
    56.                     shotPos = ShotPositions[si].Value;
    57.                     delta = shotPos - receiverPos;
    58.                     distSquared = math.dot(delta, delta);
    59.  
    60.                     if (distSquared <= CollisionRadiusSquared)
    61.                     {
    62.                         shot = ShotsB[si];
    63.  
    64.                         damage += shot.Energy;
    65.  
    66.                         // Set the shot's time to live to zero, so it will be collected by the shot destroy system
    67.                         shot.TimeToLive = 0.0f;
    68.  
    69.                         ShotsB[si] = shot;
    70.                     }
    71.  
    72.                 }
    73.                
    74.  
    75.                 if (damage > 0f)
    76.                 {
    77.                     Shot shotA = ShotsA[index];
    78.                     shotA.TimeToLive = 0.0f;
    79.                     ShotsA[index] = shotA;
    80.                 }
    81.             }
    82.         }
    83.  
    84.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    85.         {
    86.             var settings = SpaceInvadersBootstrap.Settings;
    87.  
    88.             if (settings == null)
    89.                 return inputDeps;
    90.  
    91.             var playerShotsVsEnemyShots = new CollisionJob
    92.             {
    93.                 ShotPositions = m_PlayerShots.Position,
    94.                 ShotsA = m_PlayerShots.Shot,
    95.                 CollisionRadiusSquared = settings.playerCollisionRadius * settings.playerCollisionRadius,
    96.                 //ShotsB = m_EnemyShots.Shot,
    97.                 Positions = m_EnemyShots.Position,
    98.             }.Schedule(m_EnemyShots.Length, 1, inputDeps);
    99.  
    100.             return playerShotsVsEnemyShots;
    101.         }
    102.     }
    Initially I tried adding a shot vs shot job on the ShotDamageSystem but found as it needs entities with Health it could not be adapted to shot vs shots as they use TimeToLive.
     
  4. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    To get around this problem, use
    [Inject] ComponentDataFromEntity<Shot> cdfeShot;
    and replace both
    public ComponentDataArray<Shot> Shot;
    in both injection groups with
    EntityArray
    . Those two CDA aliased with each other, and also FYI alias with
    ComponentDataFromEntity<Shot>
    but this time we will only have the CDFE one in the job to eliminate the potential aliasing.

    Then you send both EntityArray as a representative of ShotsA and ShotsB instead, in the job use those entities with the CDFE to get the real thing.
     
    The5 likes this.
  5. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Apologies I don't quite understand that it seems like your saying put all shots together then the job systems chunking approach with magically divide them into enemy and player shots or I will have to work out which shot belongs to which system in the job?
     
  6. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    The injected struct would become like this

    Code (CSharp):
    1.         struct PlayerShotData
    2.         {
    3.             public int Length;
    4.             public EntityArray Shot;
    5.             [ReadOnly] public ComponentDataArray<Position> Position;
    6.             [ReadOnly] public ComponentDataArray<PlayerShot> PlayerShotMarker;
    7.         }
    8.         [Inject] PlayerShotData m_PlayerShots;
    9.         /// <summary>
    10.         /// All enemy shots.
    11.         /// </summary>
    12.         struct EnemyShotData
    13.         {
    14.             public int Length;
    15.             public EntityArray Shot;
    16.             [ReadOnly] public ComponentDataArray<Position> Position;
    17.             [ReadOnly] public ComponentDataArray<EnemyShot> EnemyShotMarker;
    18.         }
    19.         [Inject] EnemyShotData m_EnemyShots;
    Each `EntityArray` will be the same ones that contains the `Shot` CDA, we will get that later.

    Then the declaration part of the job would be like this. Put in the correct EntityArray from outside the job.

    Code (CSharp):
    1.  
    2.             [ReadOnly] public EntityArray ShotsA;
    3.             [ReadOnly] public EntityArray ShotsB;
    4.            [NativeDisableParallelForRestriction]
    5.             public ComponentDataFromEntity<Shot> cdfeShot;
    To get CDFE use `GetComponentDataFromEntity<>` class method of JobComponentSystem or use [Inject].

    Now we don't have aliasing problem, as the memory area containing Shot data is only cdfeShot (and not ShotA and ShotB)

    Then in place like line 62 it would be
    shot = ShotsB[si];
    ->
    shot = cdfe[ShotsB[si]]
    . You use Entity (from both A and B) to get the actual Shot from one central data source. (CDFE) In place like line 69, you can also save back despite the name says ComponentData"From"Entity. (
    cdfe[ShotsB[si]] = shot
    )

    I think you also need to remove from injection and provide CDFE for Position as well and use EntityArray to access it in the same manner for the split group-same job approach to work without aliasing. For PlayerShot and EnemyShot there should be no problem.
     
    Last edited: Jun 17, 2018
    The5 likes this.
  7. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I would possibly have a tag for “destroy shot”, which needs to be added from the collision system(s) and it seems you have a timer system for the shots, too, which would set it on expiration.

    You would inject the two separate read only arrays as 5argon mentioned. I have not tried this myself, but as I understand it, you can with the latest ecs version write post update / barrier commands to a buffer from a parallel job. This is where you would add the destroy tag, to be picked up by your clearance system later. Otherwise you could just store them in a results array (I would use one for each type, you know the length before the job)

    In general though, as you have deterministic behavior, you could also run it in a normal job, as you can immediately remove colliding entities from further checks as no two shots can be in the same position. As long as the collision is simple Luke radius it does not really matter I guess.
     
  8. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Tried this and no compile errors on this code but this error pops up?

    No idea how changing one system can create an error in another that was not there before?
     
  9. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Try turning off Burst and get the error again? It might be more descriptive. But what I guess is if you turn off Burst it will tell you about some variable you might forgot to hook up before scheduling a job. (all containers must be available)
     
  10. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    That error goes but as soon as the player shoots this new error appears, it's like the new Job is messing up the data for the old job?

    Turning off the Burst compiler defeats the purpose and only delays when the error appears.

    I'm amazed scanning two sets of the same type of data is such an issue with the ECS system.
     
  11. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    It is not, I literally did the same in a collision test a few weeks ago. Maybe there is a bug in the ecs version you use or something is wrong with the code. Can you try to isolate and only have a system that moves player shots and enemy shots say on out of phase sin waves and then add your collision system?
     
  12. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    I mean, turning off Burst just temporarily to see the new error message. Because your current message error at Burst compile time and there are a lot of `at Burst.Compiler.IL.` we don't really know what is the real problem or which line is the problem. The error that appears when the player shoot without Burst should be different from what you posted and be more descriptive.
     
  13. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    The new error is at least on the ShotVsShotSystem.cs code line 72

    Code (CSharp):
    1.         [ComputeJobOptimization]
    2.         struct CollisionJob : IJobParallelFor
    3.         {
    4.             public float CollisionRadiusSquared;
    5.                        
    6.             [ReadOnly] public ComponentDataArray<Position> Positions;
    7.  
    8.             [ReadOnly]public EntityArray ShotsA;
    9.             [ReadOnly]public EntityArray ShotsB;
    10.  
    11.             [NativeDisableParallelForRestriction]
    12.             public ComponentDataFromEntity<Shot> cdfeShot;
    13.  
    14.             [NativeDisableParallelForRestriction]
    15.             [ReadOnly] public ComponentDataArray<Position> ShotPositions;          
    16.  
    17.             public void Execute(int index)
    18.             {
    19.                 float damage = 0.0f;
    20.  
    21.                 float3 receiverPos = Positions[index].Value;
    22.                 float3 shotPos;
    23.                 float3 delta;
    24.                 float distSquared;
    25.                 Shot shot;
    26.                
    27.                 for (int si = 0; si < ShotsB.Length; ++si)
    28.                 {
    29.                     shotPos = ShotPositions[si].Value;  // Line 72 Error Here!!!
    30.                     delta = shotPos - receiverPos;
    31.                     distSquared = math.dot(delta, delta);
    32.  
    33.                     if (distSquared <= CollisionRadiusSquared)
    34.                     {
    35.                         shot = cdfeShot[ShotsB[si]];
    36.  
    37.                         damage += shot.Energy;
    38.  
    39.                         // Set the shot's time to live to zero, so it will be collected by the shot destroy system
    40.                         shot.TimeToLive = 0.0f;
    41.  
    42.                         cdfeShot[ShotsB[si]] = shot;
    43.                     }
    44.                 }              
    45.  
    46.                 if (damage > 0f)
    47.                 {
    48.                     Shot shotA = cdfeShot[ShotsA[index]];
    49.                     shotA.TimeToLive = 0.0f;
    50.                     cdfeShot[ShotsA[index]] = shotA;
    51.                 }
    52.             }
    53.         }
     
  14. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Well you need to see what you setup the ShotPositions with before beginning a job. Likely that it is not from the same entity as ShotsB which you are looping the i index with so the Length does not match up.
     
  15. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Doesn't ECS have a way to combine archetypes/data patterns to help solve this problem and without having developers make lost of different collections of data types for each problem domain?
     
  16. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Solved it by using the ShotDamageSystem as an example I regrouped the work into two jobs one tests for invaderShots vs PlayerShots and the other does the opposite. This way I can just pass in the positions of one set of shots as data and the actual shot data of the other set.

    Code (CSharp):
    1. using System.ComponentModel.Design;
    2. using System.Runtime.CompilerServices;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8.  
    9. namespace ECSSpaceInvaders
    10. {
    11.  
    12.     /// <summary>
    13.     /// Assigns out damage from shots colliding with entities of other factions.
    14.     /// </summary>
    15.     class ShotVsShotSystem : JobComponentSystem
    16.     {
    17.  
    18.         /// <summary>
    19.         /// All player shots.
    20.         /// </summary>
    21.         struct PlayerShotData
    22.         {
    23.             public int Length;
    24.             public ComponentDataArray<Shot> PlayerShot;
    25.             [ReadOnly] public ComponentDataArray<Position> Position;
    26.             [ReadOnly] public ComponentDataArray<PlayerShot> PlayerShotMarker;          
    27.         }
    28.         [Inject] PlayerShotData m_PlayerShots;
    29.  
    30.         struct PlayerShotPositionData
    31.         {
    32.             public int Length;
    33.             [ReadOnly] public ComponentDataArray<Position> Position;
    34.             [ReadOnly] public ComponentDataArray<PlayerShot> PlayerShotMarker;
    35.         }
    36.         [Inject] PlayerShotPositionData m_PlayerShotPositions;
    37.  
    38.         /// <summary>
    39.         /// All enemy shots.
    40.         /// </summary>
    41.         struct InvaderShotData
    42.         {
    43.             public int Length;
    44.             public ComponentDataArray<Shot> InvaderShot;
    45.             [ReadOnly] public ComponentDataArray<Position> Position;
    46.             public ComponentDataArray<EnemyShot> EnemyShotMarker;
    47.         }
    48.         [Inject] InvaderShotData m_InvaderShots;
    49.  
    50.         struct InvaderShotPositionData
    51.         {
    52.             public int Length;
    53.             [ReadOnly] public ComponentDataArray<Position> Position;
    54.             [ReadOnly] public ComponentDataArray<EnemyShot> EnemyShotMarker;
    55.         }
    56.         [Inject] InvaderShotPositionData m_InvaderShotPositions;
    57.  
    58.  
    59.  
    60.         [ComputeJobOptimization]
    61.         struct CollisionJob : IJobParallelFor
    62.         {
    63.             public float CollisionRadiusSquared;                      
    64.  
    65.             [ReadOnly]public ComponentDataArray<Position> Position;
    66.  
    67.  
    68.             [ReadOnly]public ComponentDataArray<Position> ShotsPosition;
    69.  
    70.             [NativeDisableParallelForRestriction]
    71.             public ComponentDataArray<Shot> Shots;
    72.  
    73.             public void Execute(int index)
    74.             {              
    75.                 float3 receiverPos = Position[index].Value;
    76.                 float3 shotPos;
    77.                 float3 delta;
    78.                 float distSquared;
    79.                 Shot shot;
    80.  
    81.                 if (Shots.Length > 0)
    82.                 {
    83.                     for (int si = 0; si < Shots.Length; ++si)
    84.                     {
    85.                         shotPos = ShotsPosition[si].Value;
    86.                         delta = shotPos - receiverPos;
    87.                         distSquared = math.dot(delta, delta);
    88.  
    89.                         if (distSquared <= CollisionRadiusSquared)
    90.                         {
    91.                             shot = Shots[si];                    
    92.  
    93.                             // Set the shot's time to live to zero, so it will be collected by the shot destroy system
    94.                             shot.TimeToLive = 0.0f;
    95.  
    96.                             Shots[si] = shot;
    97.                         }
    98.                     }
    99.                 }
    100.             }
    101.         }
    102.  
    103.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    104.         {
    105.             var settings = SpaceInvadersBootstrap.Settings;
    106.  
    107.             if (settings == null)
    108.                 return inputDeps;
    109.  
    110.             if (m_PlayerShots.Length > 0)
    111.             {
    112.  
    113.                 var playerShotsVsEnemyShots = new CollisionJob
    114.                 {                                      
    115.                     Position = m_PlayerShotPositions.Position,
    116.                     CollisionRadiusSquared = settings.playerCollisionRadius * settings.playerCollisionRadius,
    117.                     Shots = m_InvaderShots.InvaderShot,
    118.                     ShotsPosition = m_InvaderShots.Position,
    119.                 }.Schedule(m_PlayerShots.Length, 1, inputDeps);
    120.            
    121.                 var EnemyShotsVsPlayerShots = new CollisionJob
    122.                 {
    123.                     Position = m_InvaderShotPositions.Position,
    124.                     CollisionRadiusSquared = settings.playerCollisionRadius * settings.playerCollisionRadius,
    125.                     Shots = m_PlayerShots.PlayerShot,
    126.                     ShotsPosition = m_PlayerShots.Position,
    127.                 }.Schedule(m_InvaderShots.Length, 1, playerShotsVsEnemyShots);
    128.  
    129.  
    130.                 return EnemyShotsVsPlayerShots;
    131.             }
    132.  
    133.             return inputDeps;
    134.         }
    135.     }
    136. }
    137.  
    138.  
    Still seems silly that the ECS system gets in a mess when two different sets of data are passed into it as seperate arrays but their formats match (even though both have different filtering IComponentData flags.

    As now I have to test twice as much just to update two sets of shots.
     
  17. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    @5argon and @sngdan I was watching the ECS Q&A and it looks like strings and arrays are unsupported ECS features at the moment.
     
  18. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    For the time being, but that will change "soon".
     
  19. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Yes, but they are coming - mentioned in a talk at Unite Berlin (link somewhere here).

    Is this related to the problem you were having?