🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Getting started with game-specific networking/multiplayer, any good books/guides?

Started by
3 comments, last by hplus0603 4 years, 3 months ago

I have some idea of some of the concepts used (deterministic lockstep, dead reckoning, etc.) but probably missing many many things, and not really on the actual implementation details to architect something that feels right to the player.

I had a search around for books and such, but not sure what would really be suitable?

My day job has been heavily involved with networking from a high to low level, well known protocols and some custom ones, so really not looking for another socket guide.
But with many network use-cases even a second or more worst case latency is considered acceptable, with the focus on getting the correct result, but for real-time games that isn't enough.

e.g. in a lockstep simulation it seems simple enough to try out, but if the client waits for the inputs (from all other clients or via a central server) for frame N before running it, why doesn't every lost packet or tiny latency change seem to cause freezing or at least stutter?

while (input = wait_for_server_input()) run(input);

And as for fast paced games first/third person, racing, etc. where the input latency of waiting for rountrip to enact is far too great, less sure. I know of some things like client-side prediction, but only really guessing at the implementation, or how to avoid clients cheating in a whole bunch of ways..

Advertisement

Why doesn't every lost packet cause a stutter in a lockstep game, such as an RTS?

Two reasons:

  1. A typical RTS game keeps running animations even while waiting for step resolutions, so the stutters are somewhat “hidden.”
  2. A typical game networking system that relies on smooth tick updates, will have a time buffer to allow re-sends of lost packets before they're noticed; e g this will be a buffer on the receiving side that queues commands for the future.

For fast-paced games, the lockstep system isn't used; instead the client extrapolates other entities based on available data. When some packet is lost, those data are a little later, but the client keeps extrapolating the entities, and then correct them smoothly one they arrive. This is no different from a physical correction, or a “player stopped moving” correction.

The general game look doesn't look like you're describing, either. It looks more like:

while (game_is_running) {
  drain_and_decode_all_incoming_network_packets();
  read_and_forward_user_inputs();
  while (time_now() > next_step_time) {
    simulate_physics();
    next_step_time += timestep_size;
  }
  render_the_scene();
}

There are tons of things you can adjust here – never step more than X steps at a time; keep clock synchronized with server; interpolate objects that get network corrections; and on and on. The specifics vary based on the specifics of your game and networking model.

enum Bool { True, False, FileNotFound };

hplus0603 said:
A typical RTS game keeps running animations even while waiting for step resolutions, so the stutters are somewhat “hidden.”

True, so any “render” interpolation/extrapolation and GUI stuff is easy to keep going, even if the main logic/physics is lockstep.

hplus0603 said:
A typical game networking system that relies on smooth tick updates, will have a time buffer to allow re-sends of lost packets before they're noticed; e g this will be a buffer on the receiving side that queues commands for the future.

So you mean if the game measures say 70ms latency, it will actually essentially wait for say 100ms to give it some margin for delayed packets. But full resends, that seems like a long time to wait (at least 140ms in this case?)? Do games really delay each step over double the latency to allow for a lost packet detect + resend?

I guess it could only slow down so much if it saw packets actually being lost recently, lost packets seem fairly rare anyway?

hplus0603 said:
The general game look doesn't look like you're describing, either. It looks more like:

Hmm, I don't think I follow here. For a fast paced game with some way to correct for prediction errors (presumably fewer objects and sending more complete state like position/death/etc. occasional).

But in the context of a basic lockstep simulation, isn't it essential that the client has all the input data for a given “simulate_physics” before it does it? If that `while` loop runs different just once on some client then its going to desync?

So I maybe optimistically thought I could write something working from scratch in a few hours, hopefully will have something practical later in the week :(

But in a client-server lockstep model, I was thinking that the server/host player runs “a normal loop”, while all the client players would be dependent on waiting for the flow of data from the server, since they just can't run the simulation without that data, or they would desync immediately?

    void run_client()
    {
        while (_window.handle_events())
        {
            _client->send_actions(_gui.take_actions()); // Player actions not immediately enacted, not tied to a specific frame, let the server synchronise and decide when these happen
            auto actions = _client->recv_frame_actions(); // blocking, if it can't get data, its a disconnect
            frame_pacing_sleep(); // Fixed-rate loop intentionally running a little behind recv_frame_actions to absorb latency variation
            update(actions);
            render(); // TODO: In actual game, rendering can be decoupled
        }
    }
    void run_server() // or local. Basically the same for headless/dedicated server, window/gui can be the CLI or such, and don't render()
    {
        unsigned frame = 0;
        while (_window.handle_events())
        {
            auto actions = _gui.take_actions();
            if (_server)
            {
                _server->recv_player_actions(actions); // actions for everyone elses send_actions
                _server->broadcast_actions(frame, actions); // Every client will enact these on the recv_frame_actions for same frame
            }
            update(actions);
            render(); // TODO: In actual game, rendering can be decoupled

            wait_for_next_frame(); // fixed rate loop
            frame += 1;
        }
    }

For peer-to-peer, seems a lot more complex. I haven't really thought about it, and generally with NAT, firewalls, etc., not sure it is something with a great advantage over “dedicated host player/server”?

[quote]But in the context of a basic lockstep simulation, isn't it essential that the client has all the input data for a given “simulate_physics” before it does it?[/quote]

Yes! You could handle that inside the simulate_physics() function. If you don't have inputs for all of the players for the current game tick number, do nothing in the simulation, and essentially “slip” the game by one tick. You might also want to report this to the upstream server; if lots of ticks get slipped, the amount of buffering needs to increase.

Btw, it's not uncommon to buffer more than one round-trip ahead of time. If RTT is 70 ms, you can easily buffer 200 ms to give time for re-sends of un-acknowledged packets. RTS games can go up over 1000 ms of buffering – basically, the duration of the “yes, sir!” animation that plays for each command, before the units actually start moving, is the amount of buffering you have. Or, to put it another way: the “yes, sir!” animation is a brilliant way of hiding the latency that comes from a strongly buffered lock-step simulation model.

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement