GhostPredictionSystemGroup called many times per frame dropping FPS very low !

Hello,

I facing a performance issue :
=> The client’s GhostPredictionSystemGroup is called many times(between 2 to 10) per frame.

What can i do to keep those callings as lows as possible ?

Is it possible to Clamp the maximum iteration of the client’s GhostPredictionSystemGroup can do per frame ?

I am targeting 60 fps for my mobile game.

Thanks in advance

Clamping the number of the step in the GhostPredictionSystemGroup will lead to prediction error (especially on the player) that may be not what you want.

The number of predicted tick depends on the latency pretty much. So the only way to keep these low is to have low latency (of course) or trying to execute multiple step at once.

In the next 1.0 release there is the possibility to execute multiple prediction step in batch (one single update with large delta time instead of multiple update).
But in current public release (0.51) we don’t have such a logic.

Simplifying a little bit, it work like that: The first tick or two step are always executed, because they are “close” to the snapshot received and so they are good guesses.
The subsequent steps can be batched (lets say 3/4 step at once) and executed with a large delta time.
In order to make the client prediction work correctly the batches should to be splitted/aggregated based on the player input. In particular, if the player input changed from tick X to tick y, you may want to simulate that tick precisely, (ex: jump). However, if the player input is the same, you can batch the ticks together.

That requires some gameplay adaptation though (that may be not trivial in certain cases).

By the way, this is interesting, maybe this question has already been asked. Why was the scheme with resimulation/reprediction after each snapshot taken ? What benefits does it provide? This can be expensive, especially for a mobile device and if physics is being resimulated.

For example, in the Overwatch netcode video talks about resimulation only if the state of the world on the client and server do not match. Upon receiving a snapshot, the client compares its world state with the state from the snapshot and does world resimulation only if the states diverge (misprediction). Client/server world mismatch (misprediction) and resimulation occurs much less frequently than with every snapshot.

Prediction step batching is an interesting idea, haven’t heard about it before. I hope this won’t be too hard to work with this.

We are “pessimistically” predicting the entities states (not the whole world state) from the tick of the last snapshot received for 5 main reasons:

  • we have to deal with partial snapshot (not all entities fitting inside a single packet). That break some determism in the simulation.
  • the cost of that comparison, that may be quite high in both memory and cpu (we need to store the world state for all the ticks on the client) especially for large world.
  • we can’t guarantee your gameplay code is deterministic either.
  • in presence of entities interaction (i.e predicted physics) the probability that the state is different is quite high. So, in addition to the cost of the rollback, you are also adding on top the cost of the comparison.
  • some robustness (the loop is always the same) and simplicity in the approach.

To mitigate the cost of the rollback, we are only re-simulating the entities for which we had received a new snapshot (technically speaking). The others are kept with their current state. The idea was that this, in principle, should have been able to reduce the overhead by a good margin.
However, when physic is present (this is there real deal, but this is true also with other things) the cost of scheduling the update for just some entities is still very high (mostly job scheduling).

We though about adding such optimisation (the snapshot comparison) to reduce the rollback costs (especially on mobile) but hasn’t been planned yet.

The comparison though must be fuzzy. Fuzzy may be necessary because the state may be not exactly the same, for example quantization just to mention one. That may requires some custom user code too in some cases.
But we can indeed skip some prediction in some scenario, especially if all entities fit in the snapshot.

However, with partial snapshot it is hard to get the same simulation result as the server on client. I’m not even speaking about strict determinism here. There may be for example inter-dependency in between entities that may cause different computation on the client and on the server, with the results that the predicted entities state on the client is always slightly off, enough to cause a re simulation anyway.

@CMarastoni Thank you for your answer !

So the reason why the client’s GhostPredictionSystemGroup is called many times per frame is ONLY Latency ?

I was thinking that maybe if the prediction was not the same with the server, it triggers also the client’s GhostPredictionSystemGroup to be called many times ?

Can you give some basic example on how to prevent systems to run many times if Player Input did not change ?

Thanks in advance

Yes, the mai reason is latency.

No. And also you always tend to mispredict unfortunately (is a prediction in the end, it is usually good for local player, but off for other things).

You can’t prevent that at the moment.
The prediction is always run (if you have predicted entities) as soon a snapshot (that contains predicted ghost updates) is received.
Even if your input does not change, that does not means predicted ghosts in the world does not update/change.

To implement the logic to avoid skipping prediction are necessary some changes to NetCode systems and also add robust backup solution for the entities states.

Ok, thank you @CMarastoni

So basically i can do anything regarding the client’s GhostPredictionSystemGroup calls…

Because in the profiler sometimes the client’s GhostPredictionSystemGroup is called between 2-3 times and then suddenly it goes up to 7-9 times… And those tests are made locally (server and client connected to the same local wifi)…

Any clue on why it goes up like that suddenly ?

@CMarastoni ,
Is there a way to know if all our date fits in one snapshot or multiple snapshots ?

Because if i optimize my data to fit in one snapshot, it can reduce the number of client’s GhostPredictionSystemGroup call ? right ?

no, it will not reduce the number of call at all. Snapshots are sent at the NetworkTickRate (that is by default the same as SimulationTickRate, so let’s say 60hz).
The client always predict from the received server tick to client current tick (that is ahead).

The only thing that can affect how frequently you rollback is reducing the NetworkTickRate (so let’ say 20hz). That has of course other implication (i.e, larger misprediction)

@CMarastoni , Thanks for the answer.

The permanent prediction scheme looks powerful, but I’m not sure that this approach is more intuitive than resimulation on prediction error only.

Perhaps the same scheme can be used for resimulation on prediction error only. We check the state of the entities from the partial snapshot and resimulate this entities only if the state diverges (position, velocities). We don’t necessarily have to check for an error every tick, but the more often the check occurs, the larger the error can be and its correction can be noticed by users/players.

Of course, the user should be able to define/override what is a prediction error and what is not in the custom code. (comparison of position, velocities)

Indeed, prediction errors can appear during quantization, loss of input, collision with a player-controlled entity, errors in the code that violate determinism. But these events can be much rarer than getting a snapshot 20-60 times per second.

It’s good that there are different approaches with their pluses and minuses. My only slight concern is the performance, however I haven’t tested it in detail.

@EugenyN1 I would say that doing the same loop over and over is the most intuitive in my opinion. You get a snapshot, you re- simulate the current state. There isn’t much to think about (he didn’t or not re-simulate? If it didn’t why? it was identical or there is a bug in my code that say otherwise?).

It is also the simpler honestly, and if that would not have any big performance hit (ideally, can’t be in reality) would be good enough.

When it come to performance then, you are right on mark. A re-simulate on error can be better, depending on the mis-prediction frequency and error tolerance.

As I said, we already though about it while ago, but I think should be a on/off feature (predict only on error).

In our experience It usually have mixed nature:

  • you mis-predict all the time … (usually code mistake or different starting point because of quantization).
  • When you start mispredict you continue for a while. And in this case you are re-simulating every time (on top of the cost for checking the state identity).

Yes it can be used as well with partial updates. It is just that requires a little more logic here and there.

1 Like

As for intuitiveness, I think it’s subjective. I need to adapt myself to re-simulate every snapshot scheme, in my project re-simulation was rare, once every few seconds. But we didn’t use quantization and there were few physical interactions in the world. Perhaps also simulation debugging is easier in conditional/on error re-simulation scheme, but if prediction errors are rare.

I agree it’s a good idea.