Search Unity

A bug or feature? PostUpdateCommands

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

  1. Emre_U

    Emre_U

    Joined:
    Jan 27, 2015
    Posts:
    49
    I am experimenting with ECS for a few days now.The code is below, PostUpdateCommands is declared and used in an "if" clause. Yet it works for every entity with ballsGroup as determined in struct when it is in an "if" condition. Even though PostUpdateCommands require a single entity as Args?

    What I am trying to achieve buffer a command to kill entities at the end of frame to not to frak with struct and its for loop.

    What is happening, even though there is a condition if ( dist < 1)) to buffer those PostUpdateCommands automatically all entities of BallsGroup die instantly (probably at the end of first frame?)

    Probably I am not correctly understanding what is PostUpdateCommand is doing.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Transforms;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Collections;
    6. using Unity.Mathematics;
    7.  
    8. public class EraseBallsSystem : ComponentSystem
    9. {
    10.     public struct PlayerGroup
    11.     {
    12.         public ComponentDataArray<Position> playerPos;
    13.         public ComponentDataArray<Cube> cube;
    14.         public int Length;
    15.     }
    16.  
    17.     public struct BallsGroup
    18.     {
    19.         public ComponentDataArray<Ball> ball;
    20.         public ComponentDataArray<Position> ballPos;
    21.         public EntityArray entityArray;      
    22.         public int Length;
    23.     }
    24.  
    25.     [Inject] PlayerGroup playerGroup;
    26.     [Inject] BallsGroup ballsGroup;
    27.  
    28.  
    29.  
    30.     protected override void OnUpdate()
    31.     {
    32.        
    33.  
    34.         for (int i = 0; i < ballsGroup.Length; i++)
    35.         {
    36. //playerGroup has only 1 entity so here I cheat with [0]
    37.             float dist = math.distance(playerGroup.playerPos[0].Value, ballsGroup.ballPos[i].Value);
    38.  
    39. // Problem is not on if condition being always true, other code with pos working as expected when
    40. // puc is commented.
    41.             if ( dist < 1)
    42.             {
    43. // First 3 lines working as expected with if, when below puc is commented
    44.                 var newPos = ballsGroup.ballPos[i];
    45.                 newPos.Value = new float3(newPos.Value.x ,3-dist,newPos.Value.z);
    46.                 ballsGroup.ballPos[i] = newPos;
    47.                
    48. // Somehow this executes for every entity even though, for loop shouldn't
    49. // enter here without passing from if, when below code is commented, top one
    50. // is working as expected.
    51.                 var puc = PostUpdateCommands;
    52.                 puc.DestroyEntity(ballsGroup.entityArray[i]);
    53.  
    54.             }
    55.         }
    56.        
    57.     }
    58. }
     
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Well there is no real doc about PostUpdateCommand
    I beleave it will be called after a system has been completed and not at the end of the frame.

    I use the following code instead

    Code (CSharp):
    1. [Inject] EndFrameBarrier endFrameBarrier;
    2.  
    3. protected override void OnUpdate() {
    4.         EntityCommandBuffer commandBuffer = endFrameBarrier.CreateCommandBuffer();
    5.  
    6.        // Do what ever you wanna do...
    7.        // The buffer will than be executed by the EndFrameBarrier itself
    8. }
     
    Emre_U likes this.
  3. Emre_U

    Emre_U

    Joined:
    Jan 27, 2015
    Posts:
    49
    Yeah, I will try your code, you are great Spy-Shifty. But even then my code shouldn't be called after the system for all of the entities while it is still inside "if" I am thinking it might be more of a bug.
     
  4. Emre_U

    Emre_U

    Joined:
    Jan 27, 2015
    Posts:
    49
    Ok, looks like everything is working as expected with PostUpdateCommand and it was my silly mistake. It happened because I was not familiar with the ECS system yet.

    I kinda missed a race(? if I can say so) condition in my code, there are 2 systems, 1 is positioning cubes into places, other is looking for the distance from cubes. Even though my if is checking about distance, looks like it is always checking even before the entities are set apart, resulting distance being 0 for the first frame and tagging everything "correctly" but unexpectedly. So every entity is tagged even though I was thinking they should not be tagged.

    Code (CSharp):
    1. //[Inject] EndFrameBarrier endFrameBarrier;
    2.     protected override void OnUpdate()
    3.     {
    4.         //EntityCommandBuffer commandBuffer = endFrameBarrier.CreateCommandBuffer();
    5.        
    6.  
    7.         for (int i = 0; i < ballsGroup.Length; i++)
    8.         {
    9.             float dist = math.distance(playerGroup.playerPos[0].Value, ballsGroup.ballPos[i].Value);
    10.             //!= 0 is required as this system actually checks distances even before I set them apart in the first frame
    11.             // in another system.
    12.             if ( dist < 3 & dist != 0)
    13.             {
    14.                 var newPos = ballsGroup.ballPos[i];
    15.                 newPos.Value = new float3(newPos.Value.x, 3 - dist, newPos.Value.z);
    16.                 ballsGroup.ballPos[i] = newPos;
    17.                 // this baby is working perfect. Spy-Shifty's endBarrier is working perfect too. PostUpdatecommands
    18.                 // might be a great shortcut, as it is 2 lines of less code.
    19.                 PostUpdateCommands.AddComponent<Die>(ballsGroup.entityArray[i], new Die());
    20.  
    21.                 //commandBuffer.AddComponent<Die>(ballsGroup.entityArray[i], new Die());
    22.             }
    23.  
    24.          
    25.         }
    26.        
    27.      
    28.     }
    So Thank you again @Spy-Shifty , even you couldn't save me from my own stupidity. But I think I am starting to understand this ECS thing after a week :)
     
  5. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    There might be other solutions to your "problem", two come to mind immediately:
    - you can set an order in which the systems are run (there is a thread here, explaining this)
    - you could set a tag component for initialized entities or use an existing component as a tag (i.e. ballpos component is attached by your init system at the time you set the value) - this way the distance checking system only operates once the values are set
     
    Emre_U likes this.
  6. Obikson

    Obikson

    Joined:
    Sep 21, 2017
    Posts:
    8
    Please, where I could find any docs about BarrierSystem which is @Spy-Shifty using?
     
  7. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    The only doc about that is here : https://github.com/Unity-Technologi.../content/ecs_in_detail.md#entitycommandbuffer

    I could explain broadly to you : a barrier is also a system. It will run at some point in the frame like other systems so you still need to tell them UpdateBefore/Afters. But it has no OnUpdate, so nothing will happen when it runs.

    You have an ability to inject any system, it is not specific to barrier system.

    When you inject a barrier system you can make an EntityCommandBuffer from it. With ECB you can queue up a command so that when it is time that the barrier runs it will use its EntityManager to execute those queued commands one by one. Some tricks you can do with ECB is that they can go into the job, you can destroy entity later in a loop so the loop does not get screwed up immediately, you can even write things to CDA that you marked [ReadOnly] because it is done later, this could enable more concurrency for you.

    So a barrier will have this invisible OnUpdate only if it received some commands from its ECB. You can make many ECB from one barrier and distribute them to multiple systems, then run all the command at once.

    Note that if the barrier system has already activated and you inject+make ECB+queue command then you need to wait for the next frame for those commands to run.

    EndFrameBarrier is just a subclass of BarrierSystem which has [UpdateBefore(typeof(Initialization))] pasted. So you don't have to do it yourself. By that attribute It would run the first thing in the next frame to execute all of your accumulated commands in the previous frame, meaning that "End frame" is not literally correct (rather at the start of the next frame) but by behaviour it is the same and you can think that it runs at the end of your frame.
     
  8. Obikson

    Obikson

    Joined:
    Sep 21, 2017
    Posts:
    8
    Nice and simple, thanks. As I see it now, the Barrier System is asociated mainly with ECB and will be described more in future. I will go through the linked docs you have provided.