Search Unity

[UpdateAfter] [UpdateInGroup] [UpdateBefor] forces a system to update if there is no injection

Discussion in 'Entity Component System' started by Deleted User, Sep 16, 2018.

  1. Deleted User

    Deleted User

    Guest

    Hi there.

    Without any order tags my system doesn't call OnUpdate(), because there is no injection happened. However, if I put my system in the order, it will call OnUpdate() in this group, no matter if injection happened or not.

    As far as I know, it supposed to be disabled unless injection happened. The reason I want in to keep being disabled, is a waste call of OnUpdate. It should not be called if there is a SetFilter

    I think it is somehow connected with packetPool.SetFilter(new NetOpcodeFilter { Value = Opcode.OP_NULL_ACTION });

    The system updates based on GetComponentGroup(typeof(NetOpcodeFilter), typeof(NetReceivedPacket)), but ignores the filter rule.

    [
    Code (CSharp):
    1.  
    2. [UpdateInGroup(typeof(NetUpdate))]
    3. public class TestMessageHandler1 : ComponentSystem
    4. {
    5.     private ComponentGroup packetPool;
    6.  
    7.     protected override void OnCreateManager()
    8.     {
    9.         packetPool = GetComponentGroup(typeof(NetOpcodeFilter), typeof(NetReceivedPacket));
    10.         packetPool.SetFilter(new NetOpcodeFilter { Value = Opcode.OP_NULL_ACTION });
    11.     }
    12.  
    13.     protected override void OnUpdate()
    14.     {
    15.         System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
    16.         int length = packetPool.CalculateLength(); // wasted call,
    17.         // OnUpdate should not be called at all because there is no injection hapenned
    18.  
    19.         stopwatch.Stop();
    20.         Debug.Log(string.Format("'{0}'", stopwatch.Elapsed.TotalMilliseconds));
    21.     }
    22. }


    Stopwatch debug



    So the more systems I will have the more overhead will be

     
    Last edited by a moderator: Sep 16, 2018
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Maybe you can split up NetOpcodeFilter into different tag components and filter directly by that tag.
     
    Deleted User likes this.
  3. Deleted User

    Deleted User

    Guest

    I think it might work. Add some group of Opcodes, so handlers would work only with tags like BasicOpcodes, WorldOpcodes etc.

    Thank you.
     
  4. dstilwagen

    dstilwagen

    Joined:
    Mar 14, 2018
    Posts:
    36
    I believe that GetComponentGroup is treated the same way as injection since it stores and is maintained by the same injection system. I don't know why it runs/stops running when you add/remove the system order attributes. It might have something to do with using SetFilter in OnCreateManager you should only really use SetFilter after the injections update so ideally at the start of OnUpdate. Also Check out the two posts by Joachim_Ante in this thread here. Only run a system when all of its [Inject] are satisfied? I think this answers how to only run OnUpdate when you need multiple injected groups to have entities for the system to run. So I think you would use SetFilter and then do the check Joachim_Ante recommends in the post above.
     
  5. Deleted User

    Deleted User

    Guest

    Thank you for your reply.
    Here is my workaround. The system will work only if the pool's length is greater than zero; which means that we have filtered data. No need to SetFilter every frame and use [AlwaysUpdateSystem] to do this kind of check. The execution time of this check is minimum: 0.0022ms. So let's say. 100 handler systems will block the main thread only for 0.22ms. But I will have around 5-10 systems that would work with network messages. The first call is a "warm-up" call. That's why I had 0.1766ms which is quite a lot for a single system. But after that, future calls is only 0.0022 milliseconds.

    Code (CSharp):
    1. [
    2.  
    3. [UpdateInGroup(typeof(NetUpdate))]
    4. public class TestMessageHandler : NetComponentSystem
    5. {
    6.     private ComponentGroup packetPool;
    7.  
    8.     protected override void OnCreateManager()
    9.     {
    10.         packetPool = GetComponentGroup(typeof(NetOpcodeFilter), typeof(NetReceivedPacket), typeof(ByteBuffer));
    11.         packetPool.SetFilter(new NetOpcodeFilter { Value = Opcode.SOP_DATA });
    12.     }
    13.  
    14.     protected override void OnUpdate()
    15.     {
    16.         System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
    17.  
    18.         if (packetPool.CalculateLength() > 0)
    19.         {
    20.            // If we have injected components that satisfy Opcode.SOP_DATA
    21.         }
    22.  
    23.         stopwatch.Stop();
    24.         Debug.Log(string.Format("'Filter check execution time: {0} ms' ", stopwatch.Elapsed.TotalMilliseconds));
    25.     }
    26. }
    27.  
     
    Antypodish likes this.
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    @wobes thx fro referencing this post in my thread
    https://forum.unity.com/threads/two...stem-execution-condition.556021/#post-3687148

    I will continue here, with a question.
    The main difference between first post code, and your latest, is the length check in if condition.
    Code (CSharp):
    1. if (packetPool.CalculateLength() > 0)
    That makes sense.
    But is that it, or I am missing anything else critically different?

    I mean I see your group components has changed for example, but ByteBuffer in your case is the project specific. Hence, I suppose is irrelevant to the problem itself.

    So understanding that setting filter at OnCreateManager is required?

    It looks like it is best what we can have atm.

    Just a thought.
    Putting in if condition return, instead of actual code execution and then after if loop, put the code as required.
    That makes early exist, as @Joachim_Ante suggested in hist post.
    That could be cleaner, I think?
     
  7. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    In most cases it's better to use method guards (hope this is the correct translation :D )
    Code (CSharp):
    1. if(not some condition ) {
    2.     return;
    3. }
    instead of
    Code (CSharp):
    1. if(some condition ) {
    2.     do some stuff
    3. }
    The focus is then more on the important execution code then on the condition...
    And it's easier to read.
     
  8. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    `SetFilter` works after an injection. By using `GetComponentGroup` the first time you have permanently added an archetype {NetOpcodeFilter, NetReceivedPacket, ByteBuffer} to the system's injection list. Then on before each Update(), all entities matching this archetype will be injected (should cost little time on real device), then the filter will run through all entities to find if there are any matches. So your filter cannot prevent an injection, but can hide a data in the component group.

    If you have many entities with NetOpcodeFilter for example, the time it takes to update the system will be slower the more entities you have. (Because the filter is basically a for loop, "ForEachFilter" as in one method of the API) Compared to if you create a special tag component OpCodeNullAction and put that as a subtractive component of your component group, that will cause an archetype mismatch in the first place and the entity will not be injected at all.

    ps. I have some more information about the inject cost with filters here https://gametorrahod.com/unity-ecs-the-cost-of-inject-a000bab7cf99
     
    deus0, Deleted User and Antypodish like this.
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Thx that is nice info to digest ;)
     
  10. Deleted User

    Deleted User

    Guest

    What if it is shared component
    Could you suggest any method to generate a IComponentData for each enum value in NetOpcodeFilter instead of creating every single tag by hand?
     
  11. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    ISharedComponentData's strong point is this "categorize by value" property since it hash all of its values and group the entity with the same hash together in one chunk. But injection works on Archetype level, not chunk level. SharedComponentData with any value is considered the same archetype and so you cannot filter by value at injection.

    I guess the new chunk iteration can solve this problem since the thing you want will be in the same chunk, but I haven't get to try it yet. For sure you will have more control since it is not related to the injection system.

    In my game I am also doing every single tag by hand for enum-like value. It is only inconvenience when defining but after that it goes well with everything that is based on Archetype.

    In my opinion a good candidate to use SharedComponentData is not to replicate enum (For example NetOpcodeFilter is not exactly related to the value 0) but to replicate something related to integer. For example I have entities of all racers in the track. I could have Lap : ISharedComponentData with values 0,1,2,3.. and so on. Then I can filter to ask how many racers at lap 2, and also possible to filter in range since it is an integer. (If the final lap is n then you could filter n-2 ~ n to see how many racers are about to finish the race) Other use is for storing reference value.
     
    Deleted User likes this.