Stuttering issue with multi-threaded entity system

Hi all,

I was using a home made 2d engine for physics in my game using zay-es entity system, and I was experiencing almost not stuttering at all. Yesterday I decided to plug the excelent dyn4j physic engine and everything work perfectly : collisions, forces… it’s perfect.

I just have a system that do the following :

  • call the world.updatev(MyAlwaysSameSecondPerTick)
  • update the positioning component of my entities, reading body.getTransform().getTranslation()

But I encounter a jittering issue ! My bodies in motion seems to teleport back a few pixels at some random frame. The effect is irregular and get larger with speed.

Note that I have two threads : the jme thread with all the rendering, and a logic thread running at a fixed 50fps, running the logic and physics (and which seems to be never late, with an average 85% of idle time).

I don’t do any interpolation on the rendering side, and I know that the famous stuttering effect would become visible on very fast bodies or with lower fps. What’s strange is that this wasn’t noticable with my previous home made implementation. The jMonkey fps and logic thread idle time are the same than before.

Note that if I put all my logic systems into the jMonkey thread, everything become perfectly smooth.

Do you see what could have brought dyn4j to create this effect?

It may be the occasion to talk about game loop using entity system :smile:

Here is the system :

public class PhysicWorldProc extends BaseProcessor {
	
	private final World world;

	public PhysicWorldProc() {
		world = new World(new AxisAlignedBounds(100, 100));
		world.setGravity(new Vector2Adapter(Point2D.ORIGIN));
	}
	
	@Override
	protected void registerSets() {
		registerDefault(Physic.class, PlanarStance.class);
	}
	
	@Override
	protected void onEntityAdded(Entity e) {
		PlanarStance stance = e.get(PlanarStance.class);
		Physic physic = e.get(Physic.class);
		Body body = new Body();
		body.getTransform().setTranslation(new Vector2Adapter(stance.getCoord()));
		body.getTransform().setRotation(stance.getOrientation().getValue());
		
		body.setLinearVelocity(new Vector2Adapter(physic.getVelocity()));
		body.setAngularVelocity(physic.getAngularVelocity());
		
		world.addBody(body);
		Pool.bodies.put(e.getId(), body);
	}
	
	@Override
	protected void onTickBegin() {
		world.updatev(Pipeline.getSecondPerTick());
	}
	
	@Override
	protected void onEntityEachTick(Entity e) {
		PlanarStance stance = e.get(PlanarStance.class);
		Body b = Pool.bodies.get(e.getId());
		setComp(e, new PlanarStance(new Point2DAdapter(b.getTransform().getTranslation()), new Angle(b.getTransform().getRotation()), stance.elevation, stance.upVector));
	}
	
	@Override
	protected void onEntityRemoved(Entity e) {
		world.removeBody(Pool.bodies.get(e.getId()));
		Pool.bodies.remove(e.getId());
	}
}

Clarifying, your previous implementation was also running on a separate thread?

In multithreading without interpolation, I saw a lot of jitter but never teleporting backwards. That part is concerning and would be good to verify through logging that it is actually happening and not just a visual misinterpretation.

Else, the two different framerates will produce a lot of jitter. Especially if you are just pushing new Position objects every frame because there will be some latency in the change notification under the covers.

If you are open to interpolation, I can point you to some ready-made stuff, I think.

Edit: for example, I can show you how to integrate this with an ES and avoid the component churn for real time physics: https://github.com/Simsilica/SimMath/tree/master/src/main/java/com/simsilica/mathd/trans

Yes it was.

Yes I am just pushing new components holding the new immutable position. I understand that the rendering may occur a long time after the component has been published.

But since positionning is updated 50 times per second, I was expecting at most 20 milliseconds, making unnoticable “jumps” at a raisonnable speed. Do I miss something on that part?

This is a more general question. When I first used entity system, I understodd that the CPU consumming may become a problem for more complex logic, and that it would be very benefic to make a second thread handeling the logic.

Of course, if the rendering is made at variable framerate, there are some synching problem to solve.

On the other hand, when a read about game loops, I find that most of exemples are made on a single frame, with fixed timestep for logic and interpolation of the rendering, but no multi-threading. So what is the best practice using entity system?

I don’t understand to goal of your implementation. It seems to be a queue of states but what’s the point? In my case, the renderer only take into account the last published position component.

As I understand the interpolation solution, it consists in storing the velocities as well as the current transform, and applying part of these velocities, accordingly to the age of the data. Maybe I miss something on that part too?

Well, the first thing will be to debug why your objects jump backwards and if they really are. If they are jumping backwards then it’s not a frame rate difference problem. It may be that something is getting shared that shouldn’t be or something. Like, make sure your new position components aren’t sharing old vector3fs. 100% new Vector3fs only.

That’s not interpolation, that’s extrapolation. Interpolation is used to interpolate between two known good values. In this case, you’d have your rendering lag slightly from your physics (by at least a frame) and then your visuals would interpolate between the current and last physics frames.

The jitter between 50 FPS and 60+ FPS can be quite striking to those who notice it.

In case you find it interesting, here is my BodyPosition from my ES-based networked asteroids game (unpublished):

public final class BodyPosition implements EntityComponent {
    private byte size;
    private transient TransitionBuffer<PositionTransition> position;

    public BodyPosition() {
    }
    
    public BodyPosition( int history ) {
        this.size = (byte)history;
        this.position = PositionTransition.createBuffer(history);    
    }
 
    /**
     *  Called for a retrieved entity to make sure this BodyPosition
     *  has it's shared transition buffer.  It must be called for
     *  all retrieved BodyPosition components before use.
     */
    public void initialize( EntityId id ) {
        if( this.position == null ) {
            this.position = BodyPositionCache.getBuffer(id, size);
        }
    }
    
    public TransitionBuffer<PositionTransition> getBuffer() {
        return position;
    }
 
    public void addFrame( long endTime, Vector3f pos, Quaternion quat, boolean visible ) {
//System.out.println("addFrame(" + endTime + ", " + pos + ", " + quat + ")");        
        PositionTransition trans = new PositionTransition(endTime, pos, quat, visible);
        getBuffer().addTransition(trans);
    }        
 
    public PositionTransition getFrame( long time ) {
        return getBuffer().getTransition(time);        
    }
    
    @Override
    public String toString() {
        return "BodyPosition[" + position + "]";
    } 
}

Some of that like the ‘transient’ and the initialize() can be ignored as they are related to client side initialization.

The idea here is that you’d set BodyPosition on the entity once. This is bypassing the normal entity change stuff and so we will handle our own multithreading. In this case, the physics thread will add new positions at some game time t. The client side will grab the transition the transition frame that covers time t - someDelay.

PositionTransitsion trans = e.get(BodyPosition.class).getTransition(t - someDelay);
Vector3f v = trans.getPosition(t - someDelay);
Quaternion rot = trans.getRotation(t - someDelay);

That would provide the interpolated position for that particular set of book-ended frames.

But note: if your objects really are jumping backwards then it’s a completely separate problem. You’ll have to debug that first by narrowing down where the jumping backwards occurs.

okay I will track my jittering bug down to check if it is really a rendering issue but, since it become completly invisible when I run the system in the rendering thread, I guess so. I must have changed something else while integrating dyn4j, that might have add jittering.

About the interpolation I understand it very well. Your view don’t extrapolate the actual uncomputed position of your object from the last computed one, which may be aged of 15 or 20 milliseconds (assuming 50 ticks per second), but intend to render the object at the position it had 20 ms in the past. This way, you are sure to have a position before and a position after to make a perfect interpolation.

I still don’t understand why your are storing more than two positions. Wouldn’t it be possible to just archive the previous position in a component ?

If you only want a buffer that is two deep then you can do that… it’s best to have at least 3 for reasons of thread writing/reading. There is a lot of volatile-piggy-backing magic inside that transition buffer class. Two deep means only one transition frame at a time and I’d feel slightly uncomfortable about the UI thread grabbing it right as the physics thread was dropping it.

The reason you might want even more than that is in case your frame rates differ more than you think they might… or in my case, I account for 200 ms of network lag so I have buffers that are like 12 deep or something. In single player, I think my buffers are only 4 deep (just to be sure) and it was fine. The worst that happens if you fall off the bottom is a little stutter. No more than you’d get without interpolation anyway.

Just to reiterate what pspeed already said. I did dead-reckoning extrapolation in my 2D game, the objects jitter sometimes even when I send network packets on localhost.
I went the easy way and send a timestamp in the network packets and interpolate between the “previous” and “new” position using that. Much smoother experience. Even when the network lags I just let the objects stay still until I can interpolate again. The eye seems to be more sensitive to ‘teleporting’ objects than objects that stand still a little bit.

btw also using dyn4j - it is excellent and the latest release has some performance enhancements so it is even sweeter :smile:

Let’s assume your logic/physic thread is running at 50 tps, does that mean that you ask your view to render the state with a lag of 60 milliseconds, just to be sure to read a data that is old enough to be sure it is completly wrote ?

About the networking, you say you’re managing up to 200ms lag. If your UI has no more data to read, meaning that the serveur is late, it won’t draw anything new obiously. Now when the serveur is sending data again, the UI will receive 200ms of changes, but as I understand your code, it will only take into account the three or four most recent changes, so what is the benefit to store 12 states?

Maybe you manage some accelerated UI that draw faster to catch up its late without creating a teleport effect?

The UI is displaying 200 ms behind what it perceives the server to be running at… so it has to accumulate about that many frames in case the packets are coming through super-on-time. They are UDP after all and so can travel very fast in some cases… especially on a LAN.

The packets are also packed with several frames of data… the data is only sent 20 times a second as I recall… so 50 ms worth of data . (my physics thread runs at 60 FPS). So a packet contains 3 frames I guess unless it spills over into another packet. I limit the packet sizes to 1400 bytes of compressed changes to try to get under a common MTU size.