Is there an update to framerated fixed physics?

Hi,

My project had framerate dependend physics and i stopped working on it…

Is there now a way to resolve this?

If you want control system updates rates, you can look into
FixedTimestepWorkaround
https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/ECSSamples/Assets/Advanced/FixedTimestepWorkaround

Fixed and variable rate spawner.

@Antypodish This is not what i was looking for. Iam looking to specify the Steps of the PhysicsSystem. Not to specify the steps of some other systems.

One workaround is moving SimulationSystemGroup to FixedUpdate:

class WorldBootStrap : ICustomBootstrap
{

    static MethodInfo insertManagerIntoSubsystemListMethod = typeof(ScriptBehaviourUpdateOrder).GetMethod("InsertManagerIntoSubsystemList", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);

    public bool Initialize(string defaultWorldName)
    {
        Debug.Log("Executing World Bootstrap");
        var world = new World("Custom World");
        World.DefaultGameObjectInjectionWorld = world;
        var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);

        DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems);
        UpdatePlayerLoop(world);
        return true;
    }

    public static void UpdatePlayerLoop(World world)
    {
        var playerLoop = PlayerLoop.GetDefaultPlayerLoop();
        if (ScriptBehaviourUpdateOrder.CurrentPlayerLoop.subSystemList != null)
            playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
        if (world != null)
        {
            for (var i = 0; i < playerLoop.subSystemList.Length; ++i)
            {
                int subsystemListLength = playerLoop.subSystemList[i].subSystemList.Length;
                if (playerLoop.subSystemList[i].type == typeof(FixedUpdate))
                {
                    var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                    for (var j = 0; j < subsystemListLength; ++j)
                        newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                    var mgr = world.GetOrCreateSystem<SimulationSystemGroup>();
                    var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                    genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                    playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                }
                else if (playerLoop.subSystemList[i].type == typeof(PreLateUpdate))
                {
                    var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                    for (var j = 0; j < subsystemListLength; ++j)
                        newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                    var mgr = world.GetOrCreateSystem<PresentationSystemGroup>();
                    var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                    genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                    playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                }
                else if (playerLoop.subSystemList[i].type == typeof(Initialization))
                {
                    var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                    for (var j = 0; j < subsystemListLength; ++j)
                        newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                    var mgr = world.GetOrCreateSystem<InitializationSystemGroup>();
                    var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                    genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                    playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                }
            }
        }

        ScriptBehaviourUpdateOrder.SetPlayerLoop(playerLoop);
    }
}

Ofc, you can also manually tick SimulationSystemGroup however you want when you do your own World Initialization. Disclaimer though: I just started playing with Unity.Physics Yesterday, so maybe there are other quirks I’m not thinking of right now.

4 Likes

@Singtaa

Awesome! this is working. I wonder if you could talk a little bit about the code to give us some pointers how the whole thing works.

Lets say i want to execute some methods in fixed update and some in Update and some in LateUpdate … how would i do something like this?

You can also just point me to an explanation if you can :slight_smile:

Sure. So the UpdatePlayerLoop(World world) is just copy-pasta from ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World world). Only difference is it changed typeof(Update) to typeof(FixedUpdate). You can browse through DefaultWorldInitialization.cs and DefaultWorld.cs to see how the Default World and SystemGroups are setup.

You can create your own SystemGroup (see how SimulationSystemGroup is done in DefaultWorld.cs) and insert it to the Update Loop (adding an extra else-if in my previous code sample):

else if (playerLoop.subSystemList[i].type == typeof(Update))
{
    var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
    for (var j = 0; j < subsystemListLength; ++j)
        newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
    var mgr = world.GetOrCreateSystem<MyCustomSystemGroup>();
    var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
    genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
    playerLoop.subSystemList[i].subSystemList = newSubsystemList;
}

Also in your Bootstrap, make sure to do MyCustomSystemGroup.AddSystemToUpdateList(world.GetOrCreateSystem(systemType)) and MyCustomSystemGroup.SortSystemUpdateList() accordingly to your needs.

1 Like

Thanks for the answer :slight_smile:

EDIT: @Singtaa iam not sure if i did everything correctly, but if i go into the entity Debugger it tells me that i have each system added twice. Is this a bug on my side or a bug in the entity debugger?

Bonus question: What do you mean with this?

MyCustomSystemGroup.AddSystemToUpdateList(world.GetOrCreateSystem(systemType))
and 
MyCustomSystemGroup.SortSystemUpdateList()
accordingly to your needs.```

Hmm, I probably need to see your code for this.

So if you are doing your own custom SystemGroup. You’ll need to create it in your world:

var customSystemGroup = world.GetOrCreateSystem<MyCustomSystemGroup>();

Create and add each system to your custom SystemGroup:

foreach (var type in allSystemTypes) {
    var system = world.GetOrCreateSystem(type);
    customSystemGroup.AddSystemToUpdateList(system);
}

Then sort the update list:

customSystemGroup.SortSystemUpdateList();

@Singtaa I just created a new simulation group:

using System.Collections.Generic;
using Unity.Entities;
using UnityEngine.Scripting;

namespace Assets.ECS.Bootstrap
{
    public class UpdateSimulationGroup : ComponentSystemGroup
    {
        [Preserve] public UpdateSimulationGroup() { }

        public override void SortSystemUpdateList()
        {
            // Extract list of systems to sort (excluding built-in systems that are inserted at fixed points)
            var toSort = new List<ComponentSystemBase>(m_systemsToUpdate.Count);

            foreach (var s in m_systemsToUpdate)
            {
                toSort.Add(s);
            }

            m_systemsToUpdate = toSort;
            base.SortSystemUpdateList();
            // Re-insert built-in systems to construct the final list
            var finalSystemList = new List<ComponentSystemBase>(toSort.Count);

            foreach (var s in m_systemsToUpdate) finalSystemList.Add(s);

            m_systemsToUpdate = finalSystemList;
        }
    }
}

In combination with your posted bootstrap:

using System.Reflection;
using Unity.Entities;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;

namespace Assets.ECS.Bootstrap
{
    class WorldBootStrap : ICustomBootstrap
    {

        static MethodInfo insertManagerIntoSubsystemListMethod = typeof(ScriptBehaviourUpdateOrder).GetMethod(
            "InsertManagerIntoSubsystemList", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);

        public bool Initialize(string defaultWorldName)
        {
            Debug.Log("Executing World Bootstrap");
            var world = new World("Custom World");
            World.DefaultGameObjectInjectionWorld = world;
            var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);

            DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems);
            UpdatePlayerLoop(world);
            return true;
        }

        public static void UpdatePlayerLoop(World world)
        {
            var playerLoop = PlayerLoop.GetDefaultPlayerLoop();
            if (ScriptBehaviourUpdateOrder.CurrentPlayerLoop.subSystemList != null)
                playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
            if (world != null)
            {
                for (var i = 0; i < playerLoop.subSystemList.Length; ++i)
                {
                    int subsystemListLength = playerLoop.subSystemList[i].subSystemList.Length;
                    if (playerLoop.subSystemList[i].type == typeof(FixedUpdate))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<SimulationSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] {newSubsystemList, subsystemListLength + 0, mgr});
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                    else
                    if (playerLoop.subSystemList[i].type == typeof(PreLateUpdate))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<PresentationSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] {newSubsystemList, subsystemListLength + 0, mgr});
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                    else if (playerLoop.subSystemList[i].type == typeof(Initialization))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<InitializationSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] {newSubsystemList, subsystemListLength + 0, mgr});
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                    else if (playerLoop.subSystemList[i].type == typeof(Update))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<UpdateSimulationGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] {newSubsystemList, subsystemListLength + 0, mgr});
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                }
            }

            ScriptBehaviourUpdateOrder.SetPlayerLoop(playerLoop);
        }
    }
}

EDIT: then i just added [UpdateInGroup(typeof(UpdateSimulationGroup))] to the correct systems

So a bit more work needs to be done in the Initialize method of the bootstrap because DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups only create and add systems to the default SystemGroups (which does not include your custom one).

Right before the UpdatePlayerLoop(world) you need to do something like what I mentioned previously. For example:

var customSystemGroup = world.GetOrCreateSystem<MyCustomSystemGroup>();
var systems = GetAllMyUpdateLoopSystems(); // <- you need to implement this according to your needs
foreach (var type in allSystemTypes) {
    var system = world.GetOrCreateSystem(type);
    customSystemGroup.AddSystemToUpdateList(system);
}
customSystemGroup.SortSystemUpdateList();

Hopefully I’m not forgetting anything and that should be it.

@Singtaa this is not working unfortunately

I looked into
AddSystemsToRootLevelSystemGroups and it seems like the systems are correctly created under my new UpdateGroup … but somehow the systems are still also registered into the “old” simulation group

EDIT: i investigated further and it seems like the new UpdateGroup is added to the simulationsystemgroup (which makes them appear two times)

EDIT2: which is happening here:

var groups = type.GetCustomAttributes(typeof(UpdateInGroupAttribute), true);
if (groups.Length == 0)
{
simulationSystemGroup.AddSystemToUpdateList(GetOrCreateManagerAndLogException(world, type));
}

inside DefaultWorldInitialization

EDIT3: so we somehow need to be able to set a specific group to “rootlevel” … and this is done like this:

[UpdateInGroup(typeof(World))]
public class UpdateSimulationGroup : ComponentSystemGroup
{
}

but this logs as an error … but it works now … so i guess this is a kind of workaround. … another idea would be to delete the updatesystemgroup from the list returned from here:

DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);

Yeah you just need to filter out your custom system group from the list:

var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default).Where(s => s.Name != "UpdateSimulationGroup").ToList();

You can also consider just rolling your own world initialization code instead of DefaultWorldInitialization as I do in my project for more customization.

Iam not a fan of this as the defaultworldinitalization could be much more dynamic …

is there a github repo where i can submit a pr for a “better” version of DefaultWorldInitialization? I figured as iam doing the work now, everyone else can benefit from it

1 Like

i got it working without any error:

just add those 2 scripts to your project without modifying the DefaultWorldInitialization.cs .

UpdateSystemGroup.cs

using System.Collections.Generic;
using UnityEngine.Scripting;

namespace Unity.Entities
{

    [UnityEngine.ExecuteAlways]
    [UpdateInGroup(typeof(UpdateSystemGroup))]
    public class BeginUpdateEntityCommandBufferSystem : EntityCommandBufferSystem { }

    [UnityEngine.ExecuteAlways]
    [UpdateInGroup(typeof(UpdateSystemGroup))]
    public class EndUpdateEntityCommandBufferSystem : EntityCommandBufferSystem { }

    [UnityEngine.ExecuteAlways]
    [UpdateInGroup(typeof(UpdateSystemGroup))]
    public class LateUpdateSystemGroup : ComponentSystemGroup { }

    
    public class UpdateSystemGroup : ComponentSystemGroup
    {
        [Preserve] public UpdateSystemGroup() { }

        public override void SortSystemUpdateList()
        {
            // Extract list of systems to sort (excluding built-in systems that are inserted at fixed points)
            var toSort = new List<ComponentSystemBase>(m_systemsToUpdate.Count);
            BeginUpdateEntityCommandBufferSystem beginEcbSys = null;
            LateUpdateSystemGroup lateSysGroup = null;
            EndUpdateEntityCommandBufferSystem endEcbSys = null;
            foreach (var s in m_systemsToUpdate)
            {
                if (s is BeginUpdateEntityCommandBufferSystem)
                {
                    beginEcbSys = (BeginUpdateEntityCommandBufferSystem)s;
                }
                else if (s is LateUpdateSystemGroup)
                {
                    lateSysGroup = (LateUpdateSystemGroup)s;
                    lateSysGroup.SortSystemUpdateList(); // not handled by base-class sort call below
                }
                else if (s is EndUpdateEntityCommandBufferSystem)
                {
                    endEcbSys = (EndUpdateEntityCommandBufferSystem)s;
                }
                else
                {
                    toSort.Add(s);
                }
            }
            m_systemsToUpdate = toSort;
            base.SortSystemUpdateList();
            // Re-insert built-in systems to construct the final list
            var finalSystemList = new List<ComponentSystemBase>(toSort.Count);
            if (beginEcbSys != null)
                finalSystemList.Add(beginEcbSys);
            foreach (var s in m_systemsToUpdate)
                finalSystemList.Add(s);
            if (lateSysGroup != null)
                finalSystemList.Add(lateSysGroup);
            if (endEcbSys != null)
                finalSystemList.Add(endEcbSys);
            m_systemsToUpdate = finalSystemList;
        }
    }

}

WorldBootStrap.cs

using System.Collections.Generic;
using System.Reflection;
using Unity.Entities;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
using UnityEngine.Scripting;

namespace Assets.ECS.Bootstrap
{ 
    public class WorldBootStrap : ICustomBootstrap
    {

        static MethodInfo insertManagerIntoSubsystemListMethod = typeof(ScriptBehaviourUpdateOrder).GetMethod("InsertManagerIntoSubsystemList", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);

        public bool Initialize(string defaultWorldName)
        {
            Debug.Log("Executing World Bootstrap");
            var world = new World("Custom World");
            World.DefaultGameObjectInjectionWorld = world;
            var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
            systems.Remove(typeof(UpdateSystemGroup));
            DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems);
            UpdatePlayerLoop(world);
            return true;
        }

        public static void UpdatePlayerLoop(World world)
        {
            var playerLoop = PlayerLoop.GetDefaultPlayerLoop();
            if (ScriptBehaviourUpdateOrder.CurrentPlayerLoop.subSystemList != null)
                playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
            if (world != null)
            {
                for (var i = 0; i < playerLoop.subSystemList.Length; ++i)
                {
                    int subsystemListLength = playerLoop.subSystemList[i].subSystemList.Length;

                    if (playerLoop.subSystemList[i].type == typeof(FixedUpdate))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<SimulationSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                    else if (playerLoop.subSystemList[i].type == typeof(Update))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<UpdateSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                    else if (playerLoop.subSystemList[i].type == typeof(PreLateUpdate))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<PresentationSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                    else if (playerLoop.subSystemList[i].type == typeof(Initialization))
                    {
                        var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
                        for (var j = 0; j < subsystemListLength; ++j)
                            newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
                        var mgr = world.GetOrCreateSystem<InitializationSystemGroup>();
                        var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
                        genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
                        playerLoop.subSystemList[i].subSystemList = newSubsystemList;
                    }
                }
            }

            ScriptBehaviourUpdateOrder.SetPlayerLoop(playerLoop);
        }
    }
    

}

As i have already said, you can just remove the updatesystemgroup and then it works, eg:

systems.Remove(typeof(UpdateSystemGroup));

But i think a Attribute based solution would be better here, eg marking some systems as [RootSystem] or something like this which will be skipped in AddSystemsToRootLevelSystemGroups