Search Unity

Valid/ok to call other ComponentSystems from within a ComponentSystem

Discussion in 'Entity Component System' started by Malmer, Apr 10, 2018.

  1. Malmer

    Malmer

    Joined:
    Nov 10, 2013
    Posts:
    18
    Consider the following dummy code:

    Code (CSharp):
    1.  
    2. public class FooSystem : ComponentSystem
    3. {
    4.     protected struct Data
    5.     {
    6.         public int Length;
    7.         [ReadOnly] public ComponentDataArray<Bar> FooBar;
    8.     }
    9.  
    10.     [Inject] protected Data data;
    11.     public int TheValue;
    12.  
    13.     public void DoStuff()
    14.     {
    15.         UpdateInjectedComponentGroups();
    16.         TheValue = 0;
    17.         for (int i = 0; i < data.Length; ++i)
    18.             TheValue += data.FooBar[i].Value;
    19.     }
    20.  
    21.     protected override void OnUpdate()
    22.     {
    23.           // Nothing here
    24.     }
    25. }
    26.  
    27.  
    28. public class BarSystem : ComponentSystem
    29. {
    30.     [Inject] private FooSystem fooSystem;
    31.  
    32.     protected override void OnUpdate()
    33.     {
    34.         for(...)
    35.         {
    36.             (Create a Bar entity)
    37.             fooSystem.DoStuff();
    38.             (Do something with fooSystem.TheValue)
    39.         }
    40.     }
    41. }
    42.  
    Is it ok for BarSystem to call into FooSystem.DoStuff() like that? Will UpdateInjectedComponentGroups() in FooSystem work? Considering the case that there may be other systems like BarSystem that need FooSystem to do it's processing all over again. OnUpdate of FooSystem will only run once, but can I call DoStuff() several times and expect the processing to include the newly created Bar item.

    The alternative solution would obviously be to just let BarSystem inherit from FooSystem and make FooSystem abstract. I'm just curious if this is ok and what the potential problems with this approach could be.

    (Yes I realize that this whole code concept is a bit odd, and probably shouldn't be done in the first place. But I'm curious)
     
    Last edited: Apr 10, 2018
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Really interested in this as well. I have a use case where I more or less need to have:

    Code (CSharp):
    1. MySystemA.UpdatePhase1();
    2. MySystemB.UpdatePhase1();
    3. MySystemA.UpdatePhase2();
    4. MySystemB.UpdatePhase2();
    5. MySystemA.UpdatePhase3();
    (notice that it's not just a question of which system executes after which one. There are several interlaced updates for each system)

    ...and it is crucial that it all happens in that specific order, so I was wondering if it would be an acceptable practice to just make a general "ExecutionOrderSystem" that overrides OnUpdate() and simply manually calls functions on all systems in the game in the correct order. Would this break any Burst/ECS/JobSystem optimizations?

    I very much prefer to have my execution order very explicitly laid out like this, instead on relying on [UpdateBeforeX] attributes or that sort of stuff. I feel like it's much easier to work with and to see what's really happening

    _________________________________

    Here's another scenario:

    I need some systems to run on a sort of FixedUpdate(). Perhaps even different kinds of FixedUpdates that run at different frequencies. To accomplish this, I would like to again make some kind of "ExecutionOrderSystem" that detects if it's time for a fixed update in its OnUpdate(), and then manually calls functions on all the necessary systems in the game. I really don't like the idea of having each system individually detect when it's time for a fixedUpdate on their own
     
    Last edited: Apr 10, 2018
  3. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Actually I just noticed that the thing I'm suggesting won't work, because for example JobComponentSystems need a special OnUpdate with their dependencies. I don't think I can just write a custom Update() function for them

    In my case with several update phases per system, I guess I won't have a choice but to make one individual system per phase. But in the case of the FixedUpdate, I still don't know how this would work

    Still I think it would be really great to be able to lay out the execution order and update frequencies of all systems very explicitly in code, instead of using attributes. I don't know if this is possible currently
     
    Last edited: Apr 10, 2018
  4. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Well I think it's ok. The separation of data and logic is still given.

    As you can see in the TwoStickShooter Demo, they also have a system that is called by an other system.

    But you should not exaggerate it ;)
     
  5. Malmer

    Malmer

    Joined:
    Nov 10, 2013
    Posts:
    18
    Where in the twostickshooter demo does it do this?
     
  6. Dan2_lud

    Dan2_lud

    Joined:
    Apr 11, 2018
    Posts:
    1
    The TwoStickShooter does this in the ShotSpawnSystem, but ShotSpawnSystem is not a system per say, just a class with one static function to spawn the shot, it does not inherit from SystemComponent.
     
  7. mike_acton

    mike_acton

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    110
    Code (CSharp):
    1. MySystemA.UpdatePhase1();
    2. MySystemB.UpdatePhase1();
    3. MySystemA.UpdatePhase2();
    4. MySystemB.UpdatePhase2();
    5. MySystemA.UpdatePhase3();
    It's not clear to me why these wouldn't just be separate systems.
     
  8. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    yeah, they totally can be separate systems. I've had more time to familiarize myself with the ECS since yesterday and I've decided I'll go with the one-system-per-update approach
     
  9. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    We did have a few demos initially where we relied on injections from different systems in somewhat similar ways to the original post. One huge drawback of injecting other systems and using their component injection is that our ECS implementation no longer has all the information about what components you are accessing, which means it cannot give you correct dependencies when you start jobifying your code.

    When you loose the dependency information and no longer know which systems access what components your codebase quickly become impossible to optimize, so I would strongly recommend against using such a design.

    I know it is not always easy and you often have to rethink your problem, but you should always strive to make sure all systems define all data it is going to access upfront (by using injection / component groups directly) and that all data communicated between systems is stored in components which are using that injection. We have a few examples now where we even use empty dummy components to track access to static managed data shared between systems just to get the dependencies right.
     
  10. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I understand now why calling a system's update from another system isn't a good idea. However, I still think there is a very strong need for some kind of ability to trigger updates manually, for reasons I've tried to explain here and here. I'm having trouble understanding how these two things could be accomplished without that ability, or without putting half of all of your game's code in one single system

    Here's what I'm suggesting to solve the issue:
    • A World would have a "SystemUpdateBehaviour" whose job is to call Update() on SystemsGroups
    • A SystemsGroup would be a collection of systems that are updated together and arranged for proper Job management, in the order defined by the [UpdateBefore/After] attributes. Basically, the systems of a SystemsGroup would be updated the same way Systems are updated currently
    • By default, a World's SystemUpdateBehaviour calls Update() on one single SystemsGroup, which contains all of the systems in that world. But we would have the ability to override this SystemUpdateBehaviour with our own
    • All jobs from one SystemsGroup must be guaranteed to have finished before we call Update on the next SystemsGroup
    • That way, we could achieve something like this:
    Code (CSharp):
    1. public class MyUpdateBehaviour : SystemUpdateBehaviour
    2.  {
    3.      public SystemsGroup PreSimulationGroup;
    4.      public SystemsGroup SimulationGroup;
    5.      public SystemsGroup PostSimulationGroup;
    6.  
    7.      protected override void Init()
    8.      {
    9.          // Here we define what systems each group contains
    10.          PreSimulationGroup = new SystemsGroup(typeof(SomeSystem), typeof(SomeOtherSystem));
    11.          SimulationGroup = new SystemsGroup(typeof(RigidbodySimulationSystem));
    12.          PostSimulationGroup = new SystemsGroup(typeof(YetAnotherSystem));
    13.      }
    14.  
    15.      protected override void OnUpdate()
    16.      {
    17.          // Start by updating the first group
    18.          PreSimulationGroup.Update();
    19.  
    20.          // Now, run the rigidbody simulation update 5 times in a row
    21.          // I'm not sure this sort of thing would be possible in the current ECS (?)
    22.          for(int i = 0; i < 5; i++)
    23.          {
    24.              SimulationGroup.Update();
    25.          }
    26.  
    27.          // End by updating the last group, AFTER the 5 steps of simulation are conpletely done
    28.          PostSimulationGroup.Update();
    29.      }
    30.  }
    I'd be curious to hear your thoughts on this. Seems to me like this would solve the need for manual update-calling for online games, AND wouldn't cause trouble with dependency management since all groups are isolated
     
    Last edited: Apr 13, 2018
    floboc likes this.
  11. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Triggering updates manually is a bit different since it does not use data which is not declared up front. It should not be required in most cases, but what you describe about rolling back and re-running simulation in a multiplayer game seems like a case where it would be useful.
    I think you should be able to do something like what you describe already today, some pseudo code *I have not verified that this specific code compiles and work*
    Code (CSharp):
    1. PreSimulationGroup = new  List<ComponentSystemBase>();
    2. PreSimulationGroup.Add(World.GetOrCreateManager<SomeSystem>());
    3.  
    4. foreach (var sys in prep)
    5.     sys.Update();
    If you do that in some code inserted directly to the playerloop (or a system in a different world or even a MonoBehaviour) it should do what you want.
    It will not insert full syncronization of all jobs between each phase, but I don't know why you would want that. It still tracks the data access so inserting such a flush would just make it slower with no behavioral change. If you do need it for some reason it should be possible to do by calling CompleteAllJobs on the entity manager after the phase.
    You would also want [DisableAutoCreation] on the systems you update manually to make sure they are not executed in the player loop as well.
     
    PhilSA likes this.
  12. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Ah, sounds like this is exactly what I need! I'll give it a try, thanks
     
    Last edited: Apr 13, 2018
  13. floboc

    floboc

    Joined:
    Oct 31, 2017
    Posts:
    91
    Please tell us how it goes, I am interested in this as well :)