fishfolk / jumpy

Tactical 2D shooter in fishy pixels style. Made with Rust-lang 🦀 and Bevy 🪶
https://fishfolk.org/games/jumpy/
Other
1.64k stars 118 forks source link

📶 Network Performance #790

Open zicklag opened 1 year ago

zicklag commented 1 year ago

This issue is to track networking performance improvement efforts until we get it to a place where we think it should be and is sufficient for normal play.

zicklag commented 1 year ago

One thought that might be causing lower-than-expected networking throughput is the fact that we are currently sending inputs as raw datagrams, without making sure that they fit into the MTU. We may need to fragment the packets to fit them into the MTU and avoid them being dropped.

erlend-sh commented 1 year ago

I asked @maniwani for advice and they shared some preliminary thoughts:

I can think of some usual suspects for stuttering

GGRS has a fixed-timestep simulation. To render that smoothly, you can either:

  1. Interpolate
    • Save the current (tick N) and previous (tick N - 1) simulation state. Every frame, render the output of interp(state N - 1, state N, percent), where percent is the time remaining in the fixed timestep's accumulator divided by the timestep.
    • If you wanted to avoid the one tick of interpolation delay, you can have your clients always predict an additional tick further into the future (tick N + 1), so that every frame you can render the output of interp(state N, state N + 1, percent).
  2. Substep
    • Every frame, after client has advanced their simulation 0+ ticks, restore their state to the latest "full" tick (tick N), then simulate one additional "partial" tick using the time remaining in the fixed timestep's accumulator as dt. Render the output. To do any of these, notice you'll need copies of the state.

It's good that you use the unreliable QUIC channel to exchange inputs, so I think we can rule it out.

I can't remember if having too many ticks to resimulate can cause stuttering, but if it does, there's stuff you could do. Since you have a client-server network / client traffic goes through the server, I'd recommend making the server responsible for:

  • Being the anchor/reference for clients to synchronize their simulation clocks.
  • Retransmitting inputs.
    • It'll be faster than if clients had to do it. The sooner inputs arrive, the less clients will need to resimulate.
  • Enforcing a late cutoff for inputs.
    • Basically, if the server does not receive a client's input for tick N in time, it'll reuse their tick N - 1 input. If the input for N arrives late, the server will ignore it. Having a cutoff prevents bad/struggling clients from increasing the number of ticks the others have to resimulate.
zicklag commented 1 year ago

Very useful thoughts. I've considered doing interpolation before, and substep sounds like a cool and simple idea, too.

Having the server responsible for some stuff is a very interesting idea. That might require some collaboration and changes in GGRS, but it could be quite useful.

I still think there's a fundamental network transport issue somewhere, especially since the GGRS author said that they got a smooth gameplay before over the internet ( not sure with what game ). I want to try to tackle that first.


The next step for this I think is to create a networking transport trait that we can use throughout the game that will allow us to easily swap out the piece of our networking stack that is repsonsible for sending raw messages.

This will allow us to:

It shouldn't be a huge deal to setup, because we will just be taking our existing logic and doing a one-time transition to use our new NetworkTransport structs/methods instead of it being hard-coded to use the quinn structs/methods.


After we get that setup, then I think I should also make a debug window that we can use to test the network bandwidth, to see how many megabits we can send over the transport if we send stuff as fast as we can. That should help tell us if there is a serious issue with our transport speed.