Does anyone know a resource for implementing rollback using the new Unity Physics and DOTS?

I can’t seem to find any good resources utalising the Unity Physics simulationworlds to reverse the physics state back a few ticks and resimulating those ticks to the current tick, any information anyone has on this would be greatly appreciated!

Thank you

It depends on what you mean by rolling back. People use this to mean different things. You can easily maintain an old copy of the world and roll back to that.

There’s two key pieces of tech. One is saving the world state serializing it and restoring it.
And the second is simulating the world.
There’s lots of discussion of ways to do these things but I haven’t found a great source of examples or definitive solutions.

So I have tried to tried to implement the sort of system I’m refering to in it’s most basic sense for a rolling ball. At tick 10 the PhysicsWorld is supposed to revert back to the PhysicsWorld state at tick 5 and simulate the next five ticks to catch up. The code I have wrote does not work how I would expect it to (I’m not as farmiliar with DOTS and ECS as I would like, so I’m certain I am missing some crucial steps). When I revert the physics world back, the ball position does not seem to update and when using the StepImmidiate the ball just seems to stay in the same position (Look at the image of the console to get a sense of what I mean). Any information anyone can give me about how to progress further would be greatly appreciated!

using UnityEngine;
using Unity.Entities;
using Unity.Physics;
using Unity.Transforms;
using System.Collections.Generic;
using Unity.Physics.Systems;

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
public class RollBacker : ComponentSystem
{
    public List<PhysicsWorld> pastWorldInstances = new List<PhysicsWorld>();
    BuildPhysicsWorld m_BuildPhysicsWorld;

    protected override void OnStartRunning()
    {
        m_BuildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    }

    protected override void OnUpdate()
    {
        Debug.Log("Tick " + pastWorldInstances.Count + " ball position is" + GetBallPosition());

        pastWorldInstances.Add(m_BuildPhysicsWorld.PhysicsWorld.Clone());
         
        if (pastWorldInstances.Count == 10)
        {
            RollBackTo(5);
        }
    }

    void RollBackTo(int tickNum)
    {
        Debug.Log("Changing world back to tick " + tickNum);

        //Changes current physicsworld
        ChangePhysicsWorld(pastWorldInstances[tickNum]);
       
        Debug.Log("BallPostion after world change " + GetBallPosition());

        //Steps the physics until catches up to current tick
        StepWorldXTimes(pastWorldInstances.Count - tickNum);
    }

    private SimulationContext SimulationContext;
    void StepWorldXTimes(int stepAmount)
    {
        PhysicsStep stepComponent = PhysicsStep.Default;

        var stepInput = new SimulationStepInput
        {
            World = m_BuildPhysicsWorld.PhysicsWorld,
            TimeStep = Time.DeltaTime,
            NumSolverIterations = stepComponent.SolverIterationCount,
            SolverStabilizationHeuristicSettings = stepComponent.SolverStabilizationHeuristicSettings,
            Gravity = stepComponent.Gravity,
            SynchronizeCollisionWorld = true
        };

        for (int i = 0; i < stepAmount;i++)
        {
            SimulationContext.Reset(stepInput);
            Simulation.StepImmediate(stepInput, ref SimulationContext);
            Debug.Log("Ball position in simulation " + GetBallPosition());
        }
    }

    void ChangePhysicsWorld(PhysicsWorld physicsWorld)
    {
        m_BuildPhysicsWorld.PhysicsWorld = physicsWorld;
        //m_BuildPhysicsWorld.Update();
    }

    Vector3 GetBallPosition()
    {
        Vector3 pos = Vector3.zero;
        Entities.ForEach((ref Translation position) =>
        {
            pos = new Vector3(position.Value.x,position.Value.y,position.Value.z);
        });

        return pos;
    }
}

See this Multiple physics worlds for parallel prediction jobs?

and

    private void copyWorld()
    {
        predictiveWorld.EntityManager.CopyAndReplaceEntitiesFrom(locECSWorld.EntityManager);
        predictiveWorld.SetTime(new Unity.Core.TimeData(locECSWorld.Time.ElapsedTime, locECSWorld.Time.DeltaTime));
    }

or

    private void copyWorld()
    {
        predictiveWorld.EntityManager.DestroyAndResetAllEntities();

        predictiveEntityManager = predictiveWorld.EntityManager;
        NativeArray<Entity> e = locEntityManager.GetAllEntities(Allocator.Temp);

        for (int i = 0; i < e.Length; i++)
        {
            predictiveEntityManager.Instantiate(e[i]);
        }

        e.Dispose();
        predictiveWorld.SetTime(new Unity.Core.TimeData(locECSWorld.Time.ElapsedTime, locECSWorld.Time.DeltaTime));
    }

So I have tried to set it up so that I have a specific world for running the simulation, However, when I use simulationWorld.Update() it gives me an error saying

“AssertionException: Assertion failure. Value was False
Expected: True
PushTime without matching PopTime”

Is this something that is expected to happen? I tried using simulationWorld.PopTime() in different places but it doesn’t seem to help the error, how am I properly supposed to use PopTime() and PushTime()?

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
public class RollBacker2 : ComponentSystem
{
    static World simulationWorld;

    static List<float3> positions = new List<float3>();
    static List<float3> velocites = new List<float3>();

    static bool isSimulating = false;

    protected override void OnStartRunning()
    {
        if (isSimulating){ return;}

        simulationWorld = new World("lockStepWorld", WorldFlags.Simulation);

        var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
        DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(simulationWorld, systems);

        FixedStepSimulationSystemGroup fixGroup = simulationWorld.GetExistingSystem<FixedStepSimulationSystemGroup>();
        fixGroup.FixedRateManager = new FixedRateUtils.FixedRateSimpleManager(Time.DeltaTime);
    }

    protected override void OnUpdate()
    {
        if (isSimulating)
        {
            Debug.Log("Simulating " + GetBallPosition());
        }
        else
        {

            Debug.Log("Normal " + positions.Count + " " + GetBallPosition());

            AddBallStateToLists();

            if (positions.Count == 10)
            {
                RollBackTo(5);
            }
        }
    }

    void RollBackTo(int tickToRollBackTo)
    {
        Debug.Log("Rolling back to " + tickToRollBackTo);
         
        SetBallPosAndVelToTick(tickToRollBackTo);        

        simulationWorld.EntityManager.DestroyAndResetAllEntities();
        simulationWorld.EntityManager.CopyAndReplaceEntitiesFrom(World.EntityManager);
        simulationWorld.SetTime(new Unity.Core.TimeData(World.Time.ElapsedTime, World.Time.DeltaTime));


        SimulateTicks(positions.Count - tickToRollBackTo - 1);

        World.EntityManager.DestroyAndResetAllEntities();
        World.EntityManager.CopyAndReplaceEntitiesFrom(simulationWorld.EntityManager);
    }

    void SimulateTicks(int tickNumber)
    {
        isSimulating = true;

        Debug.Log("Simulating the next " + tickNumber + " ticks");
        for (int i = 0; i < tickNumber; i++)
        {
            simulationWorld.Update();
        }
        isSimulating = false;
    }

    public void SetBallPosAndVelToTick(int tick)
    {
        float3 ballPosition = positions[tick];
        float3 ballVelocity = velocites[tick];

        Entities.ForEach((ref PhysicsVelocity physicsVelocity, ref Translation position, ref Rotation rotation) =>
        {
            position.Value = ballPosition;
            physicsVelocity.Linear = ballVelocity;
        });
    }

    void AddBallStateToLists()
    {
        positions.Add(GetBallPosition());
        velocites.Add(GetBallVelocity());
    }
}

Thanks for the help

You need to run this in a monobehaviour.

Also probably a good idea to add UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP define in your player setting.

Also never use FixedUpdate in monobehaviour.

So all of this code should derive from MonoBehaviour instead of ComponentSystem? In a monobehaviour how can one do the equivelent of Entites.ForEach?

 Entities.ForEach((ref PhysicsVelocity physicsVelocity, ref Translation position, ref Rotation rotation) =>
        {});

and what should I use instead of FixedUpdate?

Set your FixedRateManager. I do most stuff in a fixed time step because I am just Simulating physics and related gameplay logic in ECS. You may want to enable and disable systems or use [DisableAutoCreation] depending on your use case.

 void Start()
    {
       locECSWorld = new World("lockStepWorld", WorldFlags.Simulation);
       var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
       DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(locECSWorld, systems);
       FixedStepSimulationSystemGroup fixGroup = locECSWorld.GetExistingSystem<FixedStepSimulationSystemGroup>();
       fixGroup.FixedRateManager = new FixedRateUtils.FixedRateSimpleManager(MyFixedTimeStep);
}
    private void updateOnce(inputData myInputData)
    {
    //do input stuff here
    locECSWorld.Update();
    }

For example I disable and enable the system that send positions to my graphics so it only runs once per update even though I may have multiple fixed updates. If you don’t care how many fixed updates you can use FixedRateCatchUpManager.

    public void UpdateWorld(List<inputData> myInputData)
    {
        if (myInputData.Count == 1)
        {
            updateOnce(myInputData[0]);
        }
        else if (myInputData.Count > 1)
        {
            myGxSys.Enabled = false; // because we only want to move gx after all calls to update

            for (int i = 0; i < myInputData.Count; i++)
            {
                if (i == (myInputData.Count - 1))
                {
                    myGxSys.Enabled = true;
                }

                updateOnce(myInputData[i]);
            }
        }
    }

As far as entities foreach you can do something like this. This is a very simple Query you can do vastly more complex. see Using an EntityQuery to query data | Entities | 0.17.0-preview.42

        Entity inputE = locEntityManager.CreateEntityQuery(typeof(inputDataECS)).GetSingletonEntity();

        locEntityManager.SetComponentData(inputE, new inputDataECS
        {
            inputDataRef = blobRef1,
            inputButtonsRef = blobRef2,

            hasMoveData = myInputData.hasMoveData,
            moveData = myInputData.moveData,
            selectedUnitID = myInputData.selectedUnitID,
            hasShootingData = myInputData.hasShootingData,
            shootingPos = myInputData.shootingPos,
            shootingDir = myInputData.shootingDir,
        });

Does anyone see

ArgumentException: Destroying all entities failed. Some entities couldn’t be deleted.
Unity.Entities.EntityManager.DestroyAndResetAllEntities () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/EntityManagerCreateDestroyEntities.cs:168)
Tests.RollbackTests.CreateSnapShot (Unity.Entities.World snapShotWorld, Unity.Entities.World world) (at Assets/Tests/Scripts/RollbackTests.cs:62)
Tests.RollbackTests.Update () (at Assets/Tests/Scripts/RollbackTests.cs:50)

When trying to Rollback? It only happens if I have certain components on entities or I have entities with children.

This is exactly what I need to do! I’m new to ECS and need to reset an extensive world (millions of entities) to start state… It would even be better if I could save and restore this copy as it takes quite a while to generate this world. Could you please give me a quick hint on how to approach this?

One possible approach is to maintain two physics worlds, one which represents the current time, and a delayed one which represents the world some time ago. For instance, your delayed world could represent the state of the world 15 seconds ago. Then, as your game runs, you maintain a queue of all the changes made to your current world and apply them to the delayed world 15 seconds later. If you ever need to roll back, say by 5 seconds, you discard the current world, make a copy of the delayed world to replace the current world, and apply all changes made between 15 seconds and 5 seconds ago to the new current world.

I’ve never written this kind of system in ECS, so I’m not sure if it’s possible to copy a physics world’s state over to a new one. But I have to imagine that it’s possible since ECS is just a collection of entities and components.

1 Like

It super trivial to save and restore a world.

        toWorld.EntityManager.DestroyAndResetAllEntities();
        toWorld.EntityManager.CopyAndReplaceEntitiesFrom(fromWorld.EntityManager);
        toWorld.SetTime(new Unity.Core.TimeData(fromWorld.Time.ElapsedTime, fromWorld.Time.DeltaTime));

However there’s an active bug that it doesn’t work depending on your world. For me it starts failing when I add a sharedcomponents even then it only fails after hundreds of successful rollbacks.

1 Like