Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

How to instantiate entity and immediate change component in ScheduleParallel?

Discussion in 'Entity Component System' started by imaginadio, Apr 26, 2020.

  1. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    When i instantiate Entity from prefab - my Entity appears on the point 0,0,0 (that point setted by default Transform component). I want to instantiate my entity in random position, so i create EntityCommandBuffer and do that code inside SystemBase.ForEach(...).ScheduleParallel():
    Code (CSharp):
    1. Entity spawnedEntity = ecb.Instantiate(entityInQueryIndex, zombiePrefab.prefabEnity);
    2.                     ecb.AddComponent<Translation>(entityInQueryIndex, spawnedEntity, new Translation
    3.                     {
    4.                         Value = new float3(-10f, random.NextFloat(-5f, 5f), 0)
    5.                     });
    My entity spawns in point 0,0,0 for 1 frame and on the next frame appears on my random position.
    How to spawn entity in my random position without appears on position 0,0,0 and not loosing benefits of ScheduleParallel?
     
  2. apaxnid

    apaxnid

    Joined:
    Nov 18, 2012
    Posts:
    34
    Use EntityCommandBufferSystem that runs before TransformSystemGroup or apply LocalToWorld manually
     
  3. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    how to apply LocalToWorld manually?
    Here is my full code:
    Code (CSharp):
    1. public class ZombieSpawnerSystem : SystemBase
    2. {
    3.     private EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;
    4.     private uint seed = 1;
    5.     protected override void OnCreate()
    6.     {
    7.         base.OnCreate();
    8.         m_EndSimulationEcbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    9.     }
    10.     protected override void OnUpdate()
    11.     {
    12.         float dt = Time.DeltaTime;
    13.         var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer().ToConcurrent();
    14.         Random random = new Random(seed++);
    15.         Entities
    16.             .ForEach((Entity entity, int entityInQueryIndex, ref SpawnDelayComponent spawnDelay, in ZombiePrefabComponent zombiePrefab) =>
    17.             {
    18.                 spawnDelay.currentTime += dt;
    19.                 if (spawnDelay.currentTime > spawnDelay.delayTime)
    20.                 {
    21.                     spawnDelay.currentTime = 0f;
    22.  
    23.                     Entity spawnedEntity = ecb.Instantiate(entityInQueryIndex, zombiePrefab.prefabEnity);
    24.                     ecb.AddComponent<Translation>(entityInQueryIndex, spawnedEntity, new Translation
    25.                     {
    26.                         Value = new float3(-10f, random.NextFloat(-5f, 5f), 0)
    27.                     });
    28.                 }
    29.  
    30.             })
    31.             .ScheduleParallel();
    32.         m_EndSimulationEcbSystem.AddJobHandleForProducer(this.Dependency);
    33.     }
    34. }
     
  4. apaxnid

    apaxnid

    Joined:
    Nov 18, 2012
    Posts:
    34
    You can change your code like this for example, rotation part is optional obviously.
    Code (CSharp):
    1.  
    2. if (spawnDelay.currentTime > spawnDelay.delayTime)
    3. {
    4.     spawnDelay.currentTime = 0f;
    5.     Entity spawnedEntity = ecb.Instantiate(entityInQueryIndex, zombiePrefab.prefabEnity);
    6.    
    7.     var newPosition = new float3(-10f, random.NextFloat(-5f, 5f), 0);
    8.     ecb.AddComponent<Translation>(entityInQueryIndex, spawnedEntity, new Translation
    9.     {
    10.         Value = newPosition
    11.     });
    12.    
    13.     var newRotation = quaternion.identity;
    14.     ecb.AddComponent<Rotation>(entityInQueryIndex, spawnedEntity, new Rotation
    15.     {
    16.         Value = newRotation
    17.     });
    18.    
    19.     ecb.AddComponent(entityInQueryIndex,spawnedEntity, new LocalToWorld
    20.     {
    21.         Value = new float4x4(newRotation,newPosition)
    22.     });
    23. }
    24.  
     
    nicolasgramlich and imaginadio like this.
  5. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    That works great! Thank you!

    I have one more small question: why Unity.Mathematics.Random not so random? :)
    I initialize random that way:
    Code (CSharp):
    1. Unity.Mathematics.Random random = new Unity.Mathematics.Random(seed++);
    Where seed is int and it iterates every frame. When i call random.NextFloat(-5f, 5f) i get numbers like -4.9, -4.8, -4.7, -4.6, ... etc (not exactly that numbers, but close to them)
     
  6. apaxnid

    apaxnid

    Joined:
    Nov 18, 2012
    Posts:
    34
    i guess you don't have to create it every frame, just create it once and initialize it with some big number
    Code (CSharp):
    1. Unity.Mathematics.Random random = new Unity.Mathematics.Random();
    2. random.Init(seed++)
     
  7. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    I was trying to declare random as private variable of my SystemBase class, then initialize it OnCreate with big number, but when i trying get access to it inside Entities.ForEach() - get this error:
    Entities.ForEach Lambda expression uses field 'random'. Either assign the field to a local outside of the lambda expression and use that instead, or use .WithoutBurst() and .Run()
     
  8. apaxnid

    apaxnid

    Joined:
    Nov 18, 2012
    Posts:
    34
    Just as the error says, assign it to some local variable before ForEach, like var localRand = rand; and use localRand inside ForEach
     
  9. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    Sorry, still doesn't work.. I declare variable in SystemBase class, then initialize it OnCreate method, then create local variable "rand" in OnUpdate and giving it variable "random". It always give me same float 3.35354
    Code (CSharp):
    1. public class ZombieSpawnerSystem : SystemBase
    2. {
    3.     private EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;
    4.     Unity.Mathematics.Random random;
    5.     protected override void OnCreate()
    6.     {
    7.         base.OnCreate();
    8.         m_EndSimulationEcbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    9.         random = new Unity.Mathematics.Random(493824);
    10.     }
    11.  
    12.     protected override void OnUpdate()
    13.     {
    14.         var rand = random;
    15.         float dt = Time.DeltaTime;
    16.         var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer().ToConcurrent();
    17.         Entities
    18.             .ForEach((Entity entity, int entityInQueryIndex, ref SpawnDelayComponent spawnDelay, in ZombiePrefabComponent zombiePrefab) =>
    19.             {
    20.                 spawnDelay.currentTime += dt;
    21.                 if (spawnDelay.currentTime > spawnDelay.delayTime)
    22.                 {
    23.                     spawnDelay.currentTime = 0f;
    24.  
    25.                     Entity spawnedEntity = ecb.Instantiate(entityInQueryIndex, zombiePrefab.prefabEnity);
    26.                     float nextRandom = rand.NextFloat(-5f, 5f);
    27.                     var newPosition = new float3(-10f, nextRandom, 0);
    28.                     Debug.Log(nextRandom);
    29.                     ecb.AddComponent<Translation>(entityInQueryIndex, spawnedEntity, new Translation
    30.                     {
    31.                         Value = newPosition
    32.                     });
    33.  
    34.                     var newRotation = quaternion.identity;
    35.                     ecb.AddComponent<Rotation>(entityInQueryIndex, spawnedEntity, new Rotation
    36.                     {
    37.                         Value = newRotation
    38.                     });
    39.  
    40.                     ecb.AddComponent(entityInQueryIndex, spawnedEntity, new LocalToWorld
    41.                     {
    42.                         Value = new float4x4(newRotation, newPosition)
    43.                     });
    44.                 }
    45.             })
    46.             .Schedule();
    47.         m_EndSimulationEcbSystem.AddJobHandleForProducer(this.Dependency);
    48.     }
    49. }
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    Because it's struct and pass by value. After call NextXXX you should pass random back. You can use something like that and have predifined set of randoms per thread and use that static array inside your jobs.
    Code (CSharp):
    1. /// <summary>
    2.     /// Utility system which populates and handle disposing for game randoms
    3.     /// </summary>
    4.     [UpdateInGroup(typeof(InitializationSystemGroup))]
    5.     public class RandomsUtilitySystem: SystemBase
    6.     {
    7.         /// <summary>
    8.         /// Array of randoms
    9.         /// </summary>
    10.         /// <remarks>
    11.         /// Use [NativeDisableParallelForRestriction] attribute for this array in your jobs for write back<br/>
    12.         /// Use [NativeSetThreadIndex] _nativeThreadIndex as indexer for that array
    13.         /// </remarks>
    14.         public static NativeArray<Unity.Mathematics.Random> Randoms;
    15.  
    16.         [ExcludeFromDocs]
    17.         protected override void OnCreate()
    18.         {
    19.             Randoms = new NativeArray<Unity.Mathematics.Random>(JobsUtility.MaxJobThreadCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
    20.             var r = (uint) UnityEngine.Random.Range(int.MinValue, int.MaxValue);
    21.             for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++)
    22.                 Randoms[i] = new Unity.Mathematics.Random(r == 0 ? r + 1 : r);
    23.             Enabled = false;
    24.         }
    25.      
    26.         [ExcludeFromDocs]
    27.         protected override void OnDestroy()
    28.         {
    29.             if (Randoms.IsCreated)
    30.                 Randoms.Dispose();
    31.         }
    32.      
    33.         [ExcludeFromDocs]
    34.         protected override void OnUpdate() {}
    35.     }
    Usage
    Code (CSharp):
    1. private struct SomeJob : IJobParallelFor
    2.         {
    3.             [NativeDisableParallelForRestriction] public  NativeArray<Random> Randoms;
    4.             [NativeSetThreadIndex]                private int                 _nativeThreadIndex;
    5.  
    6.             public void Execute(int                         index)
    7.             {
    8.                 var random = Randoms[_nativeThreadIndex];
    9.              
    10.                 //... Do stuff like random.NextInt(0, 100);
    11.              
    12.                 // return back
    13.                 Randoms[_nativeThreadIndex] = random;
    14.             }
    15.         }
     
    florianhanke and imaginadio like this.
  11. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    Thank you. I see your post in another thread about random, but when i see "NativeDisableContainerSafetyRestriction" - i begin reading about it and im scared :)
    Documentation tells:
    WithNativeDisableContainerSafetyRestriction(myvar) — disables normal safety restrictions that prevent dangerous access to the native container. Disabling safety restrictions unwisely can lead to race conditions, subtle bugs, and crashes in your application.

    Now i understand that is only one thread read and write only 1 element of native erray at same time because of [NativeSetThreadIndex], and this operation is safe.
     
  12. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    Yes
     
  13. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    53
    I can use RandomsUtilitySystem.Random only with IJobParallelFor or i can i use it in regular Entities.ForEach().ScheduleParallel() ?

    I can throw "[NativeDisableParallelForRestriction] public NativeArray<Random> Randoms;" using WithNativeDisableParallelForRestriction(myvar). Is there any way to throw "[NativeSetThreadIndex] private int _nativeThreadIndex;" into Entities.ForEach of SystemBase?
     
  14. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    https://docs.unity3d.com/Packages/c...ntities_foreach.html#special-named-parameters
     
    imaginadio likes this.