Search Unity

ECS Advice

Discussion in 'Entity Component System' started by Shabbalaka, Sep 26, 2019.

  1. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    :)

    Just to be clear the ps was for @Antypodish - he corrected his typo

    i hope you got it running now...
     
  2. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    lol, I am still struggling with it :(, I seem to run into all sorts of issues at the moment and end up spending hours trawling the internet.

    This is the script i wrote which allows me to create singular entitys or multiple entitys by simply adding it to a GameObject and then entering the values, In this case the Enemys gameobject and then checking the box for multiple and entering the number of enemies I wish to create.

    I tried to create 2 RenderMeshes and attach them to a Enemy but it said "You cannot attach 2 Components of the same type to an entity"

    Ok this makes sense to me, So I then tried to declare them as Components as such.

    Code (CSharp):
    1.         public struct InRangeComponent : IComponentData {
    2.  
    3.             public RenderMesh InRange;
    4.         };
    5.  
    6.             public struct OutRangeComponent : IComponentData {
    7.  
    8.             public RenderMesh OutRange;
    9.         };
    10.  
    11.  
    12.      
    13.  
    14.     #endregion
    15.  
    16.     #region EntityManagerDeclaration
    17.         private EntityManager em;
    18.  
    19.     #endregion
    20.  
    21.     #region Start
    22.     public void Start()
    23.     {
    24.  
    25.         em.SetComponentData(entity, new InRangeComponent{mesh = mesh, material = InRangeMaterial});


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Rendering;
    6. using Unity.Transforms;
    7. using Unity.Collections;
    8. /*
    9.     Attach this script to an empty gameobject.
    10. */
    11.  
    12. public class Enemy : MonoBehaviour
    13. {
    14.  
    15.     #region Variables
    16.         [SerializeField]
    17.         public Mesh mesh;
    18.  
    19.         [SerializeField]
    20.         public Material OutRangeMaterial;
    21.  
    22.         [SerializeField]
    23.         public Material InRangeMaterial;
    24.  
    25.         public Entity entity;
    26.         [SerializeField]
    27.         public NativeArray<Entity> entities;
    28.  
    29.         public EntityArchetype EA;
    30.  
    31.         [SerializeField]
    32.         public bool createMultiple;
    33.         [SerializeField]
    34.         public int numberToCreate;
    35.  
    36.         [SerializeField]
    37.  
    38.         public bool MetRequirements = true;
    39.  
    40.         public int NumEntities;
    41.  
    42.  
    43.      
    44.  
    45.      
    46.  
    47.  
    48.     #endregion
    49.  
    50.     #region EntityID
    51.         //This is Entity <Tag>
    52.         public struct Unit : IComponentData {};
    53.  
    54.      
    55.  
    56.     #endregion
    57.  
    58.     #region EntityManagerDeclaration
    59.         private EntityManager em;
    60.  
    61.     #endregion
    62.  
    63.     #region Start
    64.     public void Start()
    65.     {
    66.      
    67.      
    68.         //Create one entity.
    69.         if((!createMultiple) && (numberToCreate >= 1))
    70.         {
    71.             Debug.Log("**ERROR ** - You chose to create " + numberToCreate.ToString() + " entities \n " +
    72.             " 0 entities have been created.");
    73.             createMultiple = false;
    74.             MetRequirements = false;
    75.        
    76.         }
    77.         //create numerous entities.
    78.         if((createMultiple) && (numberToCreate < 2))
    79.         {
    80.             Debug.Log("**ERROR ** - You chose to create more than one entity," +
    81.             " \n  Please enter a number higher than 0 or 1");
    82.             createMultiple = true;
    83.             MetRequirements = false;
    84.          
    85.         }
    86.      
    87.         if(MetRequirements)
    88.         {
    89.             SetEntityManager();
    90.             SetEntity();
    91.             AddComponents();
    92.             SetComponentData();
    93.         }
    94.  
    95.  
    96.  
    97.     }
    98.     #endregion
    99.  
    100.     #region EntityManager
    101.  
    102.         public void SetEntityManager()
    103.         {
    104.            em = World.Active.EntityManager;
    105.         }
    106.  
    107.     #endregion
    108.  
    109.     #region EntityCreation
    110.  
    111.         public void SetEntity()
    112.         {
    113.  
    114.             if(createMultiple)
    115.             {
    116.                 entities = new NativeArray<Entity>(numberToCreate, Allocator.Temp);
    117.                 AddMultipleComponents();
    118.             }
    119.  
    120.  
    121.  
    122.             if(!createMultiple)
    123.             {
    124.  
    125.                 entity = em.CreateEntity();
    126.  
    127.             }
    128.  
    129.  
    130.  
    131.         }
    132.     #endregion
    133.  
    134.     #region EntityComponents
    135.       public void AddComponents()
    136.         {
    137.             //***SINGULAR ENTITY CREATION */
    138.             if(!createMultiple)
    139.             {
    140.                 //Entity tag.
    141.                 em.AddComponent(entity, typeof(Unit));
    142.  
    143.                 //Entity Visual and positional Components.
    144.                 em.AddComponent(entity, typeof(Translation));
    145.                 em.AddComponent(entity, typeof(RenderMesh));
    146.                 em.AddComponent(entity, typeof(Rotation));
    147.                 em.AddComponent(entity, typeof (LocalToWorld));
    148.                 //Custom Components..
    149.  
    150.             }
    151.         }
    152.  
    153.     #endregion
    154.  
    155.     #region AddMultipleComponents
    156.  
    157.     public void AddMultipleComponents()
    158.     {
    159.  
    160.             /* MULTIPLE ENTITY CREATION */
    161.             if(createMultiple)
    162.             {
    163.                 EA = em.CreateArchetype
    164.                 (
    165.              
    166.                 //Entity Visual and positional Components.
    167.                 typeof(Translation),
    168.                 typeof(RenderMesh),
    169.                 typeof(Rotation),
    170.                 typeof(LocalToWorld),
    171.                 //Custom Components..
    172.                 typeof(Unit),
    173.                 typeof(MoveSpeedComponent)
    174.                 );
    175.  
    176.                 em.CreateEntity(EA, entities);
    177.  
    178.              
    179.             }
    180.  
    181.  
    182.  
    183.  
    184.     }
    185.  
    186.  
    187.  
    188.     #endregion
    189.  
    190.     #region SetComponentsData
    191.  
    192.         public void SetComponentData()
    193.         {
    194.             if(!createMultiple)
    195.             {
    196.             //Set entity position.
    197.             em.SetComponentData(entity, new Translation { Value = new Unity.Mathematics.float3 (0,0,0) });
    198.             em.SetComponentData(entity, new MoveSpeedComponent { Value = Random.Range(0F,10000f) });
    199.          
    200.  
    201.             //Set Custom Component Data.
    202.  
    203.          
    204.          
    205.             //Set Entity Visual information
    206.             em.SetSharedComponentData (entity, new RenderMesh
    207.                 {
    208.                     mesh = mesh,
    209.                     material = OutRangeMaterial
    210.                 }
    211.             );
    212.             }
    213.          
    214.             if(createMultiple)
    215.             {
    216.                 NumEntities = entities.Length;
    217.  
    218.                 for(int i = 0 ; i < entities.Length; i++)
    219.                 {
    220.  
    221.                         //Set entity position.
    222.                     em.SetComponentData(entities[i], new Translation { Value = new Unity.Mathematics.float3 (Random.Range(-20,20),Random.Range(-20,20),Random.Range(-20,20)) });
    223.                  //em.SetComponentData(entities[i], new Translation { Value = new Unity.Mathematics.float3 (1,1,1) });
    224.  
    225.                     em.SetComponentData(entities[i], new MoveSpeedComponent { Value = Random.Range(0f,5000f)});
    226.                
    227.                     //Set Custom Component Data.
    228.      
    229.                  
    230.                     //Set Entity Visual information
    231.                     em.SetSharedComponentData (entities[i], new RenderMesh
    232.                         {
    233.                             mesh = mesh,
    234.                             material = OutRangeMaterial,
    235.                          
    236.                         }
    237.                     );
    238.  
    239.                  
    240.  
    241.  
    242.                
    243.  
    244.  
    245.  
    246.                 }
    247.  
    248.                 entities.Dispose();
    249.  
    250.             }
    251.  
    252.         }
    253.     #endregion
    254. }
    255.  
    256.  
    Which then resulted in an error "Enemy.InRangeComponent does not contain a definition for mesh"...

    I feel so incredibly stupid at the moment and unless someone says exactly what is wrong instead of trying to read all these posts and hours of internet trawling, It feels like I,m going down a rabbithole that is becoming messier and messier. Example I read a post which said to use MeshInstanceRenderer only to find out that has been replaced by RenderMesh.

    Why can you not apply a List of Materials to a RenderMesh and then set a specific entity a material by its index in the array ??

    I am so sorry to all who have tried to explain this to me at the moment..

    How ever I did learn some things whilst trawling about the Components and that an Entity cannot have more than 1 type and that SetSharedComponentData is what it says but you cannot seem to edit a singular entity which has SharedData or at least I could not find any answers on the internet.

    as a example of what I mean

    Code (CSharp):
    1.  
    2.         public Material material1;
    3.         public Material material2;
    4.         for(int i = 0; i < GameObject.length; i++)
    5.         if(GameObject.tag == "Enemy")
    6.         {
    7.             GameObejct.Renderer.Material = material1;
    8.         }
    9.  
    10.         if(GameObject.tag == "Player")
    11.         {
    12.             GameObejct.Renderer.Material = material1;
    13.         }
    I know the code is wrong but it shows what I am trying to vision.
     
    Last edited: Oct 27, 2019
  3. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    You have to remove rendermesh and add back the right one. You can also change just the material on the rendermesh (will still be a structural change) - unfortunately I have a busy week ahead - if I find a moment I will post something for you - I can see you are trying.... in any case, the next release should have instance color included so it’s not something you should spend so much time on...
     
  4. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thank you so much Sngdan, Im sorry that I am just not getting this, I will be happy about Instance Color when it,s implemented.

    I shall look into other things such as movements and player inputs which im sure theres loads of tutorials and videos as well as particle effects (No point in killing enemies if they just vanish) lol :)
     
  5. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    this is ugly but should get you going...
     

    Attached Files:

  6. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thank you so much for this example!!

    Can I just ask for one small explanation as I am not sure what is happening with this line of code.

    _activeMaterial = ((int) Time.realtimeSinceStartup) % 2 != 0 ? RedMaterial : WhiteMaterial;

    I think its saying assign the active material to the int since the scene started but i got no clue what % 2 !=0 ? means ;)
     
  7. BanJaxe

    BanJaxe

    Joined:
    Nov 20, 2017
    Posts:
    47
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    As explained above. What’s happening here is...

    Var fullsecondspassed = (int) Time.realtimesincestartup
    Var restofDivision = fullsecondspassed % 2
    Var oddSecondspassed = restofdevision != 0

    if (oddsecondspassed)
    _material = redmaterial
    Else
    _material = whitematerial

    Effectively changing the material (which has different color) every second
     
  9. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    thanks so much for that explanation!

    , Time to move onto the next hurdle.... lol

    I think this will be using Nested Meshes as RenderMesh works for a singular object like a sphere or a cube but when you have say a Modular spaceship or something similar it will be a prefab of nested meshes

    And I know that you could "group" them altogether and move them altogether as a GameObject or even access the children if you wanted to affect only one of the children.

    As usual I will attempt this myself before asking for help on here and keep you guys updated on progress/ attempts.

    I hope that other people will be able to read through this post and maybe shed some extra light on using ECS.
     
    Last edited: Oct 28, 2019
  10. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ok I have been making more progress with this project and have managed to make it so that the player has a radius of detection and anytime a enemy intersects with the player it changes the material of the enemy to indicate it has "collided" with the invisible radius.

    I have uploaded the project but have faced 2 issues now, Why is there such little adviceon achieving basic things in ECS. ;(

    Anyways here goes my new issues.

    1 : How do I pass a value to a HealthSystem which only job is to deduct player damage from the enemy HP and then if the health is 0.

    I only want to pass a value to the HealthSystem IF an enemy intersects with the players radius.

    2 : Instantiate a particle effect

    I think I can pass the Particle System from the Enemy Script to the HealthSystem directly so the HealthSystem knows what to spawn when the health is 0.

    Again in GameObjects I would have done this easy like having a script which says TakeDamage on the Enemy and then passed the value from a script on the player which would send the value of player damage across and do a check on the HP to see if it has reached or is less than 0

    I have noticed that if i choose to spawn 5000 enemys it barely runs at 30 FPS on my system which is a
    i7 700k, 16gb DDR3 Ram and a GTX 970.

    I am guessing I need to look at Jobs and Burst Compiling to get high amounts of entitys on screen at once.

    At the moment Systems feel very clunky to me as they seem to run constantly and there appears to be no way to stop them or "call" them as such if a collision occurs then TakeDamage(PlayerDamageValue);

    I will continue on trying to achieve this and post my results.

    Many thanks to all who read and reply!
     

    Attached Files:

    Last edited: Oct 31, 2019
  11. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ok I have been looking up jobs and how to write them and such.

    I followed a tutorial/guide here but after following the guide I receive an error, As usual the errors messages seem rather cryptic.

    https://www.red-gate.com/simple-talk/dotnet/c-programming/introducing-the-unity-job-system/

    This is the error, I am unsure as to why this is happening as I am only starting to explore Jobs.
    So any light on this error is appreciated!

    InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it
    Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckDeallocateAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) <0x26209725970 + 0x00052> in <2ce230928a98496ba12ca183760fb5f6>:0
    Unity.Collections.LowLevel.Unsafe.DisposeSentinel.Dispose (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle& safety, Unity.Collections.LowLevel.Unsafe.DisposeSentinel& sentinel) (at C:/buildslave/unity/build/Runtime/Export/NativeArray/DisposeSentinel.cs:73)
    Unity.Collections.NativeArray`1[T].Dispose () (at C:/buildslave/unity/build/Runtime/Export/NativeArray/NativeArray.cs:161)
    CubeMovementJob.LateUpdate () (at Assets/CubeMovementJob.cs:105)

    I am not sure why it is saying this as the NativeArray.Dispose is called in LateUpdate and as I undestand that will happen after every Update has been completed??

    I have posted the Job code below.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Jobs;
    5. using Unity.Entities;
    6. using Unity.Collections;
    7. using UnityEngine.Jobs;
    8.  
    9.  
    10. public class CubeMovementJob : MonoBehaviour
    11. {
    12.  
    13.  
    14.     public int NumberToSpawn = 3000;
    15.     public float MoveSpeed = 20f;
    16.     public int spawnRange = 50;
    17.     public bool useJob;
    18.  
    19.     private Transform[] transforms;
    20.     private Vector3[] targets;
    21.  
    22.     private List<GameObject> cubes = new List<GameObject>();
    23.  
    24.     private TransformAccessArray transAccArr;
    25.  
    26.     private NativeArray<Vector3> nativeTargets;
    27.  
    28.     private MovementJob job;
    29.     private JobHandle newJobHandle;
    30.  
    31.     struct MovementJob : IJobParallelForTransform
    32.     {
    33.  
    34.         public float deltaTime;
    35.         public NativeArray<Vector3> Targets;
    36.         public float speed;
    37.  
    38.  
    39.  
    40.         public void Execute(int i, TransformAccess transform)
    41.         {
    42.  
    43.             transform.position = Vector3.Lerp(transform.position, Targets[i], deltaTime / speed);
    44.         }
    45.     }
    46.  
    47.     // Start is called before the first frame update
    48.     void Start()
    49.     {
    50.  
    51.         transforms = new Transform[NumberToSpawn];
    52.         for(int i = 0; i < NumberToSpawn; i++)
    53.         {
    54.  
    55.  
    56.             GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    57.             cubes.Add(obj);
    58.             obj.transform.position = new Vector3(Random.Range(-spawnRange,spawnRange),Random.Range(-spawnRange,spawnRange),Random.Range(-spawnRange,spawnRange));
    59.             obj.GetComponent<MeshRenderer>().material.color = Color.green;
    60.             transforms[i] = obj.transform;
    61.  
    62.         }
    63.  
    64.      
    65.         targets = new Vector3[transforms.Length];
    66.         StartCoroutine(GenerateTargets());
    67.  
    68.  
    69.  
    70.     }
    71.  
    72.     // Update is called once per frame
    73.     void Update()
    74.     {
    75.  
    76.  
    77.         transAccArr = new TransformAccessArray(transforms);
    78.         nativeTargets = new NativeArray<Vector3>(targets, Allocator.Temp);
    79.         if(useJob == true)
    80.         {
    81.  
    82.             job = new MovementJob();
    83.             job.deltaTime = Time.deltaTime;
    84.             job.Targets = nativeTargets;
    85.             job.speed = MoveSpeed;
    86.             newJobHandle  = job.Schedule(transAccArr);
    87.  
    88.         }else{
    89.             for (int i = 0; i < transAccArr.length; i++)
    90.             {
    91.  
    92.                 cubes[i].transform.position = Vector3.Lerp(cubes[i].transform.position, targets[i], Time.deltaTime / MoveSpeed);
    93.  
    94.  
    95.             }
    96.         }  
    97.     }
    98.  
    99.  
    100.     private void LateUpdate()
    101.     {
    102.  
    103.         newJobHandle.Complete();
    104.         transAccArr.Dispose();
    105.         nativeTargets.Dispose();
    106.  
    107.  
    108.     }
    109.  
    110.     public IEnumerator GenerateTargets()
    111.     {
    112.  
    113.         for(int i = 0; i < targets.Length; i++)
    114.         {
    115.  
    116.             targets[i] = new Vector3(Random.Range(-spawnRange, spawnRange), Random.Range(-spawnRange, spawnRange), Random.Range(-spawnRange, spawnRange));
    117.             yield return new WaitForSeconds(2);
    118.         }
    119.     }
    120. }
    121.  
    Another small question is if you are using Jobs in a ECS ComponentSystem, I would assume you would use Translation instead of Transform.position as I would assume that eventually the Monobehaviour system would be deprecated in a full ECS System.

    Many thanks!
     
    Last edited: Nov 2, 2019
  12. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    nativeTargets should be allocated with TempJob allocator.
     
  13. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thanks for the reply, That worked a charm.

    Can someone answer why every Job tutorial I see has the Job coded within a Monobehaviour, I thought the whole point of ECS was to move away from Monobehaviours completely.

    I have read a few things such as the EntityManager is not accesible within a Job.
    The Job actually processes "copies" of the data passed to it unless it,s through a NativeArray.

    With much help from the people on here I managed to write something which is working in the System and detects when something is in the radius of the player and changes the material.

    But I have no idea how this would be as a Job as half of the variables cannot be used like to get the player Entity I cannot Use GetSingleton inside the Job.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Transforms;
    5. using Unity.Entities;
    6. using Unity.Rendering;
    7. using Unity.Collections;
    8.  
    9. public class DistanceSystem : ComponentSystem
    10. {
    11.     public struct Filter
    12.     {
    13.         public Translation translation;
    14.         public RenderMesh renderMesh;
    15.         public HealthComponent healthComponent;
    16.         public Enemy.EnemyTag enemy;
    17.     }
    18.  
    19.     public Material inRange;
    20.     public Material outRange;
    21.  
    22.     public float playerDamage = 1f;
    23.  
    24.     protected override void OnUpdate()
    25.     {
    26.         EntityManager em = World.Active.EntityManager;
    27.  
    28.         var playerEntity = GetSingletonEntity<Player.PlayerTag>();
    29.         var playerPosition = em.GetComponentData<Translation>(playerEntity);
    30.  
    31.         Entities.WithAll<Enemy.EnemyTag>().ForEach((Entity e, ref Translation translation) =>
    32.         {    
    33.             var rm = em.GetSharedComponentData<RenderMesh>(e);
    34.  
    35.             if(Vector3.Distance(playerPosition.Value, translation.Value)
    36.             < em.GetComponentData<DistanceComponent>(playerEntity).Radius)
    37.             {
    38.                // Debug.Log(e.Index + " is too close.");
    39.                 rm.material = inRange;
    40.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    41.  
    42.                
    43.             }else{
    44.  
    45.                 rm.material = outRange;
    46.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    47.             }
    48.         });
    49.     }
    50. }
    51.  
    This is proving way more difficult than I first assumed it would be due to the lack of tutorials and outdated advice/tutorials that currently exist on the internet.

    Many thanks to all who are contributing to my progression in this and I pray that one day it,ll just "click".
     
  14. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Just when I thought I was getting somewhere I hit a roadblock again...

    RadiusJobSystem.

    I seem to be unable to do a simple "Count all entities in the scene with X Components and then return this as a integer or a NativeArray of Translations so I can compare the distance to Enemy and the player.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Jobs;
    5. using Unity.Entities;
    6. using Unity.Collections;
    7. using Unity.Transforms;
    8. using Unity.Jobs;
    9. using Unity.Rendering;
    10.  
    11. public class RadiusSystem : JobComponentSystem
    12. {
    13.     /*These values are passed directly from Enemy Script to allow us to change the materials and
    14.     form a communication bond between our job and our enemies.*/
    15.     public Material InRange;
    16.     public Material OutRange;
    17.     public int enemyCount;
    18.  
    19.     //The struct RadiusJob defines what we are working on in this case only Entities with an EnemyTag and a Translation component.
    20.     public struct RadiusJob : IJobForEach<Enemy.EnemyTag, Translation>
    21.     {
    22.  
    23.         public float deltaTime;
    24.         public Translation _playerPosition;
    25.         public float _playerRadius;
    26.  
    27.        // public NativeArray<Translation> EnemyPositions;
    28.         //Execute this job on all entities with EnemyTag, Translation.
    29.         //[ReadOnly] apply this to any tags that we only want to read not modify in anyway.
    30.         //[ReadOnly] also applies to any "empty" components and because EnemyTag is empty.
    31.         public void Execute([ReadOnly] ref Enemy.EnemyTag etag, ref Translation curPos)
    32.         {
    33.             /*for(int i = 0; i<EnemyPositions.Length; i++)
    34.             {
    35.                 if(Vector3.Distance(_playerPosition.Value, curPos[i].Value)
    36.              < _playerRadius)
    37.              {
    38.                  Debug.Log(curPos[i].Value);
    39.              }
    40.             }
    41.             */
    42.             if(Vector3.Distance(_playerPosition.Value, curPos.Value)
    43.              < _playerRadius)
    44.              {
    45.                  Debug.Log(curPos.Value);
    46.              }
    47.         }
    48.     }
    49.     //Runs on the main Update thread and we pass all our values that we want to work with here.
    50.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    51.     {
    52.  
    53.         Entity PlayerEntity = GetSingletonEntity<Player.PlayerTag>();
    54.         Translation PlayerPos = EntityManager.GetComponentData<Translation>(PlayerEntity);
    55.         float PlayerRadius = EntityManager.GetComponentData <DistanceComponent>(PlayerEntity).Radius;
    56.  
    57.         //Get all
    58.         //positions = EntityManager.GetAllEntities(Enemy.EnemyTag, Allocator.Temp);
    59.  
    60.         var job = new RadiusJob
    61.         {
    62.             deltaTime = Time.deltaTime,
    63.             _playerPosition = PlayerPos,
    64.             _playerRadius = PlayerRadius
    65.             //EnemyPositions = positions
    66.         };
    67.         //positions.Dispose();
    68.  
    69.         //Run the job.
    70.         return job.Schedule(this, inputDeps);
    71.     }
    72. }

    I am also receiving a error which says "Internal: JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak.

    Not sure where this would be as I cannot see any information that is "leaking".

    Once again I,ll keep this posted for anyone who wants to read a noob,s progress with ECS and the Job System and it may even help others.

    However the job system does print out the values of Translations but its a case of with 250 Enemys I cannot tell which enemy is the closest and I cannot get it to print out an Index or some identifier of the closest enemys.

    Thanks all!
     
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    It is not obvious what you want for inputs and outputs here. What you are asking for does not line up with what you have code-wise. So I took a guess at what you might want and wrote up an example that might help you. Haven't really tested the code, but I have similar working code in my projects.

    Code (CSharp):
    1. public class RadiusSystem : JobComponentSystem
    2.     {
    3.         [RequireComponentTag(typeof(EnemyTag))]
    4.         [BurstCompile]
    5.         public struct RadiusJob : IJobForEach<Translation>
    6.         {
    7.             public float3 playerPosition;
    8.             public float playerRadiusSq;
    9.             public Entity playerEntity;
    10.             public ComponentDataFromEntity<NearbyEnemeyCount> nearbyEntityCountFromEntity;
    11.  
    12.             public void Execute(ref Translation enemyPos)
    13.             {
    14.                 if (math.distancesq(playerPosition, enemyPos.Value) < playerRadiusSq)
    15.                 {
    16.                     nearbyEntityCountFromEntity[playerEntity] = new NearbyEnemeyCount { Count = nearbyEntityCountFromEntity[playerEntity].Count + 1 };
    17.                 }
    18.             }
    19.         }
    20.  
    21.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    22.         {
    23.             Entity PlayerEntity = GetSingletonEntity<PlayerTag>();
    24.             Translation PlayerPos = EntityManager.GetComponentData<Translation>(PlayerEntity);
    25.             float PlayerRadius = EntityManager.GetComponentData<DistanceComponent>(PlayerEntity).Radius;
    26.  
    27.             var job = new RadiusJob
    28.             {
    29.                 playerPosition = PlayerPos.Value,
    30.                 playerRadiusSq = PlayerRadius * PlayerRadius,
    31.                 playerEntity = PlayerEntity,
    32.                 nearbyEntityCountFromEntity = GetComponentDataFromEntity<NearbyEnemeyCount>(false),
    33.             };
    34.  
    35.             return job.ScheduleSingle(this, inputDeps);
    36.         }
    37.     }
     
  16. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    @DreamingImLatios :

    Many thanks for the reply, What I was struggling with is that within the Job i thought I had to pass a NativeArray of all the enemies in the scene so then the job could "loop" through each enemy entity and check it,s position based upon the player. and then compare the "allowed" radius define.

    But it seems that was not necessary.

    Many thanks for the reply, It's always good to have working examples that you can examine and hopefully open some light on the subject.

    I will keep this thread updated ; )
     
  17. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Nope im still stuck even with this example from @DreamingImLatios :

    Sure it counts up the NearbyEnemys but how do I get all the components from the nearby enemy and if the same enemy gets within the player radius more than once it still counts it as a "unique" interaction so 10 enemys can get near to the player 5000,s times but theres no way to keep track of each entitiy like adding it to a list or something...

    Code (CSharp):
    1. public class RadiusJobSystem : JobComponentSystem
    2.     {
    3.        
    4.       [SerializeField]
    5.         [RequireComponentTag(typeof(Enemy.EnemyTag))]
    6.         [BurstCompile]
    7.         public struct Job_Radius : IJobForEach<Translation>
    8.         {
    9.             public float3 playerPosition;
    10.             public float playerRadiusSq;
    11.             public Entity playerEntity;
    12.             public NativeArray<Entity> Enemies;
    13.  
    14.            
    15.             [SerializeField]
    16.             public void Execute(ref Translation enemyPos)
    17.             {
    18.                
    19.                 foreach(Entity enemie in Enemies)
    20.                 {
    21.                   ///WHY CANNOT I NOT GetComponentData from an Entity within a Job??
    22.                   enemie.GetComponentData<HealthComponent>.HealthValue;
    23.                 }
    24.             }
    25.         }
    26.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    27.         {
    28.             Entity PlayerEntity = GetSingletonEntity<Player.PlayerTag>();
    29.             Translation PlayerPos = EntityManager.GetComponentData<Translation>(PlayerEntity);
    30.             float PlayerRadius = EntityManager.GetComponentData<DistanceComponent>(PlayerEntity).Radius;
    31.  
    32.             //Create a query of all the Enemys with EnemyTags.
    33.             EntityQuery query = GetEntityQuery(
    34.             ComponentType.ReadOnly<Enemy.EnemyTag>()
    35.             );
    36.  
    37.             //Assign all the entities found in the above query to a temporary job.
    38.             var entities = query.ToEntityArray(Allocator.TempJob);
    39.  
    40.             var job = new Job_Radius
    41.             {
    42.                 playerPosition = PlayerPos.Value,
    43.                 playerRadiusSq = PlayerRadius * PlayerRadius,
    44.                 playerEntity = PlayerEntity,
    45.                 //Send the enemy entities to the Job.
    46.                 Enemies = entities
    47.            
    48.             };
    49.            
    50.             //Dispose of the entities array as the job now has the values.
    51.             entities.Dispose();
    52.  
    53.            
    54.             return job.ScheduleSingle(this, inputDeps);
    55.  
    56.            
    57.         }
    58.     }
    59.  
    60.  
    61.  
    62.  
    I do not understand why a lot of simple functionality seems to be so difficult at the moment...

    All I want to do is change the material of an enemy when it is in range of the player and then change it back to a default material when the enemy moves out of range.

    This function below works as I want it to but I just cant seem to work out how to "Jobify" it : ( as a lot of the functionality of things like Entities.ForEach or Entities.WithAll(Entity e) does not work inside Jobs.

    Code (CSharp):
    1. public class DistanceSystem : ComponentSystem
    2. {
    3.     public struct Filter
    4.     {
    5.         public Translation translation;
    6.         public RenderMesh renderMesh;
    7.         public HealthComponent healthComponent;
    8.         public Enemy.EnemyTag enemy;
    9.     }
    10.  
    11.     public Material inRange;
    12.     public Material outRange;
    13.  
    14.     public float playerDamage = 1f;
    15.  
    16.     protected override void OnUpdate()
    17.     {
    18.         EntityManager em = World.Active.EntityManager;
    19.  
    20.         var playerEntity = GetSingletonEntity<Player.PlayerTag>();
    21.         var playerPosition = em.GetComponentData<Translation>(playerEntity);
    22.  
    23.         Entities.WithAll<Enemy.EnemyTag>().ForEach((Entity e, ref Translation translation) =>
    24.         {    
    25.             var rm = em.GetSharedComponentData<RenderMesh>(e);
    26.  
    27.             if(Vector3.Distance(playerPosition.Value, translation.Value)
    28.             < em.GetComponentData<DistanceComponent>(playerEntity).Radius)
    29.             {
    30.                // Debug.Log(e.Index + " is too close.");
    31.                 rm.material = inRange;
    32.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    33.  
    34.                
    35.             }else{
    36.  
    37.                 rm.material = outRange;
    38.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    39.             }
    40.         });
    41.     }
    42. }
    Another day goes by where I spend numerous hours trying to work this stuff out again, But perseverance pays off they say.

    Many thanks for all the replies and attempts at trying to explain this to me, Either im incredibly dumb or things are not as straightforward as they first appear with ECS Systems....
     
  18. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Code (CSharp):
    1. foreach(Entity enemie in Enemies)
    2.                 {
    3.                   ///WHY CANNOT I NOT GetComponentData from an Entity within a Job??
    4.                   enemie.GetComponentData<HealthComponent>.HealthValue;
    5.                 }
    First off, enemie is an Entity type. You need an EntityManager, ExclusiveEntityTransaction, EntityCommandBuffer, or ComponentDataFromEntity to do anything with it. It doesn't work the same way GameObjects work. Also, what are you doing with the HealthValue?

    Second, why are you iterating over every enemy inside an Execute of an IJobForEach that is already iterating over every enemy?

    It is difficult right now because you are trying to do stuff with ISharedComponentData which can't really be used easily with jobs. This means you are going to have to update the RenderMeshes on the main thread.

    So really you want to
    1. For each enemy, calculate if they are in range of player.
    2. For each enemy, determine if whether they are in range or out of range changed since last time, and add the enemy to a list or queue.
    3. For each enemy in the list or queue, update their RenderMesh on the main thread.
    Steps 1 and 2 are jobs. You can do all of this in a ComponentSystem or split it up into multiple systems (you can run jobs in ComponentSystems as long as you call Complete on the jobs).

    You can also combine steps 1 and 2 in a single job, but I list them separate because splitting the steps allows for some really clever vectorization optimizations.
     
  19. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thanks for the reply, All of this help is muchly appreciated!

    Am i right in assuming that this line

    public struct Job_Radius : IJobForEach<Translation>

    is basically the same as

    Entities.WithAll<Enemy.EnemyTag>().ForEach((Entity e, ref Translation translation) =>

    ??

    I have uploaded a Demo of the RadiusDemo working on the MainThread but not in a job to show you exactly what I am trying to show you what am I attempting to turn into a Job.
     

    Attached Files:

  20. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    The WithAll maps to the RequireComponentTag. But to get ForEach((Entity e, ref Translation translation), you actually would want an IJobForEachWithEntity instead of an IJobForEach.
     
  21. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thanks,

    How would I go about turning
    For each enemy, calculate if they are in range of player into a Job please.

    Code (CSharp):
    1. Entities.WithAll<Enemy.EnemyTag>().ForEach((Entity e, ref Translation translation) =>
    2.         {
    3.             var rm = em.GetSharedComponentData<RenderMesh>(e);
    4.             if(Vector3.Distance(playerPosition.Value, translation.Value)
    5.             < em.GetComponentData<DistanceComponent>(playerEntity).Radius)
    6.             {
    7.                // Debug.Log(e.Index + " is too close.");
    8.                 rm.material = inRange;
    9.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    10.            
    11.             }else{
    12.                 rm.material = outRange;
    13.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    14.             }
    15.         });
    This is from the ComponentSystem I done with this which does work

    The part I am having difficulty with is example within the ForEach each of the Enemy.EnemyTags can be accessed individually using "// Debug.Log(e.Index + " is too close.");" which will print out Entity 34 is too close when it is in range.

    How would I access an individual entity in the Job system if I have 300 enemy entities and only 50 are in range, I need to access each of those 50 entities individually..

    I guess what I am trying to say is how do I identify an entity

    Obviously I cannot use Entity e within the so e.index will not work. :S

    The healthValue was just a attempt to try and get something back from an individual entity but did not work and threw errors.

    The end goal is to "damage" enemies andd change their materials to indicate that they are being damaged when they are in range of the player eventually but this is proving rather tricky..

    Once again many thanks!
     
    Last edited: Nov 3, 2019
  22. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    You attached the same code three times in a row. I understand what your goal is and what is working and why, but I'm not going to write your code for you. Start with a baby step. Forget about the material swapping and just try to get the enemy damage stuff working. Make an attempt and post questions specific to errors and what doesn't make sense. I'll give you a hint, you will want an IJobForEach<Translation, HealthComponent>.
     
  23. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ok I managed to create a Job which detects the radius and then used a Component System to make material changes based upon the health of a enemy in range.

    I have uploaded the package for your looking and let me know what you think please.

    I am getting really high profiler usage here I guess this is because of the MaterialSystem which is doing the material changes?? Although I am not sure as it says VSync is using most of the CPU which is a i7 8700K.

    Any suggestions how to improve this is appreciated!

    Many thanks for the replies and interactions!!
     

    Attached Files:

    Last edited: Nov 4, 2019
  24. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    what is actually your objective? Is this for learning purposes or do you have an actual use case? Better to post code directly (in spoiler) so people like me who read on mobile can see
     
  25. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Yes. I am definitely not going to open a .unitypackage unless it directly relates to something I am personally working on. But I keep a script in my project as a general scratchpad for experimentation and code completion.

    As for your profiler snapshot, it would help if you showed the full frame, but it looks like you have vsync enabled in the editor and your actual processing time is only a couple milliseconds. The rest is just your computer being idle waiting for your monitor to vblank.
     
  26. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ah sorry, I thought it was easier to post the package but this is all an exercise for learning so I can actually apply what I learn to an actual game at some point.

    I am slowly working my way through this list.

    1: Basic ECS Creation
    2: Detcting Collisions in ECS (Either through distance checks or colliders or both)
    3: Managing User Input (There is tutorials for this I can work through)
    3: Destroying Entities / Modifying entities.
    4:Spawning Death effects in the entitys position.
    5:Jobs

    I feel that this gives me enough to produce a very basic game on a basic level.

    I have to admit I forgot about people viewing this forum on mobiles,

    I will post the Scripts that I made and am open to criticisms/improvements that could be made for optimization purposes.

    MaterialSystem.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Transforms;
    3. using Unity.Entities;
    4. using Unity.Rendering;
    5.  
    6. public class MaterialSystem : ComponentSystem
    7. {
    8.     public struct Filter
    9.     {
    10.         public Translation translation;
    11.         public RenderMesh renderMesh;
    12.         public HealthComponent healthComponent;
    13.        // public Enemy.EnemyTag enemy;
    14.     }
    15.     public Material inRange;
    16.     public Material dead;
    17.     protected override void OnUpdate()
    18.     {
    19.         EntityManager em = World.Active.EntityManager;
    20.         var playerEntity = GetSingletonEntity<PlayerTagComponent>();
    21.         var playerPosition = em.GetComponentData<Translation>(playerEntity);
    22.         Entities.WithAll<EnemyTagComponent>().ForEach((Entity e, ref Translation translation) =>
    23.         {  
    24.             var rm = em.GetSharedComponentData<RenderMesh>(e);
    25.  
    26.            // Debug.Log(e.Index + " is too close." + Vector3.Distance(playerPosition.Value, translation.Value));
    27.             if(Vector3.Distance(playerPosition.Value, translation.Value)
    28.             < em.GetComponentData<DistanceComponent>(playerEntity).Radius)
    29.             {
    30.                //
    31.                 rm.material = inRange;
    32.                 em.SetSharedComponentData<RenderMesh>(e,rm);
    33.             }
    34.  
    35.             if(em.GetComponentData<HealthComponent>(e).HP <= 0)
    36.             {
    37.                     rm.material = dead;
    38.                     em.SetSharedComponentData<RenderMesh>(e,rm);
    39.                     Debug.Log(e.Index + " is dead.");
    40.             }
    41.         });
    42.     }
    43. }
    44.  
    HealthComponent.cs
    (This actually ended up being a repository of components, Which is probably a bad idea in the big scope but for testing purposes I felt this was easier to manage, I should have renamed it to Components.cs)

    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. public struct HealthComponent : IComponentData
    4. {
    5.     public int HP;
    6. }
    7.  
    8. public struct MoveSpeedComponent : IComponentData
    9. {
    10.     public float MoveSpeed;
    11. }
    12.  
    13. public struct EnemyTagComponent : IComponentData
    14. {
    15. }
    16.  
    17. public struct PlayerTagComponent : IComponentData
    18. {
    19. }
    20.  
    21. public struct DistanceComponent : IComponentData
    22. {
    23.     public float Radius;
    24. }
    25.  
    26. public struct EnemyIndexComponent : IComponentData
    27. {
    28.     public int Index;
    29. }
    30.  
    31.  
    Player.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Jobs;
    5. using Unity.Transforms;
    6. using Unity.Entities;
    7. using Unity.Rendering;
    8. using Unity.Mathematics;
    9. public class Player : MonoBehaviour
    10. {
    11.  
    12.   [SerializeField]
    13.     public Mesh mesh;
    14.  
    15.     [SerializeField]
    16.     public Material material;
    17.  
    18.     [SerializeField]
    19.     public float _radius;
    20.  
    21.     public void Start()
    22.     {
    23.  
    24.         EntityManager em = World.Active.EntityManager;
    25.  
    26.  
    27.         Entity entity = em.CreateEntity();
    28.  
    29.         em.AddComponent<PlayerTagComponent>(entity);
    30.         em.AddComponent<MoveSpeedComponent>(entity);
    31.         em.AddComponent<HealthComponent>(entity);
    32.         em.AddComponent<Translation>(entity);
    33.         em.AddComponent<LocalToWorld>(entity);
    34.         em.AddComponent<RenderMesh>(entity);
    35.         em.AddComponent<DistanceComponent>(entity);
    36.         //---------------END ADD COMPONENTS ------------//
    37.         em.SetComponentData(entity, new DistanceComponent{Radius = _radius});
    38.         em.SetSharedComponentData(entity, new RenderMesh{
    39.  
    40.             mesh = mesh,
    41.             material = material
    42.         });
    43.         em.SetComponentData(entity, new Translation { Value
    44.          = new float3 (0,0,-10)});
    45.  
    46.         em.SetComponentData(entity, new HealthComponent{HP = 100});
    47.     }
    48.     //---------------END SET COMPONENT DATA----------------------//
    49. }
    50.  
    Unit.cs(This is the Enemy)
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Entities;
    3. using Unity.Collections;
    4. using Unity.Transforms;
    5. using Unity.Mathematics;
    6. using Unity.Rendering;
    7. public class Unit : MonoBehaviour
    8. {
    9.  
    10.     [SerializeField]
    11.     public Mesh mesh;
    12.  
    13.     [SerializeField]
    14.     public Material material;
    15.  
    16.     [SerializeField]
    17.     public int numSpawn;
    18.  
    19.     [SerializeField]
    20.     public Material inRange;
    21.  
    22.     [SerializeField]
    23.     public Material dead;
    24.  
    25.     EntityManager em;
    26.  
    27.     public void Start()
    28.     {
    29.        
    30.        
    31.         em = World.Active.EntityManager;
    32.  
    33.         var ms = World.Active.GetOrCreateSystem<MaterialSystem>();
    34.  
    35.         ms.inRange = inRange;
    36.         ms.dead = dead;
    37.  
    38.         // Using typeof to create an EntityArchetype from a set of components
    39.         EntityArchetype ae = em.CreateArchetype(
    40.             typeof(Translation),
    41.             typeof(HealthComponent),
    42.             typeof(LocalToWorld),
    43.             typeof(EnemyTagComponent),
    44.             typeof(RenderMesh),
    45.             typeof(MoveSpeedComponent)
    46.             );
    47.  
    48.         NativeArray<Entity> Entities = new NativeArray<Entity>(numSpawn, Allocator.Temp);
    49.  
    50.         em.CreateEntity(ae,Entities);
    51.  
    52.         for(int i = 0; i<Entities.Length; i++)
    53.         {
    54.  
    55.             Entity entity = Entities[i];
    56.  
    57.             em.SetComponentData(entity, new Translation{Value =  new float3(UnityEngine.Random.Range(-35,20),UnityEngine.Random.Range(-5,17),-10) } );
    58.  
    59.             em.SetComponentData(entity, new HealthComponent{HP = UnityEngine.Random.Range(100,10000)});
    60.  
    61.             em.SetComponentData(entity, new MoveSpeedComponent{MoveSpeed = 100f} );
    62.  
    63.             em.SetSharedComponentData(entity,
    64.             new RenderMesh {
    65.  
    66.                 mesh = mesh,
    67.                 material = material
    68.  
    69.             });
    70.  
    71.         }
    72.  
    73.         Entities.Dispose();
    74.     }
    75.  
    76. }
    77.  
    RadiusJobSystem.cs
    (this checks the distance of the enemy and the player and then adjusts the enemy,s HP if they are in range)
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6.  
    7.  
    8. public class RadiusJobSystem : JobComponentSystem
    9.     {
    10.      
    11.       [SerializeField]
    12.         [RequireComponentTag(typeof(EnemyTagComponent))]
    13.         //[BurstCompile]
    14.         public struct Job_Radius : IJobForEach<Translation , HealthComponent>
    15.         {
    16.             public float3 playerPosition;
    17.             public float playerRadiusSq;
    18.             public Entity playerEntity;
    19.          
    20.             [SerializeField]
    21.             public void Execute(ref Translation enemyPos, ref HealthComponent health)
    22.             {
    23.              
    24.                    
    25.                  if(Vector3.Distance(playerPosition, enemyPos.Value) < playerRadiusSq)
    26.                  {
    27.                     // Debug.Log(Vector3.Distance(playerPosition, enemyPos.Value));
    28.                  
    29.                      health.HP -= 1;
    30.  
    31.                  }
    32.  
    33.             }
    34.         }
    35.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    36.         {
    37.             Entity PlayerEntity = GetSingletonEntity<PlayerTagComponent>();
    38.             Translation PlayerPos = EntityManager.GetComponentData<Translation>(PlayerEntity);
    39.             float PlayerRadius = EntityManager.GetComponentData<DistanceComponent>(PlayerEntity).Radius;
    40.             var job = new Job_Radius
    41.             {
    42.                 playerPosition = PlayerPos.Value,
    43.                 playerRadiusSq = PlayerRadius,
    44.                 playerEntity = PlayerEntity,
    45.              
    46.             };
    47.             return job.ScheduleSingle(this, inputDeps);    
    48.         }
    49.     }
    50.  
    51.      
    This is all the scripts that are being used within that package, So any advice on optimizations and improvements are appreicated!

    Once again many thanks for the replies.
     
  27. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Nit: Change Vector3.Distance to math.distance.
    Nit: I don't think any of the [SerializeField] attributes you have littered around do anything. Get rid of them.
    Nit: You actually don't need ScheduleSingle for Job_Radius. My example used it because I was writing to the same index of a NativeArray to store the count. But here you can actually use regular-old Schedule.

    There's plenty of room for optimization for the initialization routines, but if initialization performance does not bother you, it probably isn't worth bothering.

    The big one is that your MaterialSystem is duplicating code in the RadiusJobSystem. In RadiusJobSystem, create a NativeQueue<Entity> and pass in the ParallelWriter into your job. Change your job to an IJobForEachWithEntity while you are at it. Also add an extra component to the enemy called something like WasInRangeLastFrame that stores a bool. In your job, if the current frame's in range value is calculated to be different than last frame, add the enemy entity to the NativeQueue. Then update the WasInRangeLastFrame component. Then in your MaterialSystem, instead of iterating over the Entities, you can iterate over the NativeQueue and update those entities' RenderMesh components.

    You'll probably want to store both the NativeQueue and the JobHandle of the Job somewhere that MaterialSystem can access it.
     
  28. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thanks for the suggestions and optimizations, I will attempt these changes and see what I can do.

    Just a quick question about this line

    "Nit: I don't think any of the [SerializeField] attributes you have littered around do anything. Get rid of them."

    I also cannot seem to find good information regarding NativeQueues, So any information or things i can read which shed some light on those types too would be helpful

    I have looked at the example on https://docs.unity3d.com/Packages/c.../manual/entity_iteration_job.html#with-entity

    and they are using NativeArrays, So whats the difference between NativeQueues and NatvieArrays?

    and dont even get me started on what a ParalellWriter is but when i search the UNity API i get
    "Your search for "ParallelWriter" did not result in any matches. Please try again with a wider search"

    I have them so I can drag and drop mateirals, meshes from the Porject window in the Inspector, I would love to know how would I assign these values in code???

    I have always used the Inspector for assigning values to things like Mesh, Sprite Renderers, Collides, Rigidbodys...

    So to do it all manually in code is a bit of a mystery to me.

    I,m sorry that I am struggling to understand some of the terms and stuff you use as I imagine you have been using ECS for some time and I have barely scratched the surface in the past 2 weeks or so, But thank you for your patience!

    Many thnaks!
     
    Last edited: Nov 5, 2019
  29. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    What's your question?

    https://docs.unity3d.com/Packages/com.unity.collections@0.1/api/Unity.Collections.NativeQueue-1.html

    One's a queue and one's an array? Are you familiar with general data structures? If not, spend some time with that first, as they are essential in DOTS. In particular, I'm choosing NativeQueue over NativeList for its parallel enqueing capability.

    This is a topic better suited for the general scripting or graphics forum sections. Is there a reason you want to do things manually in code?
     
  30. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    So its acceptable to use SerializeFields for assigning Mesh,s and Materials via the Inspector in ECS?

    "One's a queue and one's an array? Are you familiar with general data structures? If not, spend some time with that first, as they are essential in DOTS. In particular, I'm choosing NativeQueue over NativeList for its parallel enqueing capability."

    I am familiar with a lot of the data structures I,ve just not come to using Queues yet, I have used ArrayLists, Lists and Dictionarys to manage lists of Data.

    With the link for NativeQueue it shows me the Syntax but this is where I really struggle as things like

    public struct NativeQueue<T> : IDisposable where T : struct

    I cant quite understand how this applies to a real world scenario, I really struggle with theory. :(

    I guess it would be something like NativeQueue<Entity> : IDisposable (Not sure what the IDisposable means though).

    Would this be a good thread to look through for real world examples of NativeQueues
    https://forum.unity.com/threads/collections-0-1-0-nativequeue-bug.719474/

    I really need to do more theory than trying to learn from actually attempting to do things in Unity.

    Thanks for the help and replies, I really appreciate it!
     
    Last edited: Nov 5, 2019
  31. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Yes. Specifically the Game Object Conversion workflow is the intended DOTS authoring workflow. I made the comment about the [SerializeField] attribute because half the places you used it were on job structs and functions that you weren't actually serializing and the other half of the places were on public fields of MonoBehaviours which don't need the [SerializeField] attribute to be serialized.

    IDisposable is an interface. You can read more about it here: https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.8 But in short, all the NativeCollections implement IDisposable so that you can manually deallocate them when you no longer need them, as their internal storage memory is "native" and not tracked by the C# runtime. Anyways, you can just declare a NativeQueue<Entity> similar to declaring a NativeArray<Entity>. It has different constructor arguments and methods, but it syntactically works the same way as NativeArray.
     
  32. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thanks for your reply again, I have been doing some reading up on the various types of containers available to Unity but I still cannot understand the real world usage of using NativeQueue over NativeArray

    This was from the docmentation

    • NativeList - a resizable NativeArray.
    • NativeHashMap - key and value pairs.
    • NativeMultiHashMap - multiple values per key.
    • NativeQueue - a first in, first out (FIFO) queue.
    As I am not good with theory this does not show me in layman,s term,s, When you would use a NativeQueue or when you should use a NativeList??

    Sorry for the delay in reply I had some family issues to deal with ,

    Many thanks though!
     
  33. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ok a small update I created a job which destroys the entity after its HP reaches 0 or less.

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5.  
    6. public class EnemyHealthSystem : JobComponentSystem
    7. {
    8.     private EndSimulationEntityCommandBufferSystem barrier;
    9.  
    10.     protected override void OnCreate()
    11.     {
    12.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    13.     }
    14.  
    15.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID>
    16.     {
    17.         public EntityCommandBuffer.Concurrent Ecb;
    18.      
    19.      
    20.         //[ReadOnly] public ComponentDataFromEntity<DeadData> Dead;
    21.      
    22.         public void Execute(
    23.             Entity entity,
    24.             int index,
    25.             [ReadOnly] ref EnemyTagComponent enemyTag,
    26.             ref HealthComponent health,
    27.             ref EnemyID id)
    28.         {
    29.          
    30.             if(health.HP <= 0)
    31.             {
    32.                 Debug.Log("Enemy ID "+ id.id + " is dead.");
    33.                 Debug.Log("Entity index is " + index);
    34.                 Ecb.DestroyEntity(index, entity);
    35.             }
    36.  
    37.  
    38.          
    39.         }
    40.     }
    41.  
    42.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    43.     {
    44.         var job = new EnemyHealthJob
    45.         {
    46.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    47.             //health = GetComponentDataFromEntity<HealthComponent>()
    48.         };
    49.         inputDeps = job.Schedule(this, inputDeps);
    50.         barrier.AddJobHandleForProducer(inputDeps);
    51.         return inputDeps;
    52.     }
    53. }
    This is producing the following output when 1 entity is in range of the player.

    Enemy ID 2 is dead.
    UnityEngine.Debug:Log(Object)
    Entity index is 19
    UnityEngine.Debug:Log(Object)
    2 is dead.
    UnityEngine.Debug:Log(Object)

    Im guessing that Entity Index is different to EnemyID as its organised differently by the job, So the entity index will be different to the actual Entity Index in the Entity Debugger.

    Am I correct in thinking that job.Schedule() is a multi-threaded job but job.ScheduleSingle() works on the main thread?
     
    Last edited: Nov 9, 2019
  34. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Not quite. job.ScheduleSingle() just forces the job to be single-threaded, allowing you to use single-threaded writes to containers safely, such as adding to a NativeList or writing to a ComponentDataFromEntity. job.Run() forces it to run immediately on the main thread.
     
  35. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    I am unable to find a decent tutorial / advice on how to use particle systems, As for now the "enemies" just disappear when they die so now I need to add an effect on when they die.

    Something similar to Instantiate(DeathEffect, Enemy.transform.position);

    I can attach a particle system to my Player GameObject in the hierarchy and then use the Convert To Game Object and Inject but then I do not know how I access this Component in an ECS System.

    I tried using a Component with a value of GameObject but of course that gave me an error as a GameObject is not a blittable object.

    I Just had an idea I,ll get back to you.

    Thanks for the replies!
     
  36. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
  37. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Siggisig:

    Thanks for that link, Looks to me like they Object Pool a load of Collision Animations and then pass the position of a Collision to the PlayBulletImpact in the Monobehaviour Script bulletImpactPool and then access that pool using the script Class Name followed by the Method and parameters.

    Sorry for the long explanation there it,s just so I can refer to this thread on my progress and remind myself of things without storing a million links. xD

    My only question here is wouldn,t

    BulletImpactPool.PlayBulletImpact(pos.Value);

    create a sort of Artificial Link between the RemoveDeadSystem and the BulletImpactPool and I thought ECS was supposed to all work individually, Or is there no other way to do Particle Systems and effects in ECS yet?


    I,ll see how I get on with that information and update :)
     
  38. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ok i tried to follow that system but I am calling the method inside a Job I am not sure if this makes a difference but I get an error.

    EnemyDeathSystem.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Transforms;
    5.  
    6. public class EnemyDeathSystem : MonoBehaviour
    7. {
    8.  
    9.     static EnemyDeathSystem instance;
    10.  
    11.     public GameObject EnemyDeathAnimation;
    12.  
    13.     public int PoolSize;
    14.  
    15.     public int CurrentPoolPosition;
    16.  
    17.     public GameObject[] AnimPool;
    18.  
    19.     // Start is called before the first frame update
    20.     void Awake()
    21.     {
    22.  
    23.         if(instance != null && instance != this)
    24.         {
    25.             Destroy(gameObject);
    26.             return;
    27.  
    28.         }else{
    29.  
    30.             instance = this;
    31.         }
    32.             AnimPool = new GameObject[PoolSize];
    33.             for(int i = 0; i < PoolSize; i++)
    34.             {
    35.                AnimPool[i] = Instantiate(EnemyDeathAnimation, instance.transform) as GameObject;
    36.                 AnimPool[i].SetActive(false);
    37.             }
    38.     }
    39.     public void PlayDeathAnimation(Vector3 position)
    40.     {
    41.         Debug.Log("RECEIVED.... " + position);
    42.         if(CurrentPoolPosition >= PoolSize)
    43.         {
    44.             if(AnimPool.Length <= 0)
    45.             {
    46.                 Debug.Log("NO ANIMATIONS TO PLAY.....");
    47.             }else{
    48.  
    49.                 CurrentPoolPosition =+1;
    50.  
    51.                 AnimPool[CurrentPoolPosition].SetActive(false);
    52.                 AnimPool[CurrentPoolPosition].transform.position = position;
    53.                 AnimPool[CurrentPoolPosition].SetActive(true);
    54.             }
    55.         }else{
    56.  
    57.             Debug.Log("EXCEEDED THE NUMBER OF ANIMATIONS TO PLAY.");
    58.         }
    59.     }
    60. }
    61.  
    EnemyHealthSystem
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5. using Unity.Transforms;
    6.  
    7. public class EnemyHealthSystem : JobComponentSystem
    8. {
    9.     private EndSimulationEntityCommandBufferSystem barrier;
    10.  
    11.     protected override void OnCreate()
    12.     {
    13.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    14.     }
    15.  
    16.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID, Translation>
    17.     {
    18.         public EntityCommandBuffer.Concurrent Ecb;
    19.  
    20.         public void Execute(
    21.             Entity entity,
    22.             int index,
    23.             [ReadOnly] ref EnemyTagComponent enemyTag,
    24.             ref HealthComponent health,
    25.             ref EnemyID id,
    26.             ref Translation enemyPos)
    27.         {
    28.            
    29.             if(health.HP <= 0)
    30.             {
    31.                // Debug.Log("Enemy ID "+ id.id + " is dead.");
    32.                // Debug.Log("Entity index is " + index);
    33.                // Ecb.DestroyEntity(index, entity);
    34.              
    35.                 EnemyDeathSystem.PlayDeathAnimation(enemyPos.Value) ;
    36.             }
    37.         }
    38.     }
    39.    
    40.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    41.     {
    42.         var job = new EnemyHealthJob
    43.         {
    44.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    45.             //health = GetComponentDataFromEntity<HealthComponent>()
    46.         };
    47.         inputDeps = job.Schedule(this, inputDeps);
    48.         barrier.AddJobHandleForProducer(inputDeps);
    49.         return inputDeps;
    50.     }
    51. }
    I get an error saying "Assets\RadiusJobEntitySystem.cs(35,17): error CS0120: An object reference is required for the non-static field, method, or property 'EnemyDeathSystem.PlayDeathAnimation(Vector3)'"

    and I cannot use the new Keyword on a Monobehaviour Script.

    Any ideas please?

    Many thanks!!
     
  39. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    Your PlayDeathAnimation() method needs to be static, since you cannot get an instance of that class into the job.
     
  40. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Ahh thanks I did wonder, I seem to be encountering every issue known to man in trying to learn these things, I really appreciate all the help and hope that it can help someone else as well.

    I am getting this now though..

    UnityException: get_transform can only be called from the main thread.
    Constructors and field initializers will be executed from the loading thread when loading a scene.
    Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start

    Its receiving the position but always the GameObject is set to false.

    These error messages are always so cryptic to me, I guess thats the benefits of doing all the theory you get to recognise these and can turn it from useless information to useful information.

    for example I do not call a get_transform method anywhere.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Transforms;
    5.  
    6. public class EnemyDeathSystem : MonoBehaviour
    7. {
    8.  
    9.     static EnemyDeathSystem instance;
    10.  
    11.     public GameObject EnemyDeathAnimation;
    12.  
    13.     public int PoolSize;
    14.  
    15.     public int CurrentPoolPosition;
    16.  
    17.     public GameObject[] AnimPool;
    18.  
    19.     // Start is called before the first frame update
    20.     void Awake()
    21.     {
    22.  
    23.         if(instance != null && instance != this)
    24.         {
    25.             Destroy(gameObject);
    26.             return;
    27.  
    28.         }else{
    29.  
    30.             instance = this;
    31.         }
    32.             instance.AnimPool = new GameObject[PoolSize];
    33.             for(int i = 0; i < PoolSize; i++)
    34.             {
    35.                instance.AnimPool[i] = Instantiate(EnemyDeathAnimation, instance.transform) as GameObject;
    36.                 instance.AnimPool[i].SetActive(false);
    37.             }
    38.     }
    39.     public static void PlayDeathAnimation(Vector3 position)
    40.     {
    41.         Debug.Log("RECEIVED.... " + position);
    42.         if(++instance.CurrentPoolPosition >= instance.AnimPool.Length)
    43.                 instance.CurrentPoolPosition = 0;
    44.  
    45.                // instance.AnimPool[instance.CurrentPoolPosition].SetActive(false);
    46.                 instance.AnimPool[instance.CurrentPoolPosition].transform.position = position;
    47.                 instance.AnimPool[instance.CurrentPoolPosition].SetActive(true);
    48.          
    49.  
    50.     }
    51. }
    52.  
    Once again I cannot thank all the people enough who have contributed their time and effort here!

    I commented out instance.AnimPool[instance.CurrentPoolPosition].transform.position = position; and it said that SetActive can only be called in the MainThread now I seen that those guys are using a Component System which runs on the MainThread normally, Jobs run in the "background", Don't they ?

    I think the OnUpdate method in a job runs on the main thread also, So I am guessing we would need to have a local variable in our job which would then somehow change and in the onupdate method I would check for this state to "spawn" a DeathEffect.
     
    Last edited: Nov 10, 2019
  41. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    Actually you are :) here:
    Code (CSharp):
    1. instance.AnimPool[instance.CurrentPoolPosition][B].transform[/B].position = position;
    In the ECS Angrybots example, they are calling PlayBulletImpact() from the main thread, since the component system they are running is on the main thread. See this file:
    https://github.com/UnityTechnologie...ssets/Scripts/ECS/Systems/RemoveDeadSystem.cs

    So the issue is that you are calling this from a job that is not running on the main thread. If this logic has to be in a job, try scheduling the job on the main thread by using .Run() instead of .Schedule()
    https://docs.unity3d.com/ScriptReference/Unity.Jobs.IJobExtensions.Run.html

    Until Unity has created DOTS-friendly or new versions of all the basic systems of the engine, we're going to have to live with and learn how to work around issues like this.
     
  42. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    You could also do that yes.. For example output an NativeArray<float3> from the job with all the positions you need to spawn effects in that frame, and then iterate over that with a for loop and call PlayDeathAnimation() from there.
     
  43. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    I am unsure how you both access a variable inside a job and how you return something from it.

    I have put together a couple of examples of my thinking not sure if it,s right or wrong at the moment but will test it soon.

    EXAMPLE 1 :
    Code (CSharp):
    1.  
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using UnityEngine;
    6. using Unity.Transforms;
    7.  
    8. public class EnemyHealthSystem : JobComponentSystem
    9. {
    10.    
    11.     private EndSimulationEntityCommandBufferSystem barrier;
    12.  
    13.     protected override void OnCreate()
    14.     {
    15.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    16.     }
    17.  
    18.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID, Translation>
    19.     {
    20.         public EntityCommandBuffer.Concurrent Ecb;
    21.  
    22.         public NativeArray<Vector3> EnemyDeadPositions = new NativeArray<Vector3>();
    23.  
    24.         public void Execute(
    25.             Entity entity,
    26.             int index,
    27.             [ReadOnly] ref EnemyTagComponent enemyTag,
    28.             ref HealthComponent health,
    29.             ref EnemyID id,
    30.             ref Translation enemyPos)
    31.         {
    32.            
    33.             if(health.HP <= 0)
    34.             {
    35.                // Debug.Log("Enemy ID "+ id.id + " is dead.");
    36.                // Debug.Log("Entity index is " + index);
    37.                // Ecb.DestroyEntity(index, entity);    
    38.                 EnemyDeadPositions.Add(enemyPos);
    39.             }
    40.  
    41.          return EnemyDeadPositions;
    42.  
    43.         }
    44.     }
    45.    
    46.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    47.     {
    48.         var job = new EnemyHealthJob
    49.         {
    50.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    51.             //health = GetComponentDataFromEntity<HealthComponent>()
    52.         };
    53.         inputDeps = job.Schedule(this, inputDeps);
    54.         barrier.AddJobHandleForProducer(inputDeps);
    55.         return inputDeps;
    56.  
    57.  
    58.         foreach(Vector3 pos in job.EnemyDeadPositions)
    59.         {
    60.  
    61.             EnemyDeathSystem.PlayDeathAnimation(pos);
    62.  
    63.  
    64.         }
    65.     }
    66. }
    67.  
    RESULT : Assets\RadiusJobEntitySystem.cs(22,37): error CS0573: 'EnemyHealthSystem.EnemyHealthJob': cannot have instance property or field initializers in structs

    EXAMPLE 2 :
    Code (CSharp):
    1. sing Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5. using Unity.Transforms;
    6. public class EnemyHealthSystem : JobComponentSystem
    7. {
    8.     private EndSimulationEntityCommandBufferSystem barrier;
    9.     protected override void OnCreate()
    10.     {
    11.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    12.     }
    13.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID, Translation>
    14.     {
    15.         public EntityCommandBuffer.Concurrent Ecb;
    16.      
    17.         public NativeArray<Vector3> DeadEnemyPositions = new List NativeArray<Vector3>();
    18.         public NativeArray<Vector3> Execute(
    19.             Entity entity,
    20.             int index,
    21.             [ReadOnly] ref EnemyTagComponent enemyTag,
    22.             ref HealthComponent health,
    23.             ref EnemyID id,
    24.             ref Translation enemyPos)
    25.         {
    26.          
    27.             if(health.HP <= 0)
    28.             {
    29.                // Debug.Log("Enemy ID "+ id.id + " is dead.");
    30.                // Debug.Log("Entity index is " + index);
    31.                // Ecb.DestroyEntity(index, entity);
    32.                  DeadEnemyPositions.add(enemyPos.Value);
    33.                 //EnemyDeathSystem.PlayDeathAnimation(enemyPos.Value); /*<---- OUR LIST OF DEAD ENEMYS INSIDE OUR JOB
    34.             }
    35.         }
    36.      
    37.         return DeadEnemyPositions;///<-RETURNING THE LIST OF EITHER 0 OR POPULATED NativeArray.
    38.      
    39.     }
    40.  
    41.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    42.     {
    43.         var job = new EnemyHealthJob
    44.         {
    45.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    46.             //health = GetComponentDataFromEntity<HealthComponent>()
    47.         };
    48.         inputDeps = job.Schedule(this, inputDeps);
    49.         barrier.AddJobHandleForProducer(inputDeps);
    50.         return inputDeps;
    51.      
    52.         //THIS IS GUESSWORK....
    53.         //I THINK I HAVE TO USE EITHER THE JOB ITSELF OR THE JOBHandle to get things??
    54.         NativeArray<Vector3> EnemyList = new NativeArray<Vector3>();
    55.         EnemyList = job;
    56.      
    57.      
    58.  
    59.         //----------OUR LOOP USING THE LIST OF VECTOR3,s INSIDE THE ONUPDATE(MAIN THREAD).I THINK.
    60.         foreach(Vector3 pos in EnemyList)
    61.         {
    62.  
    63.             EnemyDeathSystem.PlayDeathAnimation(pos);
    64.  
    65.         }
    66.  
    67.     }
    68.  
    69.  
    70. }
    RESULT :
    Assets\RadiusJobEntitySystem.cs(13,37): error CS0738: 'EnemyHealthSystem.EnemyHealthJob' does not implement interface member 'IJobForEachWithEntity_ECCCC<EnemyTagComponent, HealthComponent, EnemyID, Translation>.Execute(Entity, int, ref EnemyTagComponent, ref HealthComponent, ref EnemyID, ref Translation)'. 'EnemyHealthSystem.EnemyHealthJob.Execute(Entity, int, ref EnemyTagComponent, ref HealthComponent, ref EnemyID, ref Translation)' cannot implement 'IJobForEachWithEntity_ECCCC<EnemyTagComponent, HealthComponent, EnemyID, Translation>.Execute(Entity, int, ref EnemyTagComponent, ref HealthComponent, ref EnemyID, ref Translation)' because it does not have the matching return type of 'void'.

    I guess this is telling me that an Execute function in a Job can not return anything as it expects it to be void?? Right?? probably not lol xD


    I am so sorry if a lot of this stuff seems trivial to a lot of you more experienced people, I am just finding it hard to find a lot of good documentation which explains stuff to people about the more basics of ECS, Sometimes RTFM does not work as a line of Syntax does not always provide a good insight into whats happening.

    I am sooo appreciative of all the people who put time and effort into replying and I hope that eventually all of these questions can be compiled into a Coherent thread so for people who have the same issues as myself can refer to it for help.

    So once again

    Thanks all!!
     
    Last edited: Nov 11, 2019
    siggigg likes this.
  44. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    For output data you need to use Native collections, which you initialize when you set up the job. Any other field modified inside the job would be reset to its initial value.

    Modified your example above, with some dirty untested code :)

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5. using Unity.Transforms;
    6. public class EnemyHealthSystem : JobComponentSystem
    7. {
    8.     private EndSimulationEntityCommandBufferSystem barrier;
    9.     protected override void OnCreate()
    10.     {
    11.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    12.     }
    13.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID, Translation>
    14.     {
    15.         public EntityCommandBuffer.Concurrent Ecb;
    16.    
    17.         [WriteOnly] public NativeList<float3> DeadEnemyPositions;
    18.         public NativeArray<Vector3> Execute(
    19.             Entity entity,
    20.             int index,
    21.             [ReadOnly] ref EnemyTagComponent enemyTag,
    22.             ref HealthComponent health,
    23.             ref EnemyID id,
    24.             ref Translation enemyPos)
    25.         {
    26.      
    27.             if(health.HP <= 0)
    28.             {
    29.                // Debug.Log("Enemy ID "+ id.id + " is dead.");
    30.                // Debug.Log("Entity index is " + index);
    31.                // Ecb.DestroyEntity(index, entity);
    32.                  DeadEnemyPositions.add(enemyPos.Value);
    33.                 //EnemyDeathSystem.PlayDeathAnimation(enemyPos.Value); /*<---- OUR LIST OF DEAD ENEMYS INSIDE OUR JOB
    34.             }
    35.         }
    36.    
    37.         return DeadEnemyPositions;///<-RETURNING THE LIST OF EITHER 0 OR POPULATED NativeArray.
    38.    
    39.     }
    40.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    41.     {
    42.         var job = new EnemyHealthJob
    43.         {
    44.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    45.             DeadEnemyPositions = new NativeList<Vector3>(8,
    46. Allocator.TempJob)
    47.             //health = GetComponentDataFromEntity<HealthComponent>()
    48.         };
    49.         inputDeps = job.Schedule(this, inputDeps);
    50.         barrier.AddJobHandleForProducer(inputDeps);
    51.  
    52.         // To get the result you either call .Complete() here on the job and read the array, or pass it on to another job that is dependant on this one
    53.         // NOT TESTED pseudo code below :)
    54.         inputDeps.Complete();
    55.         var deadEnemyPositions = job.DeadEnemyPositions;
    56.  
    57.         foreach(Vector3 pos in deadEnemyPositions )
    58.         {
    59.             EnemyDeathSystem.PlayDeathAnimation(pos);
    60.         }
    61.  
    62.         // And finally dispose the array
    63.         job.DeadEnemyPositions.Dispose();
    64.  
    65.         return inputDeps;
    66.     }
    67.  
    68.  
    69. }
     
  45. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thanks for the example,

    I tried to get it to work like so but I still receive a similar error to the one above about Execute returning void.

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5. using Unity.Transforms;
    6. using Unity.Mathematics;
    7. public class EnemyHealthSystem : JobComponentSystem
    8. {
    9.     private EndSimulationEntityCommandBufferSystem barrier;
    10.     protected override void OnCreate()
    11.     {
    12.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    13.     }
    14.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID, Translation>
    15.     {
    16.         public EntityCommandBuffer.Concurrent Ecb;
    17.  
    18.         [WriteOnly] public NativeList<float3> DeadEnemyPositions;
    19.         public NativeArray<Vector3> Execute(
    20.             Entity entity,
    21.             int index,
    22.             [ReadOnly] ref EnemyTagComponent enemyTag,
    23.             ref HealthComponent health,
    24.             ref EnemyID id,
    25.             ref Translation enemyPos)
    26.         {
    27.  
    28.             if(health.HP <= 0)
    29.             {
    30.                // Debug.Log("Enemy ID "+ id.id + " is dead.");
    31.                // Debug.Log("Entity index is " + index);
    32.                // Ecb.DestroyEntity(index, entity);
    33.                  DeadEnemyPositions.add(enemyPos.Value);
    34.                 //EnemyDeathSystem.PlayDeathAnimation(enemyPos.Value); /*<---- OUR LIST OF DEAD ENEMYS INSIDE OUR JOB*/
    35.             }
    36.                return DeadEnemyPositions;
    37.         }
    38.     }
    39.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    40.     {
    41.         var job = new EnemyHealthJob
    42.         {
    43.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    44.             DeadEnemyPositions = new NativeList<Vector3>(8,
    45. Allocator.TempJob)
    46.             //health = GetComponentDataFromEntity<HealthComponent>()
    47.         };
    48.         inputDeps = job.Schedule(this, inputDeps);
    49.         barrier.AddJobHandleForProducer(inputDeps);
    50.         // To get the result you either call .Complete() here on the job and read the array, or pass it on to another job that is dependant on this one
    51.         // NOT TESTED pseudo code below :)
    52.        // inputDeps.Complete();
    53.        //----------------
    54.         //CALLING job.Complete();
    55.        job.Complete();
    56.         //Reading the array.
    57.         var deadEnemyPositions = job.DeadEnemyPositions;
    58.         foreach(Vector3 pos in deadEnemyPositions )
    59.         {
    60.             EnemyDeathSystem.PlayDeathAnimation(pos);
    61.         }
    62.         // And finally dispose the array
    63.         job.DeadEnemyPositions.Dispose();
    64.         return inputDeps;
    65.     }
    66. }

    OK i got the code to a "runnable" state where it will run it with no errors but upon running I receive
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5. using Unity.Transforms;
    6. using Unity.Mathematics;
    7. public class EnemyHealthSystem : JobComponentSystem
    8. {
    9.     private EndSimulationEntityCommandBufferSystem barrier;
    10.     protected override void OnCreate()
    11.     {
    12.         barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    13.     }
    14.     private struct EnemyHealthJob : IJobForEachWithEntity<EnemyTagComponent, HealthComponent, EnemyID, Translation>
    15.     {
    16.         public EntityCommandBuffer.Concurrent Ecb;
    17.  
    18.         [WriteOnly] public NativeList<Vector3> DeadEnemyPositions;
    19.         public void Execute(
    20.             Entity entity,
    21.             int index,
    22.             [ReadOnly] ref EnemyTagComponent enemyTag,
    23.             ref HealthComponent health,
    24.             ref EnemyID id,
    25.             ref Translation enemyPos)
    26.         {
    27.    
    28.             if(health.HP <= 0)
    29.             {
    30.                // Debug.Log("Enemy ID "+ id.id + " is dead.");
    31.                // Debug.Log("Entity index is " + index);
    32.                // Ecb.DestroyEntity(index, entity);
    33.                  DeadEnemyPositions.Add(enemyPos.Value);
    34.                 //EnemyDeathSystem.PlayDeathAnimation(enemyPos.Value); /*<---- OUR LIST OF DEAD ENEMYS INSIDE OUR JOB*/
    35.             }
    36.               // return DeadEnemyPositions;
    37.         }
    38.     }
    39.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    40.     {
    41.         var job = new EnemyHealthJob
    42.         {
    43.             Ecb = barrier.CreateCommandBuffer().ToConcurrent(),
    44.             DeadEnemyPositions = new NativeList<Vector3>(8,
    45. Allocator.TempJob)
    46.             //health = GetComponentDataFromEntity<HealthComponent>()
    47.         };
    48.         inputDeps = job.Schedule(this, inputDeps);
    49.         barrier.AddJobHandleForProducer(inputDeps);
    50.         // To get the result you either call .Complete() here on the job and read the array, or pass it on to another job that is dependant on this one
    51.         // NOT TESTED pseudo code below :)
    52.         inputDeps.Complete();
    53.        //----------------
    54.         //CALLING job.Complete();
    55.        //job.Complete();
    56.         //Reading the array.
    57.         var deadEnemyPositions = job.DeadEnemyPositions;
    58.         foreach(Vector3 pos in deadEnemyPositions )
    59.         {
    60.             EnemyDeathSystem.PlayDeathAnimation(pos);
    61.         }
    62.         // And finally dispose the array
    63.         job.DeadEnemyPositions.Dispose();
    64.         return inputDeps;
    65.     }
    66. }


    I feel like such an idiot at the moment as this is probably quite trivial.... lol
     

    Attached Files:

    Last edited: Nov 11, 2019
  46. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    The Execute method should always return void, it's just changing the data in the array that is declared as a public field.

    If you need the parallel read it might make sense for you to split this into two different jobs or use a different job type.

    Alternatively you could use an Entities.ForEach here that runs on the main thread, it might make things a lot easier for you in this case.
     
  47. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Yes I considered using a Entities.ForEach, Just thought I would try to start to make use of Jobs also seems they are a bit harder than they first perceive... :)

    I think i,ll throw the towel in for now on Jobs until there is better documentation and more tutorials in the hope i can make sense of them a bit more. :)

    thanks for the replies!
     
  48. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    It's not relates on DOTS, it's basic C# programming things. You can't have field initializers in struct.
    Although the CLR allows it, C# does not allow structs to have a default constructor. The reason is that, for a value type, compilers by default neither generate a default constructor, nor do they generate a call to the default constructor. So, even if you happened to define a default constructor, it will not be called and that will only confuse you. To avoid such problems, the C# compiler disallows definition of a default constructor by the user. And because it doesn't generate a default constructor, you can't initialize fields when defining them. Structures implicitly derive from System.ValueType. The System.ValueType implements a default constructor that subclasses can not override. This means that it not physically possible to explicitly define a default constructor in a structure. Because of this physical limitation the compiler imposes, it is not possible for the compiler to take your field initialization code and put it in the default constructor.
    Sorry, but in most of your posts I see problems understanding the basic things of a programming language that are not related to DOTS by themselves, would it not be better to have a solid C # base for a start? It will be better for you. Otherwise, the threshold for joining DOTS increases significantly:(
     
    Last edited: Nov 11, 2019
  49. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Thank you for your reply,

    I find it hard to understand theory as a lot of the technical terms are used and are not quite explained what that means in laymens terms.

    For example :
    Although the CLR allows it, C# does not allow structs to have a niladic constructor. The reason is that, for a value type, compilers by default neither generate a default constructor, nor do they generate a call to the default constructor. So, even if you happened to define a default constructor, it will not be called and that will only confuse you. To avoid such problems, the C# compiler disallows definition of a default constructor by the user. And because it doesn't generate a default constructor, you can't initialize fields when defining them.

    These posts are really technical a bit like if i asked someone what is the north bridge on a computer who has never even set eyes on a computer they are likely to not know what I am talking about.

    But if i showed them and pointed to it and said this chip here is the north bridge and basically performs a lot of the more complex tasks they are more likely to understand that.

    I understand that learning the foundations of things is always better but sometimes there is nothing quite like "doing" as you encounter walls which you then overcome and learn from, The only difference being that you do not know why is was fixed.

    Another prime example is the driving theory test, In theory everyone can drive by passing the test yet people who pass can have a crash 2 weeks later, I have had my driving license 10 years and not 1 accident.

    **Off to research what a Niladic Constructor is....I'll be back lol

    If you have any links to good guides and or books for C# , I would appreciate those.

    Going to look for Dummies Guide to C# xD

    Thanks for all the insights and replies. :)
     
  50. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    It’s not for blaming you, just advice to you, for better and easier life :) Don’t get it close to heart