Why is Simulation the default system group?

In regular Unity you use Update for the game logic while FixedUpdate is recommended for physics-related things. Is this different in ECS? Why?

I’m guessing the ECS team feels that (at least for now) most ECS systems will involve pure calculations rather than rendering stuff. And in general, we should use fixed timestamp for pure calc and use Update for input and rendering-related stuff. Again just a guess.

ECS has no physics API yet.

ECS is pretty good at rendering stuff. Specially thousands of objects. That significant advantage over Classic OOP.

I wasn’t saying ECS is good or bad at rendering, just that the amount of math code may be prioritized over rendering code. Hence the preference of SimulationSystemGroup as the default over PresentationSystemGroup.

That is not necessary true. At least not for every case.
Yes you can use ECS, for math heavy applications, which is fine and you will be right.
But you can use ECS as well, just to render 100s Thousands of entities, with little to none math. Something which isn’t feasible in Classic OOP otherwise.
That is why I said [quote=“Antypodish, post:3, topic: 734381, username:Antypodish”]
ECS is pretty good at rendering stuff
[/quote]

I think by using opinionated naming like that it encourage you to think about simulation and rendering separately rather than simulation THEN render. Previously I don’t want to touch fixed update since it was known for physics stuff. Now I am tempted to calculate in that phase since putting everything in presentation now sounds wrong.

3 Likes

Hello, I’m the engineer who worked on this change; hopefully I can shed some light on it. The decision to move “simulation” systems to the FixedUpdate phase was motivated by the DOTS principle of “determinism by default”. In the DOTS world, iterative simulation work should be repeatable given identical inputs, which in turn requires a fixed timestep. The most straightforward way to arrange for this in Unity was to add the SimulationSystemGroup to the FixedUpdate phase.

The intention is for this to be Unity’s default behavior, not a hard requirement. We ultimately want the application to be in control of the update loop. An application that’s willing to give up determinism should be able to opt out of a fixed timestep and return to a more familiar game loop, such as one with a variable timestep and one simulation tick per render tick.

However, that opt-out mechanism doesn’t exist yet in DOTS. We also don’t have a solid solution in place to interpolate between the results of two consecutive simulation ticks, which would be necessary to fully decouple the presentation rate from the simulation rate. Meanwhile. there are legitimate pitfalls when running simulation code with a fixed timestep (and in FixedUpdate specifically). The duration of any frame rate spikes will be amplified, as the spike itself is followed by 1+ frames where the simulation step runs repeatedly to “catch up”, which may in turn cause further hitches. Input processing is also more complicated, as it requires extra tracking to figure out which simulation tick a particular input event should apply to. For all these reasons, we’ll be moving the system groups around the player loop in the next release:

  • SimulationSystemGroup will move from the end of FixedUpdate to the end of Update. The simulation tick rate will match the rendering tick rate, and both will be variable. This is a temporary workaround; long-term, we plan to move simulation back to a fixed timestep once we have more infrastructure in place to allow users to customize the structure of the game loop.
  • PresentationSystemGroup will move from the end of Update to the end of PreLateUpdate. Most notably, it will run after ScriptRunBehaviourLateUpdate and the Unity Animation update, and can react to changes made by those phases (e.g. the final animated transforms can be used to spawn VFX, track cameras to moving objects, etc.) while still running early enough to affect that frame’s rendered output.

Apologies for the churn; this remains an iterative process, and your feedback on the preview packages is invaluable in informing our design process.

Related: If this sort of thing interests you, I highly recommend checking out Tyler Glaiel’s recent article, which discusses the pros & cons of various game loop structures in much more detail.

26 Likes

I didn’t understand the context of this. I just realized it now that I’m upgrading.

Thanks for the detailed explanation, I really like the direction in which this is going. I’d like to say 3 things:

  • I don’t feel like we’d need an automagical solution for interpolating things in the presentation world between simulation world updates (not sure if this is what you were suggesting). This sort of thing is arguably better done manually by users (aside maybe from default components that would provide basic transform interpolation for users who want it), and besides we already need to deal with this kind of stuff with rigidbodies and interpolation in legacy Unity
  • In my experiments with making an ECS Physics engine and an ECS Online game framework, I’ve very often found the need to be able to easily call MyUpdateGroup.Update() manually. I think this would be a good feature to have built-into the ECS. Right now it’s possible to store systems in a list somewhere and iterate on it and call Update() for each, but having a function that Updates all in a specific group would be much better from a UX perspective. It would save programmers from having to manually register systems in lists instead of simuply using the [UpdateInGroup] method. This sort of thing is pertinent for when we need to call a bunch of systems potentially multiple times per frame.
  • Another issue I’ve run into is that there are times where you want multiple different FixedUpdates running simultaneously but at different timesteps. It would help a lot if users had the possibility to declare multiple FixedUpdates easily in the PlayerLoop, and then subscribe their systems to those new FixedUpdates. For example in an online game, you may want non-networked physics rigidbodies to be simulated at 50fps like they are by default in Unity, but you’d also want a different FixedUpdate running at 30fps that simulates your networked gameplay code (aka the “tickrate”). In this case, the non-networked rigidbodies would be purely aesthetic and would not affect player movement/projectiles/etc… Player ragdolls in Halo Reach are an example of this.

These are just a few things that I feel would make the ECS more useable in a wider range of scenarios

3 Likes

I’ll have to get back to you on your other points, but I can address the second one immediately:

This should already be supported. Since a ComponentSystemGroup is derived from ComponentSystemBase, it has an Update() method like any other system. If you’d like more control over exactly when it’s ticked, you can mark the group as [DisableAutoCreation] (to prevent it from participating in default World initialization), create it manually with var group = World.GetOrCreateManager<MyUpdateGroup>(), and then update all systems in that group with group.Update() from your own (main-thread) code. It’s not quite as smooth as it could be yet, and we intend to improve the custom bootstrap feature to support use cases like this, but the basic functionality should be there today.

6 Likes

Thanks for the insights. Splitting logic from interpolated rendering was something I used to do from 20 years ago up until Unity came around so it’s not a new concept, but I really appreciate splitting logic (simulation) from rendering.

Keeping things in sync however for rendering and logic is tricky with things like the the animator, because often animation will be used to position a collider (on a sword for example), so we would probably want to animate things in sim as well, before somehow interpolating it, or doing the work sort of twice…

2 Likes

@cort_of_unity Hi, is this correct that I can’t use FixedUpdate loop for my ComponentSystems until the future version of Entities be ready?

In the current release of the Entities package, component systems in the SimulationSystemGroup are updated from FixedUpdate.
In the next release of the Entities package, those systems will be moved to the Update phase of the player loop.
In some unspecified future version of the Entities package, we will make it easier to choose which of those alternatives you’d prefer.

Meanwhile, it is possible in even the current Entities package to manually create ComponentSystems (by adding the [DisableAutoCreation] attribute to prevent default initialization). These systems can be updated from any main-thread code you’d like – from a MonoBehaviour in FixedUpdate, for example. It’s not as clean/discoverable an experience as we’d like, but it should work.

So what would [UpdateInGroup(typeof(UnityEngine.Experimental.PlayerLoop.FixedUpdate))] do to a system’s scheduling?

Same question as @johnabruce In context of preview27, what’s the recommended way to have a system update with fixed timestamp?

[UpdateInGroup(typeof(UnityEngine.Experimental.PlayerLoop.FixedUpdate))] ? Or, manual initialization like @cort_of_unity just said?

The type T in [UpdateInGroup(T)] must be derived from ComponentSystemGroup, so [UpdateInGroup(typeof(UnityEngine.Experimental.PlayerLoop.FixedUpdate))] will not work (and the error message will be clarified in preview27 to make that more obvious). The workaround for now is the manual creation/update approach described in #13.

1 Like

Thanks for the fast answer. you may want to throw an update to the docs here then to avoid confusion:
https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation~/system_update_order.md

1 Like

Yikes – yeah, that document is very much out of date. I’ll write up a replacement.

5 Likes

Thanks : https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/system_update_order.html

Here is my temporary solution for fixed time step update.

public class MyFixedStepSystem : JobComponentSystem
{
    static readonly float TimeStep = 1.0f / 30.0f;
    float processedTime = 0.0f;

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        float deltaTime = Time.deltaTime;

        if ((deltaTime - this.processedTime) < TimeStep)
        {
            this.processedTime -= deltaTime;
            return inputDeps;
        }

        while ((deltaTime - this.processedTime) >= TimeStep)
        {
            this.processedTime += TimeStep;

            // Do my own update for fixed time step
            ...
        }


        this.processedTime -= deltaTime;
       
        return inputDeps;
    }
}
2 Likes