Looking for some ECS architecture advice for a specific case

Go straight to this post for a clearer explanation of the problem


I’m in the early stages of converting my networking system to ECS and Jobs, and trying to come up with a good architecture for it. Here’s the specific case I’m working on: I have a MainSimulationSystem that handles reverting the world to a previous state when receiving network data, and resimulating to the present all in a single frame. Here’s some pseudocode for that “MainSimulationSystem”

public class MainSimulationSystem
{
    protected void OnTick()
    {
        // Handle resimulation of previous ticks if necessary
        if(receivedNetworkData)
        {
            int simulationTick = receivedTick;

            // Tell each gameplay system to revert world to received tick
            foreach(IStateSystem s in allStateSystems)
            {    
                s.RevertWorldToTick(simulationTick);
            }
 
            while(simulationTick < currentTick)
            {
                // Tell each gameplay system to save the world state at this tick
                foreach(IStateSystem s in allStateSystems)
                {    
                    s.SaveWorldToTick(simulationTick);
                }

                // Tell each gameplay system to simulate a frame
                foreach(ISimulationSystem s in allSimulationSystems)
                {    
                    s.Simulate();
                }

                simulationTick++;
            }
        }

        // Resimulation is over. Now we simulate the current frame

        // Tell each gameplay system to save the world state at present tick
        foreach(IStateSystem s in allStateSystems)
        {    
            s.SaveWorldToTick(currentTick);
        }

        // Tell each gameplay system to simulate a frame
        foreach(ISimulationSystem in allSimulationSystems)
        {    
            s.Simulate();
        }
    }
}

As you can see, all of the “ISimulationSystems” and “IStateSystems” will possibly run different kinds of updates several times within the same frame. I would like to make all ISimulationSystems and IStateSystems be JobComponentSystems. But as far as I know (and I know very little) this notion of running different kinds of updates multiple times in a frame seems to be incompatible with JobComponentSystems, which seem to only be able to have one update that automatically runs once per frame.

Instead of relying on JobComponentSystem.OnUpdate(JobHandle inputDeps), would it be possible to just add other functions to a JobComponentSystem, and make those functions be called manually from other systems? Would it break all kinds of optimizations? What is the JobHandle that’s passed as parameter to JobComponentSystem.OnUpdate(), and how can I pass it to my custom functions?


Also, don’t hesitate if you can think of a better way to approach this. But right now, it seems to me like if I can’t call updates on external systems manually, I’d be forced to put all the logic of my entire game inside MainSimulationSystem

Two ways of running systems multiple times.

  1. Update your systems manually
  2. Make your systems generic. (Generics are not updated automatically). Then derive from that a class for each instance you want to update. Control order with UpdateBefore/UpdateAfter attributes.
1 Like

EDIT: nevermind what I just wrote, I just understood how to arrange things to make my original example work using separate worlds and “ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);” as in the link you provided

Thanks!

Original post

Awesome,

And just to be sure: is there any caveat to making a function in a regular ComponentSystem that acts like an update and that is called externally?

public class SomeSystem : ComponentSystem
{
    struct Data
    {
        public ComponentDataArray<MyComponent> MyComponents;
    }
    [Inject] private Data m_MyComponents;

    public struct JobA : IJob
    {
        // Job that affects MyComponents....
    }

    public struct JobB : IJob
    {
        // another job that affects MyComponents....
    }

    public void DoSomething()
    {
        // Schedule JobA...
    }

    public void DoSomethingElse()
    {
        // Schedule JobB...
    }
}

For example if I called DoSomething() and then DoSomethingElse() on that system from another system’s OnUpdate(), would it be just as good as if I had two JobComponentSystems doing those things?

Basically I’m trying to understand if JobComponentSystems and OnUpdate() calls have special optimizations made specifically for them that I won’t get if I handle jobs manually in a custom function of a regular ComponentSystem. Because even with those two options you explained, I don’t know how I could make my original example work

Really sorry to bring this back once again, but I am still having trouble figuring out the “correct” way to make this work afterall. I’ll try to reiterate my problem more clearly and without a wall of text. Here’s pseudocode for a simplified version of what I want to be able to do:

public class MyMainManager
{
    // This would be called every fixed update
    public void TickSimulation()
    {
        // [?] Tell a certain group of systems to run an update
        // For example: Receive network messages, and restore the world to a past state

        // In the case where we are in an online game and have received past data,
        // we might need to "resimulate" several frames of the entire game's simulation within one frame
        while(haveToResimulateAFrame)
        {
            // [?] Tell all simulation-related systems to run an update
            // For example: tick the simulation of all characters and projectiles
        }

        // [?] Tell another group of systems to run an update
        // For example: Handle input for local players

        // [?] Tell all simulation-related systems to run an update (the same systems we used in the resimulation loop, but this time it's for the present frame, after gathering input)
        // For example: tick the simulation of all characters and projectiles
    }
}

I think my problem is essentially this:
I need to be able to put systems into groups, and then manually tell them “MySystemGroup.DoYourUpdate()”. And all of the jobs of one group must be guaranteed to be finished before updating the next group. I think simply having this would solve all of my problems. Right now I’m not sure that [UpdateInGroup] and [UpdateBefore] satisfy this need, because of the part in the while loop in my example code. How would I handle telling a certain group to keep updating multiple times until a certain condition is met, and then run other groups? Would I be expected to put each of my 3 different groups in their own separate ECS Worlds, and manually tell their world to update from some kind of main loop?

I think that the specific solution for your use case would be to clone the Worlds and tick them independently.

At the moment we don’t provide an easy way to do it (but we are working on it, being cloning and merging worlds a key feature in our opinion), but you can safely move entities between Worlds using EntityManager.MoveEntitiesFrom, and you can probably start from its implementation to provide a simple way to copy chunks, before we expose an API to do it. Or just copy the entities via jobs.

Obviously there are a million details that depend on your game: how long the history on the client should be? how many entities you have? do you need to clone all the components to be able to reply the history, or you only need a subset? For example you might just need a subset of them, and by just copying this subset, you will end up automatically restricting the systems that get invoked to only the ones that match what you need. You might tag the cloned entities to control even further which ones get processed and by which system, and so on.

The point I’m trying to make is this: the implementation is very dependent on the way you game works, what we will expose is a set of generic and very efficient functionalities that you will be able to rely on to copy, move, filter, merge components data around.

thanks for your answer,

Could you point me in the right direction for how to trigger an update for a World? Looking at the World.cs class, I’m not seeing anything resembling an Update()


Also, would you have any comment on the approach suggested here?
https://discussions.unity.com/t/697771/11

It seems like it would solve my problem, but will updating everything manually within the same world bypass any optimizations that I don’t know of?

It depends on how your systems are implemented. What Tim’s suggesting should work fine assuming that the Worlds and the ComponentSystems you are referring to are properly initialized (which is the case if you follow his code snippet’s approach). I don’t know what to add more to what Tim wrote.

Once we will ship our solution, things will be more clear.

2 Likes