I need help about my client netcode

Started by
4 comments, last by elboursicode 2 days, 3 hours ago

Hello guys!

This is my first post in this forum, I am not so used to do this, usually I always find solutions to my problems but this time, I already invested a huge amount of time and I struggle.

I am working on a cooperative 2.5D platformer, using only web technologies. You can find the last version here => https://dev.compositethegame.com/en​ (IF YOU ARE NOT IN EUROPE, YOU WILL EXPERIENCE HIGH LATENCY AND ITS NOT REALLY PLAYABLE, that's why I am here)​

All the code of this project is open source and is hosted here => https://github.com/benjaminbours/Composite/tree/level-builder-core-back

So basically, in term of design, I followed, mainly this => https://www.gabrielgambetta.com/,​ so I have an authoritative server, and client side prediction. On the URL of the project I shared before, if you are close to Strasbourg in France, you might have the chance to experience the best possible experience, where the game is performant enough and not too laggy.

So basically, my issue is that some people were complaining about latency issues or performance issues. Digging more into it, what is consuming most of the client resources is state reconciliation and state interpolation / display state. Correct me if I am wrong but I think what I did so far is that, I am delaying the state displayed, depending of the latency, which in case of low latency is find and provide a good experience, because all state corrections are applied off screen and I only display clean game state to the client. In case of high latency it sucks hard, it's either unplayable because it's too much delayed, or the prediction history buffer is exploding the memory because it has to keep a lot of states in memory to correct when receiving an update from the server.

My design is obviously bad, I feel I miss something. I know in the code I provided, the interpolation usage is terrible. I can see there are so many rooms for optimizations and to have more control over the performance accordingly with the latency but I really struggle to make it.

I tried another mechanism, that would be to not have a prediction history but to apply state correction over multiple frames with interpolation direction on the displayed state. It was not successful, again I probably managed it poorly but it was an increasing differences between the state to correct and the corrections.

And even snapping completely the position to force them, in case of players collisions, it creates multiple weird teleportation, for probably 1 seconds before it stabilizes. Its not the kind of snap that is accetable, I would have expect one very quick teleportation, not my player being project to the 4 corners of the screen before getting to the correct position.

So to resume, despite my researches, the best result I had was delaying the display state accordingly with latency and correct offscreen. Other techniques, I failed to apply them.

I feel like I am not so far from somethings very performant and stable, but I could really use the eyes of an expert or somebody with experience in order to unlock the situation at this level, because it touches the core of the game. Then I will be able to focus more on the features and the many things the game is missing but I can't focus on something else while this issue, which is compromising the whole project potentially, is still there.

Most of the client side logic about game state management is in 2 files:

Thanks in advance for your help 🙏

To really give you the maximum information, I know having a low latency game with a single server in Strasbourg is not viable, I am still developing the project. I plan at some point to have a region selector for people to connect on dedicated server in NA, EU, etc. It will help for sure, but I need to have something more stable than what I have now, before thinking about it. It would just hide some bad design in the development.

Advertisement

Hi there,

Lot of information in this post, and I can feel the struggle.

I've tried your demo and as you say the ping is terrible (more than 1sec to actually move). It does not seem like you have any client side prediction or maybe it's not implemented correctly. I don't really have the time to check out your code. You obviously on the right track using the amazing gabriel gambetta walkthrough.

Not sure if this will help, but I recently implemented for the movement in my current project and it works amazingly even with terrible ping. If you look at https://github.com/orion3dgames/t5c/blob/main/src/client/Entities/Entity/EntityMove.ts​ :

  1. Entities update loop continously lerps at 60 fps from current position to next position
  2. When a player uses a input key, I send that straight to the server at 10 fps with a sequence number using processMove() and then do a predictionMove() to actually move the player locally based on that input. I also store all inputs pressed so we can reconcile later.
  3. Every time the current player receives an update for himself, I then use the reconcileMove(sequence). This will iterate through all stored inputs, removed all the ones older than the received sequence and then apply all the remaining in sequence.
  4. I've never done any networked jumps, but there is no reason it should not work the same way.

This wasn't clear for me at the start but this is only useful for the current player, everyone else can just be “lerped” to whatever is the new position.

The easiest way to debug is usually to dumb down functionalities to the minimum, how about you remove everything but the left to right movement and get that working first?

Look forward to seeing your progress,

Good luck,
Orion

I think understanding your issues is going to be mostly about understanding your own code and your own bottlenecks.

First, you can't beat the speed of light, or signal over the wire. That base latency is unavoidable. You can mitigate it by hosting games in places closer to the players, but ultimately if you have people in Perth Australia, London England, and Shanghai China trying to play together your connection is going to suck no matter what you do.

You'll need understand how much data you're sending, and how often you're sending it. The more data you send the longer it takes. Compression is faster than transmission, and the typical approach is very dense encoding along with compressed data. For games you're also looking at signal to noise ratios, sending lots of tiny packets means you're wasting a lot of bandwidth on headers, so look for ways to send better data less frequently. If you're only sending 30 or 40 bytes you're not filling up a single network packet so you'll need to tell the system to send off the incomplete packet rather than waiting around to bundle it up. If you're sending a kilobyte you're going to take a long time. Some of the sweet spots for performance are 68 bytes, 576 bytes, 1280 bytes, and just under 1500 bytes, but even those are subject to details. This is something you need to evaluate specifically for your own code, it will be different from other people's games and doesn't generalize.

Once data is transmitted you'll need to understand how you're using it. If your game simulation runs on a 60 Hz tick then you're going to introduce 8ms on average from that alone. If your game simulation runs at a 10 Hz tick then you're going to introduce 50ms on average from that. Faster means more responsive, but also more processing work.

Next you'll have more time introduced from your game itself. The actual simulation takes time. Displaying simulation results takes a frame. If you're double-buffering or even triple-buffering your graphics (needed for some effects) you'll add another two or three graphics frames on the clients before they see a response.

Get hard numbers for those first, then you'll know where you can work on. You might accidentally be adding 20, 50, 70 or more milliseconds due to implementation decisions in those areas, so you need to accurately profile the code to understand it.

When you can confirm that you're transmitting and processing data as fast as the underlying systems allow, you can also look at ways to mitigate the appearance of latency like playing an audio effect immediately, playing different animations on senders versus receivers, and other tricks like the ones in the links you posted. But the tasks of masking slow transmissions should be secondary to solving actual slowdowns.

If you're using GGPO, why is there latency before moving? The whole idea is that you immediately apply the input commands, and then fix up the past if you receive a correction you didn't predict correctly.

Also, 1 second between input action and actually seeing something on the screen, is way slower than the actual latency between the US and Europe. From the US West coast to Northern Europe, the round-trip time is about 170 milliseconds. If you're seeing something worse, then you're getting in your own way. You will need to figure out where the time is going and get close to that theoretical time. You should be able to reasonably achieve 4x frame time plus round-trip-time, and with 60 Hz frame rate, that's still under 250 ms.

(It's four frames, because you'll queue once for the frame going out, once on the receiving end, then once for the return response, and once again for receiving it.)

enum Bool { True, False, FileNotFound };

Thank you guys for the time you took to read and to answer me! 🙏🙏🙏

I was overwhelmed when I did this message, you were right I was actually lost in my own code. The project is bigger than just this aspect; even if it's probably the most key aspect; and it took me so long to get my head focusing enough on that part.

I think I finally reach a decent result but it's for me to say, please have a look and let me know what you think!

If you could even tell me where you are located, it would be even better feedback for me.

And any kind of feedback is valuable to me, not only about the initial problem I described.

https://dev.compositethegame.com

If you are interested about any aspect of this project => https://linktr.ee/compositethegame

Thank you a lot again,

Benjamin

Advertisement