Search Unity

ICustomBootstrap, control update order of systems?

Discussion in 'Entity Component System' started by fholm, Mar 21, 2019.

  1. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    I have the need to be able to very tightly control which systems execute, when they execute and in which exact order. I know there's been revamps to the way bootstrap and also the UpdateOrder attribute.

    This has to do with networking, and during a rollback I need to execute very specific systems in a specific sequence. Is there anything in ICustomBootstrap which helps me do this? Previously I had to setup all systems by hand and call them by hand also.

    I had to do something like this:

    Code (csharp):
    1.  
    2.     _managers.Add(_world.CreateManager<NetCode.BufferSystem>());
    3.     _managers.Add(_world.CreateManager<NetCode.DeltaCompressorSystem>());
    4.  
    And then when I wanted to execute them I would do this:

    Code (csharp):
    1.  
    2.     foreach (var manager in _world.BehaviourManagers) {
    3.       manager.Update();
    4.     }
    5.  
    Is this something ICustomBootstrap helps me with?
     
  2. Leuthil

    Leuthil

    Joined:
    Jul 26, 2013
    Posts:
    97
    I'm running into the same issue as well. Sorry I can't be of help but I'm interested if anyone has a better solution. I'm also manually updating all my systems to ensure update ordering and updating across multiple worlds.
     
  3. Piefayth

    Piefayth

    Joined:
    Feb 7, 2017
    Posts:
    61
    If you need to run a particular system multiple times per tick / frame, there's no real way around updating it manually, especially if there are a variable number of updates/tick.

    That said, it seems like my personal systems fall into two groups. You have A) the systems that run once on every network tick to do initialization/housekeeping/etc, then B) the systems that are core to your game loop that may need to run multiple times in a single network tick to keep the simulation at pace with the network or re-verify simulations with multiple different parameters.

    The systems in category B need updated manually. The systems in category A *can* leverage (to some extent) the built-in ECS update ordering with UpdateAfter / UpdateBefore.

    First off, I assume you are initializing your worlds with DefaultWorldInitialization.Initialize

    Code (CSharp):
    1. DefaultWorldInitialization.Initialize(WorldKey.SERVER_WORLD.ToString(), false);
    2. DefaultWorldInitialization.Initialize(WorldKey.CLIENT_WORLD.ToString(), false);
    When doing so, keep in mind that any system without DisableAutoCreation will be created in these worlds and default to updating in the SimulationSystemGroup. You can, of course, specify this with UpdateInGroup, but we'll use the default. After our worlds are initialized we can update all the built in systems (including our custom systems that are automatically picked up) by simply updating the appropriate groups in Update/FixedUpdate. Note that you can use the same sort of logic in a custom PlayerLoopSystem, rather than use a MonoBehaviour, but this is less code for effectively the same result.

    Code (CSharp):
    1. public class GameLoop : MonoBehaviour {
    2.     float timeSinceLastServerTick = 0;
    3.     float timeSinceLastClientTick = 0;
    4.  
    5.     InitializationSystemGroup serverInit;
    6.     InitializationSystemGroup clientInit;
    7.  
    8.     SimulationSystemGroup serverSim;
    9.     SimulationSystemGroup clientSim;
    10.  
    11.     PresentationSystemGroup clientPres;
    12.  
    13.     void Awake() {
    14.         serverInit = Worlds.serverWorld.GetExistingManager<InitializationSystemGroup>();
    15.         clientInit = Worlds.clientWorld.GetExistingManager<InitializationSystemGroup>();
    16.  
    17.         serverSim = Worlds.serverWorld.GetExistingManager<SimulationSystemGroup>();
    18.         clientSim = Worlds.clientWorld.GetExistingManager<SimulationSystemGroup>();
    19.  
    20.         clientPres = Worlds.clientWorld.GetExistingManager<PresentationSystemGroup>();
    21.     }
    22.  
    23.     void FixedUpdate() {
    24.         if (Settings.server) {
    25.             timeSinceLastServerTick += Time.fixedDeltaTime;
    26.  
    27.             if (timeSinceLastServerTick > Settings.serverNetworkTickrate) {
    28.                 timeSinceLastServerTick -= Settings.serverNetworkTickrate;
    29.                 serverInit.Update();
    30.                 serverSim.Update();
    31.             }
    32.         }
    33.  
    34.         if (Settings.client) {
    35.             timeSinceLastClientTick += Time.fixedDeltaTime;
    36.  
    37.             if (timeSinceLastClientTick > Settings.clientNetworkTickrate) {
    38.                 timeSinceLastClientTick -= Settings.clientNetworkTickrate;
    39.                 clientInit.Update();
    40.                 clientSim.Update();
    41.             }
    42.         }
    43.     }
    44.  
    45.     void Update() {
    46.         if (Settings.client) {
    47.             clientPres.Update();
    48.         }
    49.     }
    50. }
    In the above, calling clientSim.Update() / serverSim.Update() updates ALL the automatically added systems that didn't specify a different group, including user created ones, in those worlds.

    If you use this sort of game loop, you can then leverage UpdateAfter/UpdateBefore on the systems within your SimulationSystemGroup and the order will be correct. At least for me, this cuts down on a large number of updates. If you have additional distinct phases between the default update groups, you can simply make your own group and update that manually. When doing this, it's probably useful to point ScriptBehaviourUpdateOrder at an empty/dummy world to avoid the default system updates.

    Also, when doing this, you might find it annoying that all of your systems are being created in all of your default-initialized worlds (because you stopped using DisableAutoCreation). THAT is where you can use ICustomBootstrap. I have an attribute like,
    [IncludeInWorld(WorldName)]
    that I use to filter the correct systems to the correct worlds in my custom bootstrap.
     
    Last edited: Mar 24, 2019
    Deleted User likes this.
  4. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    Interesting approach, maybe I ask why you have both serverSim and clientSim in the same mono behaviour? Or are you running both on the client (??) also?

    Edit: Or rather why are you creating both server and client world inside the same monobehaviour at the same time?
     
    Last edited: Mar 25, 2019
  5. Piefayth

    Piefayth

    Joined:
    Feb 7, 2017
    Posts:
    61
    Optionally running both on the client so I can have two clients and a server in two Unity instances. That's also a simplification of my full game loop; I have another tick timer that the client and server share when they're being run on the same instance, so it's useful to have them together.

    It's definitely not optimal to run them together like that, since if either the client or server requires a .Complete at any point they both are forced to wait for that single main thread sync point. Depending on what work is going on, could fiddle with the update order / scheduling to try and reduce the impact of Completes, but I have no real need to optimize that yet. Ideally every update would just schedule a pure uninterrupted job chain and the update order between client/server would be irrelevant, but things aren't always so nice and clean, haha.

    Note that the actual world creation and configuration happens well before that game loop starts. I'm also not married to this game loop if you think you see an improvement.
     
  6. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    The reason I asked about the client/server world being created at the same time/in the same script was because i thought it might have something to do with interpolation/etc. I.e. basically the client having 'both' worlds locally but not simulating the server world where the 'server' world is the actual world that receives the state updates, and the 'client' world is what's used for the client side prediction.

    I read your first post again, and I follow how it works... I used to do a manual world creationg (new World()...) but that seems to be broken with the latest updates? The reason I did this is because it lets me define in code a very explicit order the systems are simulated in, I am aware of UpdateBefore/UpdateAfter but when the system count grows it becomes fairly unmanageable to have the 'order' spread all over the codebase.

    Is there any way to call the update on the systems by hand, in the order I define in a linear block of code? Essentially:

    Code (csharp):
    1.  
    2. CallNetSystem();
    3. CallPredictionSystem();
    4. CallInterpolationSystem();
    5. CallRenderSystem();
    6.  
    Obviously a contrived example, but I find having the system call order explicitly laid out makes it much easier.
     
  7. Piefayth

    Piefayth

    Joined:
    Feb 7, 2017
    Posts:
    61
    Shouldn't have to do anything special to do that, but if you are subverting the default bootstrap by using new World instead of DefaultWorldInitilization, you need to manually create and update every system in every World. If you don't rely on ScriptBehaviourUpdateOrder to tick your Worlds, you need to grab every system from every World with World.GetOrCreateManager and call .Update() on each system in Update/FixedUpdate somehow. This will get the order and job scheduling right.

    You can also manually create ComponentSystemGroups, add systems to them, then .Update() each group manually. Might have some organizational benefit.