Search Unity

List returns null in Job after 203+ entities

Discussion in 'Entity Component System' started by MadboyJames, Aug 17, 2019.

  1. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    I have a strange problem. (See "The Problem" for the problem). I am trying to Jobify the instantiation of projectiles. My current process is to use jobs to get the position and rotation of the to-be-instantiated projectiles and put them in a struct. Then that struct is sorted into projectile type (such as rocket, bullet, etc), and added to a list containing only that type. For each list of type, the total number of entities are instantiated using NativeArray. Finally each entity in the array is placed at it's prospective spawn position and rotation. This works for a handful of entities (202 entities, exactly)

    The Problem: When I have more than 202 guns wanting to instantiate a projectile, the list that the jobs are adding the projectile data structs to returns null.

    Here is the code (un-comment out the big block in SpawnShotSystem, 25 to 52, if you just want to use this on the main thread, where it works for any amount of entities. It's just is not jobbified yet. And if so, don't use the job system script. My computer konks out at 3000 entities instantiating in one frame while working on the main thread, dropping to 5 fps).

    Since the code on the main thread is functional, and needed quite a bit of help to get this far (meaning I didn't find someone else who did this before me), I'll post the whole code to make this work.

    First, the spawnshot Component
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Mathematics;
    3.  
    4.  
    5. public struct SpawnShotComponent : IComponentData
    6. {
    7.     public float rateOfFire;
    8.     public float3 forwardDirection;
    9.     public float timeSinceLastShot;
    10.     public Entity bulletPrefab;
    11.     public int id;
    12. }
    Next the Spawnshot Component Proxy, because I use gameObjects that are converted into entities.
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5.  
    6. [RequiresEntityConversion]
    7. public class SpawnShotComponetProxy : MonoBehaviour, IDeclareReferencedPrefabs,IConvertGameObjectToEntity
    8. {
    9.     public float rateOfFire;
    10.     public float3 forwardDirection;
    11.     public GameObject projectile;
    12.     public int id;
    13.  
    14.     public void DeclareReferencedPrefabs(List<GameObject> gameObjects)
    15.     {
    16.         gameObjects.Add(projectile);
    17.     }
    18.  
    19.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    20.     {
    21.         var data = new SpawnShotComponent {
    22.             rateOfFire = rateOfFire,
    23.             forwardDirection=forwardDirection,
    24.             timeSinceLastShot = Time.time,
    25.             bulletPrefab = conversionSystem.GetPrimaryEntity(projectile),
    26.             id=id
    27.         };
    28.    
    29.         dstManager.AddComponentData(entity, data);
    30.     }
    31. }
    Then, the code that actually instantiates the projectiles
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Entities;
    3. using Unity.Transforms;
    4. using Unity.Physics;
    5. using Unity.Mathematics;
    6. using Unity.Collections;
    7.  
    8. [UpdateBefore(typeof( GetShotDataSystem))]
    9. public class SpawnShotSystem : ComponentSystem
    10. {
    11.     public static Dictionary<int, int> shotTypeIndex;//used to check what list contains what
    12.  
    13.     public static List<List<SpawnShotData>> spawnshotList;//the list of lists with organized spawnshot data
    14.  
    15.  
    16.  
    17.     protected override void OnCreate()
    18.     {
    19.         base.OnCreate();
    20.         shotTypeIndex = new Dictionary<int, int>();
    21.         spawnshotList = new List<List<SpawnShotData>>();
    22.     }
    23.     protected override void OnUpdate()
    24.     {
    25.         //float currentTime = Time.fixedTime;
    26.         //Entities.ForEach((ref SpawnShotComponent spawnShot, ref LocalToWorld localToWorld) =>
    27.         //     {
    28.         //         if (spawnShot.timeSinceLastShot < currentTime)
    29.         //         {
    30.  
    31.         //             spawnShot.timeSinceLastShot = currentTime + (1 / spawnShot.rateOfFire);
    32.                  
    33.         //             SpawnShotData data = new SpawnShotData
    34.         //             {
    35.         //                 bulletPrefab = spawnShot.bulletPrefab,
    36.         //                 position = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z),
    37.         //                 rotation = localToWorld.Rotation(),
    38.         //                 heading = math.mul(localToWorld.Rotation(), spawnShot.forwardDirection),
    39.         //                 id = spawnShot.id
    40.         //             };
    41.                  
    42.         //             if (shotTypeIndex.ContainsKey(data.id))
    43.         //             {
    44.         //                 spawnshotList[shotTypeIndex[data.id]].Add(data);
    45.         //             }
    46.         //             else
    47.         //             {
    48.         //                 shotTypeIndex.Add(data.id, shotTypeIndex.Count);
    49.         //                 spawnshotList.Add(new List<SpawnShotData> { data });
    50.         //             }
    51.         //         }
    52.         //     });
    53.         SpawnProjectile();
    54.     }
    55.  
    56.     private void SpawnProjectile()
    57.     {
    58.         int length = spawnshotList.Count;
    59.  
    60.         for (int i = 0; i < length; i++)
    61.         {
    62.  
    63.             int length2 = spawnshotList[i].Count;
    64.             if (length2 > 0)
    65.             {
    66.                 NativeArray<Entity> newProjectiles = new NativeArray<Entity>(length2, Allocator.Temp);
    67.                 EntityManager.Instantiate(spawnshotList[i][0].bulletPrefab, newProjectiles);
    68.  
    69.                 for (int j = 0; j < length2; j++)
    70.                 {
    71.                     SpawnShotData data = spawnshotList[i][j];
    72.                     EntityManager.SetComponentData(newProjectiles[j], new Translation { Value = data.position });
    73.                     EntityManager.SetComponentData(newProjectiles[j], new Rotation { Value = data.rotation });
    74.                     EntityManager.SetComponentData(newProjectiles[j], new PhysicsVelocity { Linear = data.heading });
    75.  
    76.                 }
    77.                 newProjectiles.Dispose();
    78.                 spawnshotList[i].Clear();
    79.             }
    80.         }
    81.  
    82.  
    83.  
    84.  
    85.  
    86.     }
    87.     public struct SpawnShotData //the struct with bullet data
    88.     {
    89.         public Entity bulletPrefab;
    90.         public float3 position;
    91.         public quaternion rotation;
    92.         public float3 heading;
    93.         public int id;
    94.     }
    95.  
    96. }
    Finally, the code (Jobsystem) that gets the data for each shot and organizes it by id.
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Burst;
    6. using Unity.Transforms;
    7. using Unity.Mathematics;
    8. using static SpawnShotSystem;
    9.  
    10.  
    11. public class GetShotDataSystem : JobComponentSystem
    12. {
    13.  
    14.  
    15.     [BurstCompile]
    16.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    17.     {
    18.         GetShotDataJob getDataJob = new GetShotDataJob
    19.         {
    20.             currentTime = Time.time
    21.         };
    22.  
    23.         return getDataJob.Schedule(this, inputDeps);
    24.     }
    25.  
    26.  
    27.     public struct GetShotDataJob : IJobForEach<LocalToWorld, SpawnShotComponent>
    28.     {
    29.         public float currentTime;
    30.         public void Execute(ref LocalToWorld localToWorld, ref SpawnShotComponent spawnShot)
    31.         {
    32.             if (spawnShot.timeSinceLastShot < currentTime)
    33.             {
    34.            
    35.                 spawnShot.timeSinceLastShot = currentTime + (1 / spawnShot.rateOfFire);
    36.  
    37.                 SpawnShotData data = new SpawnShotData
    38.                 {
    39.                     bulletPrefab = spawnShot.bulletPrefab,
    40.                     position = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z),
    41.                     rotation = localToWorld.Rotation(),
    42.                     heading = math.mul(localToWorld.Rotation(), spawnShot.forwardDirection),
    43.                     id = spawnShot.id
    44.                 };
    45.  
    46.                 if (shotTypeIndex.ContainsKey(data.id))
    47.                 {
    48.                
    49.  
    50.                     spawnshotList[shotTypeIndex[data.id]].Add(data);
    51.                 }
    52.                 else
    53.                 {
    54.                     shotTypeIndex.Add(data.id, shotTypeIndex.Count);
    55.                     spawnshotList.Add(new List<SpawnShotData> { data });
    56.                 }
    57.             }
    58.         }
    59.     }
    60. }
    Post-Finally, An extensions class with a few methods to make the LocalToWorld values work (which I gained on this forum by Tertle, a user I am extremely grateful towards).
    Code (CSharp):
    1. using Unity.Mathematics;
    2. using Unity.Transforms;
    3. using UnityEngine.Assertions;
    4.  
    5. public static class ExtensionMethods
    6. {
    7.     public static float3 GetScale(this float4x4 matrix) => new float3(
    8.            math.length(matrix.c0.xyz),
    9.            math.length(matrix.c1.xyz),
    10.            math.length(matrix.c2.xyz));
    11.  
    12.     public static float4x4 ScaleBy(this float4x4 matrix, float3 scale) => math.mul(matrix, float4x4.Scale(scale));
    13.  
    14.     public static float3 Invert(this float3 f)
    15.     {
    16.         Assert.IsFalse(math.any(f == float3.zero));
    17.  
    18.         return new float3(1 / f.x, 1 / f.y, 1 / f.z);
    19.     }
    20.     public static quaternion Rotation(this LocalToWorld localToWorld)
    21.     {
    22.         var scale = localToWorld.Value.GetScale();
    23.         var inverted = scale.Invert();
    24.         var value = localToWorld.Value.ScaleBy(inverted); // remove the scale
    25.  
    26.         return new quaternion(value);
    27.     }
    28.  
    29. }
    Anyone know why spawnshotList returns null when over 202 entities? I'm running on an Intel Core i7-6700HQ @2.60 GHz. Any input would be appreciated. I'm rather stumped.
     
    Last edited: Aug 17, 2019
  2. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    There are quite a few issues here but here are couple of the main ones:

    Code (CSharp):
    1. [BurstCompile]
    2. protected override JobHandle OnUpdate(JobHandle inputDeps)
    This does nothing, [BurstCompile] goes on job structs.

    Code (CSharp):
    1. if (shotTypeIndex.ContainsKey(data.id))
    2. {
    3.     spawnshotList[shotTypeIndex[data.id]].Add(data);
    4. }
    5. else
    6. {
    7.     shotTypeIndex.Add(data.id, shotTypeIndex.Count);
    8.     spawnshotList.Add(new List<SpawnShotData> { data });
    9. }
    You can't do this. You have to use NativeCollections(NativeList, NativeHashMap, etc), the exception is if you create the collection in the job itself, or some read-only cases, there is a reason you can't pass reference types to a job. Since shotTypeIndex and spawnshotList are static, not thread safe, and in another system, any thread in any system can mutate it at any time, this will corrupt the data eventually. C# collections are generally not thread safe(unless Concurrent) and none are guaranteed to work in JobSystem.

    I'll probably post some changes to improve this tomorrow, but I haven't written a system in a while and am too tired to catch up atm.
     
    Last edited: Aug 17, 2019
  3. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Huh, okay. Thank you! I thought the Burstcompile could go at the start of a class. And the list referencing I implemented I thought might be hacky, but I couldn't get anything else to work. Granted I couldn't find much reference material, so I have not tried many different things.

    I should also note that the fundamental design of my process is also in question. If there is a better way to instantiate a whole bunch of entities at different locations, then I can assure you I picked this way out of inexperience and ignorance, rather than conscious decision, and would appreciate being pointed in the right direction.

    Update: After implementing the stated changes (changing the list and dictionary to Native list and Native Hashmap), I now get a "thread unsafe" error, regardless of the amount of entities trying to spawn entities. Which seems more like what is supposed to happen, yes? I'm still working on getting it to function.
     
    Last edited: Aug 17, 2019
  4. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Code (CSharp):
    1. I thought the Burstcompile could go at the start of a class.
    It would go on any job struct you create in your system(eg IJobForEach, IJobChunk, IJobParallelFor, etc):

    Code (CSharp):
    1.  
    2. [BurstCompile]
    3. public struct GetShotDataJob : IJobForEach<LocalToWorld, SpawnShotComponent>
    I should mention that NativeCollections have their own restrictions as well.
    • Only blittable types are allowed, NativeCollections themselves are not usually blittable, so no nesting arrays.
    • If you're writing to a collection in a job, you have to use a .Concurrent version, or have a NativeArray equal to the length of items being processed(you'd use the job index argument of Execute() to access the array). If you are writing to a NativeArray in a job, you need [NativeDisableParallelForRestriction], or if you're just reading you'd use [ReadOnly]. NativeList/Array don't have concurrent version.
    • You must .Dispose of collections, either in OnUpdate() after calling complete on the last job that used it(not reccomended), or use [DeallocateOnJobCompletion] attribute on the container passed to the struct. Some collections cannot use this attribute.
     
    Last edited: Aug 17, 2019
  5. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    okay, so if I'm hearing you right, I can't use
    public static NativeList<NativeList<SpawnShotData>> spawnshotList;
    . Additionally it looks like native arrays now have .AsParallelWriter() which functions like concurrent. It does seem I can't .Add() to a nested native list. So I'm currently working around that.
     
  6. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Right, you might use NativeMultiHashMap instead, it works like lists with keys and can be Concurrent.

    I can't say anything about this as I'm still on an old package. But you most likely could just have SpawnShotSystem by itself, you can go through all entities with SpawnShotComponent and spawn projectiles in a job. You should look into EntityCommandBuffer.Concurrent and PostUpdateCommands, it's not recommended to create entities with EntityManager directly in a system's OnUpdate().
     
    Last edited: Aug 18, 2019
  7. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Okay. I'm rather new to all this, and part of my issue is I can't find much material. Could you point me in a direction to learn about EntityCommandBuffer.Concurrent and PostUpdateCommands? I'm rather at a loss for syntax and implementation.
     
  8. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    ooh, okay, I see, I think. Um... I'll try and implement this and then ask questions tomorrow. Thank you so much for your help! you have been incredibly useful and I appreciate you pointing me towards knowledge.
     
  9. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    The packages page on Unity is finally starting to get documentation. Basically PostUpdateCommands is for ComponentSystem, EntityCommandBuffer is for JobComponentSystem, the reason for them is you don't want to make changes that would alter the structure of the chunks/archetypes during OnUpdate(), but to defer them.

    https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/job_component_system.html
    https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.ComponentSystem.html

    You can also search either of those in the samples page for usage examples:

    https://github.com/Unity-Technologies/EntityComponentSystemSamples
     
  10. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
  11. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Okay, so... after doing a little research into the recommended subjects, and poking around a little bit, I think I may have something... except that I have no idea how to implement EntityCommandBuffer. Especially after reading this thread, I found myself profoundly confused.
    Here is my current code
    Code (CSharp):
    1.  
    2. public class SpawnShotSystem : JobComponentSystem
    3. {
    4.  
    5.     static private EntityCommandBuffer commandsBuffer;
    6.  
    7.  
    8. [BurstCompile]
    9.     public struct GetShotDataJob : IJobForEach<SpawnShotComponent, LocalToWorld>
    10.     {
    11.         public float currentTime;
    12.         public void Execute(ref SpawnShotComponent spawnShot, ref LocalToWorld localToWorld)
    13.         {
    14.  
    15.             if (spawnShot.timeSinceLastShot < currentTime)
    16.             {
    17.  
    18.                 spawnShot.timeSinceLastShot = currentTime + (1 / spawnShot.rateOfFire);
    19.  
    20.                 SpawnShotData data = new SpawnShotData
    21.                 {
    22.                     bulletPrefab = spawnShot.bulletPrefab,
    23.                     position = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z),
    24.                     rotation = localToWorld.Rotation(),
    25.                     heading = math.mul(localToWorld.Rotation(), spawnShot.forwardDirection),
    26.                     id = spawnShot.id
    27.                 };
    28.  
    29.                 EntityCommandBuffer buffer = ?;
    30.  
    31.                 Entity newProjectile = buffer.Instantiate(spawnShot.bulletPrefab);
    32.                 buffer.SetComponent(newProjectile, new Translation { Value = data.position });
    33.                 buffer.SetComponent(newProjectile, new Rotation { Value = data.rotation });
    34.                 buffer.SetComponent(newProjectile, new PhysicsVelocity { Linear = data.heading });
    35.             }
    36.         }
    37.     }
    38.  
    39.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    40.     {
    41.         GetShotDataJob getDataJob = new GetShotDataJob { currentTime = Time.time };
    42.  
    43.         JobHandle getDataHandle = getDataJob.Schedule(this, inputDeps);
    44.  
    45.         return getDataHandle;
    46.     }
    47.  
    48.  
    49. }
    50.     public struct SpawnShotData //the struct with bullet data
    51.     {
    52.         public Entity bulletPrefab;
    53.         public float3 position;
    54.         public quaternion rotation;
    55.         public float3 heading;
    56.         public int id;
    57.     }
    Line 29 is what I'm banging my head on right now.
     
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,262
    It goes next to public float currentTime. You assign in in the system's OnUpdate.
     
  13. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Look into barriers/sync points, you would store one of the predefined ones as a member of your system then assign it in OnCreate(), using World.Active.GetExistingManager<YourTargetBarrier>(), then assign EntityCommandBuffer as a member of your job as mentioned. In this case you want EntityCommandBuffer.Concurrent.
     
  14. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Oh, okay. When I read the other thread I thought the "Barriers" were something custom to that lad's code, like my "spawnshotdata", because he was dealing with collisions. I'll take different look at it now.
     
  15. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Okay, I'm super confused. I've gotten most of my info from this article and this article. I'm trying to do a ParallelFor job now, because the regular job will not take a buffer. Here is my code again. I'm learning a lot about how this all works, but I am still struggling greatly to put the pieces together.
    Code (CSharp):
    1.  
    2.  public EntityCommandBuffer commandsBuffer;
    3.     protected override void OnCreate()
    4.     {
    5.         base.OnCreate();
    6.         commandsBuffer = World.Active.GetExistingSystem<BeginSimulationEntityCommandBufferSystem>().PostUpdateCommands;
    7.     }
    8.  
    9. [BurstCompile]
    10.     public struct SpawnShotJob : IJobParallelFor
    11.     {
    12.         public float currentTime;
    13.         public NativeArray<Entity> newProjectiles;
    14.         public EntityCommandBuffer buffer;
    15.         public void Execute(int index)
    16.         {
    17.             SpawnShotComponent spawnShot = newProjectiles[index].SpawnShotComponent;//?
    18.             LocalToWorld localToWorld = newProjectiles[index].LocalToWorld;//?
    19.             if (spawnShot.timeSinceLastShot < currentTime)
    20.             {
    21.  
    22.                 spawnShot.timeSinceLastShot = currentTime + (1 / spawnShot.rateOfFire);
    23.  
    24.                 SpawnShotData data = new SpawnShotData
    25.                 {
    26.                     bulletPrefab = spawnShot.bulletPrefab,
    27.                     position = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z),
    28.                     rotation = localToWorld.Rotation(),
    29.                     heading = math.mul(localToWorld.Rotation(), spawnShot.forwardDirection),
    30.                     id = spawnShot.id
    31.                 };
    32.  
    33.  
    34.  
    35.                 Entity newProjectile = buffer.Instantiate(spawnShot.bulletPrefab);
    36.                 buffer.SetComponent(newProjectile, new Translation { Value = data.position });
    37.                 buffer.SetComponent(newProjectile, new Rotation { Value = data.rotation });
    38.                 buffer.SetComponent(newProjectile, new PhysicsVelocity { Linear = data.heading });
    39.             }
    40.         }
    41.     }
    42.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    43.     {
    44.  
    45.         GetShotDataJob SpawnShotJob = new SpawnShotJob {currentTime=Time.time, newProjectiles= ?, buffer=commandsBuffer };
    46.  
    47.         JobHandle SpawnShotHandle = SpawnShotJob.Schedule(this, inputDeps);
    48.  
    49.         return SpawnShotHandle;
    50.     }
    51.  
    52.  
    53. }
     
  16. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    I think this is all you need, or close:

    Code (CSharp):
    1. public struct ShotSpawnData : IComponentData {
    2.     public float RateOfFire;
    3.     public float3 ForwardDirection;
    4.     public float TimeSinceLastShot;
    5.     public Entity BulletPrefab;
    6.     // Not sure what this is, maybe not necessary anymore?
    7.     public int Id;
    8. }
    9.  
    10. public class SpawnShotSystem : JobComponentSystem {
    11.     // Not sure about best practice here, but this seems to be what unity does.
    12.     EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    13.  
    14.     protected override void OnCreate() {
    15.         endSimulationBufferSystem = World.Active.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
    16.     }
    17.  
    18.     //[BurstCompile] I'm not sure if this is BurstCompilable with the latest version
    19.     // It used to be that DestroyEntity was the only Barrier funtion that was burst compilable.
    20.     // If this code works, try re enabling it and see if burst complains.
    21.     // I changed it to IJobForEachWithEntity because it gives a jobindex which can be used
    22.     // with ECB.
    23.     struct SpawnShotJob : IJobForEachWithEntity<ShotSpawnData, LocalToWorld> {
    24.         // This needs to be concurrent
    25.         public EntityCommandBuffer.Concurrent Buffer;
    26.         public float CurrentTime;
    27.  
    28.         // It's good for concurrency to mark ReadOnly if you're not writing.
    29.         public void Execute(Entity e, int index, [ReadOnly] ref ShotSpawnData shotSpawnData, [ReadOnly] ref LocalToWorld localToWorld) {
    30.             if(shotSpawnData.TimeSinceLastShot < CurrentTime) {
    31.                 shotSpawnData.TimeSinceLastShot = CurrentTime + (1 / shotSpawnData.RateOfFire);
    32.  
    33.                 Entity bulletEntity = Buffer.Instantiate(index, shotSpawnData.BulletPrefab);
    34.                 Buffer.SetComponent(index, bulletEntity, new Translation {
    35.                     Value = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z)
    36.                 });
    37.                 Buffer.SetComponent(index, bulletEntity, new Rotation {
    38.                     Value = localToWorld.Rotation()
    39.                 });
    40.                 Buffer.SetComponent(index, bulletEntity, new PhysicsVelocity {
    41.                     Linear = math.mul(localToWorld.Rotation(), shotSpawnData.ForwardDirection)
    42.                 });
    43.             }
    44.         }
    45.     }
    46.  
    47.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    48.         var spawnShotJob = new SpawnShotJob {
    49.             CurrentTime = Time.time,
    50.             Buffer = endSimulationBufferSystem.PostUpdateCommands.ToConcurrent()
    51.         }.Schedule(this, inputDeps);
    52.         return spawnShotJob;
    53.     }
    54. }
     
    Last edited: Aug 21, 2019
  17. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Thank you! That clears up a lot about how to write the system. I have an issue though. If I add .ToConcurrent() to the end of endSimulationBufferSystem.PostUpdateCommands it becomes a null value (as far as Debug.Log is concerned). Without debug.log I just get a regular null reference, lowlevel.unsafe error at line 48. endSimulationBufferSystem.PostUpdateCommands without .ToConcurrent() is fine, although then of course it does not process the code.
     
  18. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Try this instead:
    Code (CSharp):
    1.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    2.         var spawnShotJob = new SpawnShotJob {
    3.             CurrentTime = Time.time,
    4.             Buffer = endSimulationBufferSystem.CreateCommandBuffer.ToConcurrent()
    5.         }.Schedule(this, inputDeps);
    6.         endSimulationBufferSystem.AddJobHandleForProducer(spawnShotJob);
    7.         return spawnShotJob;
    8.     }
    https://docs.unity3d.com/Packages/c...Unity.Entities.EntityCommandBufferSystem.html
     
  19. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    You shouldn’t get buffer in on create, in on create you should get barrier system, in OnUpdate you should get barrier from barrier system. In parallel job you should use Concurrent version. You should use AddJobHandleForProducer on barrier system with handles which use buffers from this barrier system for correct buffer dependency and playback.
     
  20. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    @eizenhorn could you provide a brief example code of how that should look? Additionally, if you know the address of any documents which would show me how to do that, I would appreciate it. @RecursiveEclipse IT WORKS! And very nicely too! 2000 objects instantiated in a frame with no trouble at all (you were right about Burst Compile not working). I'll post the full working code after I resolve the best practices issues eizenhorn pointed out. Thank you so much.
     
  21. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Actually I'll post what I'm using now in case it helps.

    Code (CSharp):
    1. public class SpawnShotSystem : JobComponentSystem
    2. {
    3.  
    4.     EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    5.    
    6.  
    7.     protected override void OnCreate()
    8.     {
    9.         endSimulationBufferSystem = World.Active.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
    10.     }
    11.  
    12.  
    13.    
    14.    
    15.     public struct SpawnShotJob : IJobForEachWithEntity<SpawnShotComponent, LocalToWorld>
    16.     {
    17.  
    18.         public EntityCommandBuffer.Concurrent Buffer;
    19.         public float CurrentTime;
    20.  
    21.         public void Execute(Entity e, int index, [ReadOnly] ref SpawnShotComponent shotSpawnData, [ReadOnly] ref LocalToWorld localToWorld)
    22.         {
    23.             if (shotSpawnData.timeSinceLastShot < CurrentTime)
    24.             {
    25.                 shotSpawnData.timeSinceLastShot = CurrentTime + (1 / shotSpawnData.rateOfFire);
    26.  
    27.                 Entity bulletEntity = Buffer.Instantiate(index, shotSpawnData.bulletPrefab);
    28.                 Buffer.SetComponent(index, bulletEntity, new Translation
    29.                 {
    30.                     Value = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z)
    31.                 });
    32.                 Buffer.SetComponent(index, bulletEntity, new Rotation
    33.                 {
    34.                     Value = localToWorld.Rotation()
    35.                 });
    36.                 Buffer.SetComponent(index, bulletEntity, new PhysicsVelocity
    37.                 {
    38.                     Linear = math.mul(localToWorld.Rotation(), shotSpawnData.forwardDirection)
    39.                 });
    40.             }
    41.         }
    42.     }
    43.  
    44.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    45.     {
    46.         var spawnShotJob = new SpawnShotJob
    47.         {
    48.             CurrentTime = Time.time,
    49.             Buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent()
    50.         }.Schedule(this, inputDeps);
    51.         endSimulationBufferSystem.AddJobHandleForProducer(spawnShotJob);
    52.         return spawnShotJob;
    53.     }
    54.  
    55.  
    56. }
    57.     public struct SpawnShotData //the struct with bullet data
    58.     {
    59.         public Entity bulletPrefab;
    60.         public float3 position;
    61.         public quaternion rotation;
    62.         public float3 heading;
    63.         public int id;
    64.     }
     
  22. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Hrm, Actually, I'm seeing the projectiles spawn at 0,0,0 for a split second before they are moved to their correct positions. Why would it do that if EndSimulationEntityCommandBufferSystem happens before the PresentationSystemGroup ?
     
  23. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Cause it runs after transform system, which updates LocalToWorld matrix with actual values from Translation. Hybrid Renderer v2 uses LocalToWorld for render batches.
     
    MadboyJames likes this.
  24. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Okay, I see. Thanks! I changed my buffer to BeginSimulationEntityCommandBufferSystem and it fixed it. I would still love to see some example code about getting a barrier rather than the buffer, even pseudo code. Something to get me started.
     
  25. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    What? Barrier it’s buffer itself (in simple words) you work with barriers through buffer. Or what you mean speaking of “getting a barrier”?
     
  26. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    What would a barrier system be? Is EndSimulationBufferSystem a barrier system (I wouldn't think so, because it says it's a buffer system)? How is a barrier different from a buffer?
     
  27. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    I think there is confusion here because eizenhorn said

    But I think he means buffer from barrier. EndSimulationEntityCommandBufferSystem is a barrier, if you look at the implementation, it inherits EntityCommandBufferSystem which used to be named BarrierSystem, barriersystem/buffersystem are the same thing. A buffer itself just queues commands for the barrier/buffer system when it runs later on.

    The reason you see Entities at 0, 0, 0 for a frame is because of system update order. If your system comes after TransformSystem, your entities would be processed(transform wise) after TransformSystem runs in the next frame. You need to use [UpdateBefore(typeof(TransformSystem))] at the top of your system name.
     
    Last edited: Aug 23, 2019
    MadboyJames likes this.
  28. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Oh, I see. Yes my confusion was with the terminology. Okay. Thank you both RecusiveEclipse and eizenhorn! I will post the final code (for others who have my questions when getting into ECS) over the weekend.
     
  29. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Oh, yes it was typo :) of course buffer from barrier. My bad.