Search Unity

To make it clear... Do I have a Start function on ECS?

Discussion in 'Entity Component System' started by Emre_U, Mar 28, 2018.

  1. Emre_U

    Emre_U

    Joined:
    Jan 27, 2015
    Posts:
    49
    Ok we have a protected override void OnUpdate which is Update but to make things once should I really create a new system and set a boolean to solve my problem only once as in the code, or should I really create lots of different systems iterating onUpdate where I check with bools to execute things once in a while? Enemy spawn system in Pure example spawn stuff this way for example.

    But I realized even this below system is consuming 0.01ms (I know I know it is like nothing but it is actually doing nothing after the first frame but still consumes resources) and it may add up.

    1- So do I have a Start function on ECS?

    2- the Same problem for jobs too. Can I create a job to do things only once? and never schedule it again.

    3- Also another question: How may I inject an array of random numbers into a job system. Apparently, I can't send in an array (Or I couldn't manage it) where I have stored lots of random numbers in it.



    Code (CSharp):
    1.     class RandomStartingPositions : ComponentSystem
    2. {
    3.     struct PositionGroup
    4.     {
    5.         public int Length;
    6.         public ComponentDataArray<Position> Positions;
    7.     }
    8.  
    9.     [Inject] private PositionGroup m_Group;
    10.  
    11.     bool isFinished = false;
    12.  
    13.  
    14.     protected override void OnUpdate()
    15.     {
    16.        
    17.         float spreader = 5;
    18.  
    19.         if (!isFinished)
    20.         {
    21.             for (int index = 0; index < m_Group.Length; index++)
    22.             {
    23.                 var copy = m_Group.Positions[index];
    24.                 copy.Value = new Vector3(Random.value * spreader, Random.value * spreader, Random.value * spreader);
    25.                 m_Group.Positions[index] = copy;
    26.  
    27.                 if ((index + 1) == m_Group.Length )
    28.                 {
    29.                     isFinished = true;
    30.                 }
    31.             }
    32.         }
    33.     }
     
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    If you want to initalize some entities with values like the Start / Awake funktion of Monobehaviour:
    Well yes create an initsystem for that. But instead of trigger a boolean use a filter component!

    In this case I used InitWithRandomPosition to activate the system. After I handled the entity I remove InitWithRandomPosition from it.

    Code (CSharp):
    1.  
    2.  
    3. class RandomStartingPositionSystem : JobComponentSystem {
    4.     struct PositionGroup {
    5.         public int Length;
    6.         public ComponentDataArray<Position> Positions;
    7.         public ComponentDataArray<InitWithRandomPosition> RequiredInit; // just added to the group as filter
    8.         public EntityArray Entities;
    9.     }
    10.  
    11.     [Inject] private PositionGroup m_Group;
    12.  
    13.     struct SetRandomPositionJob : IJobParallelFor {
    14.         public ComponentDataArray<Position> positions;
    15.         [ReadOnly]public EntityArray entities;
    16.         public NativeArray<float3> randomPositions;
    17.         [NativeDisableParallelForRestriction]public EntityCommandBuffer commandBuffer;
    18.  
    19.         public void Execute(int index) {
    20.             positions[index] = new Position { Value = randomPositions[index] };
    21.             // Removes the InitWithRandomPosition at the end of frame
    22.             commandBuffer.RemoveComponent<InitWithRandomPosition>(entities[index]);
    23.         }
    24.     }
    25.  
    26.     [Inject] EndFrameBarrier endFrameBarrier;
    27.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    28.         float spreader = 5;
    29.         NativeArray<float3> randomPositions = new NativeArray<float3>(m_Group.Length, Allocator.Temp);
    30.         for (int i = 0; i < m_Group.Length; i++) {
    31.             randomPositions[i] = new float3(Random.value * spreader, Random.value * spreader, Random.value * spreader);
    32.         }
    33.  
    34.         var setRandomPositionJob = new SetRandomPositionJob {
    35.             positions = m_Group.Positions,
    36.             entities = m_Group.Entities,
    37.             randomPositions = randomPositions,
    38.             commandBuffer = endFrameBarrier.CreateCommandBuffer()
    39.         };
    40.         var setRandomPositionJobFence = setRandomPositionJob.Schedule(m_Group.Length, 1, inputDeps);
    41.         setRandomPositionJobFence.Complete();
    42.         randomPositions.Dispose();
    43.         return inputDeps;
    44.     }
    45. }
    46.  
    Usinge this:
    Code (CSharp):
    1. protected override void OnCreateManager(int capacity){ }
    Might not be a good Idee, because you can only init you entities once in the entire game...

    And it dependend on the order of system execution. Maybe your spawnsystem was called after your RandomStartingPositionSystem... Nothing happens
     
    Last edited: Mar 28, 2018
    Singtaa, RichardFine and Emre_U like this.
  3. Emre_U

    Emre_U

    Joined:
    Jan 27, 2015
    Posts:
    49
    Thank you for a thorough answer and it looks like I have to learn and surgically study whatever magic you have written on top. But this part did not compile for me yet I make it worked by adding "new" before SetRandomPositionJob and a comma after randomPositions ;)

    Code (CSharp):
    1.  
    2. // Added new before SetRandomPositionJob and there was a missing coma
    3. var setRandomPositionJob =  new SetRandomPositionJob {
    4.             positions = m_Group.Positions,
    5.             entities = m_Group.Entities,
    6.             randomPositions = randomPositions,
    7.             endFrameBarrier = endFrameBarrier,
    8.              };
    Though even then I have got lots of warnings and red flags;

    Code (CSharp):
    1. InvalidOperationException: SetRandomPositionJob.endFrameBarrier is not a value type. Job structs may not contain any reference types.
    2. Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData (System.Type type, Unity.Jobs.LowLevel.Unsafe.JobType jobType, System.Object managedJobFunction0, System.Object managedJobFunction1, System.Object managedJobFunction2) (at C:/buildslave/unity/build/Runtime/Jobs/ScriptBindings/Jobs.bindings.cs:96)
    3. Unity.Jobs.IJobParallelForExtensions+ParallelForJobStruct`1[T].Initialize () (at C:/buildslave/unity/build/Runtime/Jobs/Managed/IJobParallelFor.cs:23)
    4. Unity.Jobs.IJobParallelForExtensions.Schedule[T] (T jobData, System.Int32 arrayLength, System.Int32 innerloopBatchCount, Unity.Jobs.JobHandle dependsOn) (at C:/buildslave/unity/build/Runtime/Jobs/Managed/IJobParallelFor.cs:50)
    5. RandomStartingPositionSystem.OnUpdate (Unity.Jobs.JobHandle inputDeps) (at Assets/EmreFolder/Code/RandomStartingPos.cs:60)
    6. Unity.Entities.JobComponentSystem.InternalUpdate () (at C:/ProgramData/Unity/cache/packages/staging-packages.unity.com/com.unity.entities@0.0.11/Unity.Entities/ComponentSystem.cs:353)
    7. Unity.Entities.ScriptBehaviourManager.Update () (at C:/ProgramData/Unity/cache/packages/staging-packages.unity.com/com.unity.entities@0.0.11/Unity.Entities/ScriptBehaviourManager.cs:82)
    8. Unity.Entities.ScriptBehaviourUpdateOrder+DummyDelagateWrapper.TriggerUpdate () (at C:/ProgramData/Unity/cache/packages/staging-packages.unity.com/com.unity.entities@0.0.11/Unity.Entities/ScriptBehaviourUpdateOrder.cs:59)
    9.  
    10. A Native Collection has not been disposed, resulting in a memory leak. It was allocated at C:\Users\emre\Desktop\ECS Examples\TwoStickShooter\Pure\Assets\EmreFolder\Code\RandomStartingPos.cs:46.
    11.  
    12. Internal: JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak
    13.  
     
    Last edited: Mar 28, 2018
  4. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Thank you :)

    I wrote it straight in here. I also fixed it in my post.

    Well, If you need further info about the code, let me know

    EDIT:

    Sorry I have to test it first :(

    Ok now I fixed the issues

    Well you need to use the command buffer directly, not the endFrameBarrier...
    Just create a new command buffer from the endFrameBarrier with CreateCommandBuffer()
    This will buffer all changes to an entity unitl endFrameBarrier as been executed. This is to prevent to invalidate all systems and jobs, because of invalidating EntityManager

    I also added [NativeDisableParallelForRestriction] to access the commandbuffer in parallel and ReadOnly to the EntityArray...
     
    Last edited: Mar 28, 2018
    Emre_U likes this.
  5. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    It's great spy-shifty is participating here, I learned about ECS using his framework for a few days. He is very knowledgeable with this.

    I will also take a look at what he posted, it's way more sophisticated than what I would have come up with.

    But the part that directly concerns your question is actually straight forward.
    - you add a tag (component) to your entity to be initialized (initwithrandomposition)
    - your init system requires this component to process entities
    - once it's done (in update), you remove this component (initwithrandomposition) in post update
    - after this the system will not process the entity since it will not pass the filter without the initwithrandomposition component. If you attach it again, it will be initialized again
     
    Spy-Shifty and Emre_U like this.
  6. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    The system also has a reference to the entiymanager
    just use the property EntityManager ;)

    use the commandBuffer because of performance reasons
    Each time you execute a command from the EntityManager directly you cause to invalidate all your data

    With the EntityCommandBuffer you collect all you commands and execut them all at once.
    By using a commandbuffer from the EndFrameBarrier (which is a system that is executed at the end of the frame) you can collect all your changes at one single point in time and all your changes to all the entities will happend at this point. So this is a real performance gain if you have thousands of entities...
     
    lianaqiang, recursive and FROS7 like this.