ashley icon indicating copy to clipboard operation
ashley copied to clipboard

Split updating into updatePhysics, update and interpolate to make using a fixed timestep easier

Open drusin opened this issue 9 years ago • 10 comments
trafficstars

I tired to enhance Ashley to make it easier to apply the technique mentioned here: http://gafferongames.com/game-physics/fix-your-timestep/.

Now you can have this in your main loop:

accumulator += delta;
while (accumulator > PHYSICS_STEP) {
   engine.updatePhysics(PHYSICS_STEP);
   accumulator -= PHYSICS_STEP;
}
engine.update(delta);
engine.interpolate(delta, PHYSICS_STEP, accumulator);

Then you can use the PhysicsSystem and InterpolatingSystem interfaces to implement fixed timestep and interpolating behaviour.

drusin avatar Mar 28 '16 14:03 drusin

Thanks for the PR!

I don't think we should introduce the concept of physics into Ashley. The framework is meant to be generic, potentially suitable for all games. Some games do not need physics at all whilst some physics engines (such as Bullet), implement a fixed time step on their own.

You could always extend Engine to allow for this or wrap it in another class that manages a fixed timestep.

What do you think about this?

dsaltares avatar Mar 30 '16 20:03 dsaltares

I was also thinking about something like this, when multiple IntervalSystem's need to coordinate so that the Physics and Game Logic intervals will pass by both systems per interval not have their intervals be isolated.

Of course we could just extend Engine to coordinate this for us or wrap both systems in an IntervalSystem.

This reminds me, i think it would be kind of nice to have a limit option on the IntervalSystem, limiting the amount of loops in each update. Seeing as how IntervalSystem has a final update method.

bornskilled200 avatar Mar 31 '16 02:03 bornskilled200

Well, to get rid of "physics" we could just rename the class and the method to moething like "FixedStepSystem" and "updateFixedStep()", but that's probably not what you are going for.

Sadly it's currently not possible to get this behavior by extending Engine in your own project, since the most logic changes are done in SystemManager, which is package private.

drusin avatar Mar 31 '16 06:03 drusin

How about a solution similar to what I proposed in #215?

Let's have an Interpolable interface.

public interface Interpolable {
    public void interpolate(float delta, float physicsStep, float inAccumulator);
}

And then a FixedTimeStepSystem wrapper class.

public class FixedTimeStepSystem<T extends EntitySystem & Interpolable> {
    private final T system;
    private final float timeStep;
    private float accumulator = 0.0f;

    public FixedTimeStepSystem(T system, float timeStep) {
        this.system = system;
        this.timeStep = timeStep;
    }

    public void update(float deltaTime) {
        accumulator += deltaTime;
        while (accumulator > timeStep) {
            system.update(timeStep);
            accumulator -= timeStep;
        }

        system.interpolate(deltaTime, timeStep, accumulator);
    }
}

You would then use it like this:

engine.addSystem(new FixedTimeStepSystem(new PhysicsSystem(), PHYSICS_STEP));

Would this work for you? Obviously, you could make the fixed time step system take several systems and update/interpolate them all with a fixed timestep.

The systems themselves only need to implement Interpolable and they don't need to be aware of anything else. The Engine remains unchanged.

dsaltares avatar Mar 31 '16 21:03 dsaltares

I am not too sure about about how correct it is to have systems depend on other systems but if I would to have a FixedTimeStepSystem, this decorator/wrapper class would break how my systems depend on each other, engine.getClass(EntitySystem.class).

It would be nice to still be able to do engine.getSystem(PhysicsSystem.class) even though it was added by engine.addSystem(new FixedTimeStepSystem(new PhysicsSystem(), PHYSICS_STEP)).

This probably is the only complaint I have against a decorator/wrapper system, otherwise I think this is perfectly suitable.

bornskilled200 avatar Apr 01 '16 03:04 bornskilled200

I haven't looked at ashley's sources in a while, but wouldn't porting SystemInvocationStrategy (default impl) over to ashley solve these issues?

It would also make things like per-system profiling easier, without having to modify existing code/systems.

junkdog avatar Apr 01 '16 03:04 junkdog

The idea with the wrapper system might work, but then all systems that do physics stuff need to implement interpolable (and they are not the systems to be interpolated) and have an empty interpolate method. Systems that draw physics bodies need to be interpolated and will be wrapped into FixedTimeSystems which they actually are not... Also the accumulator logic will be called for every wrapped system per frame instead of once per frame (which shouldn't be too bad I think). The best and cleanest solution is probably what junkdog proposed.

drusin avatar Apr 01 '16 17:04 drusin

Hmm. Adding to this, I often find myself "splitting" systems up into groups. For example, to separate updating and rendering, I will remove all non-updating systems, update the engine, remove all non-rendering systems, update the engine, etc. Maybe a solution to both issues could be to introduce a concept of groups to Ashley.

For example, a physics group, update group, interpolation group and render group could be called like this:

accumulator += deltaTime;
while (accumulator > PHYSICS_STEP) {
   engine.update(physicsGroup, PHYSICS_STEP);
   accumulator -= PHYSICS_STEP;
}
engine.update(updateGroup, deltaTime);
engine.update(interpolateGroup, deltaTime);

// Mess around with frame buffers and shaders and such
engine.update(renderGroup, deltaTime);

xSke avatar Apr 23 '16 13:04 xSke

engine.addSystem(new FixedTimeStepSystem(new PhysicsSystem(), PHYSICS_STEP));

Only one system per Class is allowed, so then all systems (possibly with different time steps, e.g. physics, logic and network) would have to be crammed into the same FixedTimeStepSystem. Not very clean.

degussa avatar Feb 04 '17 09:02 degussa

Perhaps I'm stating the obvious here, but I also had a requirement for fixed time step systems (though I wasn't aware it was known as fixed step at that time) and I simply created an abstract class that my other systems extended should they need this functionality.

It looks like I used IteratingSystem as a base, but you could of course lean on anything you like.

public abstract class InfrequentIteratingSystem extends IteratingSystem implements ComponentRetriever {

    protected final float frequencyInSeconds;

    private float timeElapsedSinceLastUpdate;

    public InfrequentIteratingSystem(float frequencyInSeconds, Family family) {
        super(family);
        this.frequencyInSeconds = frequencyInSeconds;
    }

    @Override
    public void update(float deltaTime) {
        timeElapsedSinceLastUpdate += deltaTime;

        if (timeElapsedSinceLastUpdate < frequencyInSeconds) {
            return;
        }

        deltaTime = timeElapsedSinceLastUpdate;
        timeElapsedSinceLastUpdate = 0;

        try {
            super.update(deltaTime);
        } catch (Exception e) {
            log.error("Error running system", e);
        }
    }

}

You can then have as many different systems as you like, with differing "fixed time steps" as you see fit, that are called with the delta since their last execution. I use this on my game server where I have tasks that are somewhat expensive that I might want to run every 30 seconds or so (think checking the entire world for dead bodies to expire, that sort of thing), and also for other things like AI that I might want to run say twice a second, or spell casting progress 10 times a second etc... any system I can think of that does not need to run at 60fps uses this in an effort to claw back server performance wherever I can.

My use case is certainly different to a degree, but at it's core the requirement is the same. I don't see why this couldn't be used to execute your physics or whatever else at whatever step you like.

I think if a pull request is to be accepted into Ashley to facilitate fixed-step logic, a low complexity abstract System such as this makes much more sense. You can of course adapt my simple approach above to facilitate whatever fields you need for interpolation or otherwise.

carlislefox avatar Apr 18 '23 16:04 carlislefox