17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 21:29:19 +02:00
Files
maszyna/network/manager.h
Hirek193 c0dbf99f1a multiplayer: whoever drives a train runs it, and their ride is the smooth one
Until now the server ran the physics of everything and a client re-ran it from
the server's frame times, corrected a few times a second. That is the worst of
both: the client's own train never moved smoothly, its controls answered a
round trip late, and the two copies still sat metres apart between corrections.

Simulation authority now follows the train:

- the first person aboard a train runs its physics on their own machine, and
  what they produce is what the rest of the session is told about it. A train
  with nobody in it belongs to the host. Every peer works the owner out the
  same way, from the crew of its lowest numbered car, so nobody has to be told
- a peer never applies corrections to a train it runs itself. Its own physics
  is the authority on it, and overwriting that is precisely what made the
  owner's ride stutter
- a client publishes the state of its own trains upstream, ten times a second.
  The server takes it, filtered so a peer can only move the trains it is
  entitled to move, and passes it on to everybody else in its own stream
- so the picture is: your train is smooth for you and may stutter for the
  others; their trains may stutter for you and are smooth for them

Two things follow from it:

- commands for the train you run take effect the moment you press the key
  instead of after a trip to the server and back. They are still sent, so
  everybody else sees them, and the echo is dropped when it returns
- the client no longer replays the server's frame times out of a jitter buffer.
  It runs on its own clock, like the host does, and simply carries out the
  commands as they arrive. Playing back somebody else's frame times meant the
  world ran at the wrong speed whenever the two machines drew at different
  rates, and every buffered frame sat between the player and their controls

Also: the server only sends a frame when it has commands in it, or every tenth
one to carry the tick, instead of one per rendered frame.
2026-09-07 00:19:40 +00:00

76 lines
2.6 KiB
C++

#pragma once
#include <memory>
#include "network/network.h"
#include "input/command.h"
namespace network
{
class server_manager
{
private:
std::vector<std::shared_ptr<server>> servers;
std::shared_ptr<std::fstream> backbuffer;
public:
server_manager();
void push_delta(double render_dt, double dt, uint64_t tick, uint64_t state_hash, const command_queue::commands_map &commands);
command_queue::commands_map pop_commands();
void create_server(const std::string &backend, const std::string &conf);
// refreshes the authoritative vehicle roster and publishes it when it changed
void publish_vehicle_list();
// brings the world in line with the crew roster and announces every change
void update_crews();
// sends the peers what the world actually looks like. this is what keeps a client
// in line - the command stream alone cannot, because a client runs its own physics
void publish_state();
// crew request coming from the local participant, which needs no transport
void apply_local_claim(NetworkEntityId entity_id);
void apply_local_leave(NetworkEntityId entity_id);
private:
// how many frames may pass between two unconditional broadcasts of the roster
static const int PUBLISH_INTERVAL_FRAMES = 120;
// how often the authoritative state goes out, and how often it goes out whole
// rather than as what changed since the last one
static const int STATE_INTERVAL_FRAMES = 10;
static const int STATE_FULL_EVERY = 60;
std::vector<vehicle_entry> last_published_list;
int publish_countdown = 0;
int crew_publish_countdown = 0;
int state_countdown = 0;
int state_updates = 0;
int heartbeat_countdown = 0;
// how many frames with nothing in them may pass before one is sent anyway
static const int HEARTBEAT_INTERVAL_FRAMES = 10;
};
class manager
{
public:
manager();
std::optional<server_manager> servers;
std::shared_ptr<network::client> client;
void create_server(const std::string &backend, const std::string &conf);
void connect(const std::string &backend, const std::string &conf);
void update();
// routes a lobby request to whoever holds the authority: straight into the crew
// registry when we are the server, over the wire when we are a client
void request_claim(NetworkEntityId entity_id);
void request_leave(NetworkEntityId entity_id);
// the local scenario is loaded; a client asks the server for a snapshot of the
// world as it stands right now
void notify_scenario_loaded();
// our world drifted away from the authoritative one; ask to be corrected
void request_resync(uint64_t tick, uint64_t state_hash);
};
}